20

在 SML、Erlang 和其他许多语言中,我们可以定义如下函数:

fun reverse [] = []
|   reverse x :: xs  = reverse xs @ [x];

我知道我们可以像这样在 Scala 中编写模拟(我知道,下面的代码中有很多缺陷):

def reverse[T](lst: List[T]): List[T] = lst match {
  case Nil     => Nil
  case x :: xs => reverse(xs) ++ List(x)
}

但我想知道,我们是否可以在 Scala 中编写以前的代码,也许对后者进行去糖。

将来实现这种语法是否有任何基本限制(我的意思是,非常基本 - 例如类型推断在 scala 或其他东西中的工作方式,显然解析器除外)?

UPD
以下是它的外观片段:

type T
def reverse(Nil: List[T]) = Nil
def reverse(x :: xs: List[T]): List[T] = reverse(xs) ++ List(x)
4

4 回答 4

14

这真的取决于你的意思basic

如果您真的在问“是否存在阻止实现此功能的技术证明”,那么我会说答案是否定的。你在谈论脱糖,你在正确的轨道上。所要做的就是基本上将几个单独的案例拼接到一个函数中,这可以作为一个简单的预处理步骤来完成(这只需要句法知识,不需要语义知识)。但为了使这更有意义,我将定义一些规则:

  • 函数签名是强制性的(例如在 Haskell 中,这将是可选的,但无论您是一次定义函数还是分几个部分定义函数,它始终是可选的)。我们可以尝试安排不使用签名并尝试从不同部分中提取它,但是缺少类型信息会很快让我们感到厌烦。一个更简单的论点是,如果我们要尝试推断隐式签名,我们不妨对所有方法都这样做。但事实是,在 scala 中有明确的签名是有充分理由的,我无法想象改变这一点。
  • 所有部分必须定义在同一范围内。首先,它们必须在同一个文件中声明,因为每个源文件都是单独编译的,因此简单的预处理器不足以实现该功能。其次,我们最终还是得到了一个方法,所以所有部分都在同一个范围内是很自然的。
  • 这种方法不可能重载(否则我们需要为每个部分重复签名,以便预处理器知道哪个部分属于哪个重载)
  • 零件按声明的顺序添加(缝合)到生成的match

所以这就是它的样子:

def reverse[T](lst: List[T]): List[T] // Exactly like an abstract def (provides the signature)
// .... some unrelated code here...
def reverse(Nil) = Nil
// .... another bit of unrelated code here...
def reverse(x :: xs ) = reverse(xs) ++ List(x)

可以简单地转换为:

def reverse[T](list: List[T]): List[T] = lst match {
  case Nil     => Nil
  case x :: xs => reverse(xs) ++ List(x)
}
// .... some unrelated code here...
// .... another bit of unrelated code here...

很容易看出,上面的转换是非常机械的,只需操作一个源 AST(接受这个新结构的稍微修改的语法产生的 AST),然后将它转换成目标 AST(由标准的 scala 语法)。然后我们可以像往常一样编译结果。

所以你去吧,通过一些简单的规则,我们可以实现一个预处理器来完成所有工作来实现这个新特性。


如果从根本上说,您要问“是否有任何东西会使这个功能不合适”,那么可以说这感觉不是很 scala。但更重要的是,它并没有带来那么多。Scala 作者实际上倾向于使语言更简单(例如在较少的内置特性中,试图将一些内置特性移动到库中)并添加一种实际上并不更具可读性的新语法与简化的目标背道而驰。

于 2013-02-11T10:48:31.270 回答
5

在 SML 中,您的代码片段实际上只是语法糖(语言规范术语中的“派生形式”)

val rec reverse = fn x =>
    case x of [] => []
            | x::xs  = reverse xs @ [x]

这与您展示的 Scala 代码非常接近。因此,Scala 不能提供相同类型的语法并没有“根本”原因。主要问题是 Scala 需要更多的类型注释,这使得这种速记语法总体上没有吸引力,而且可能不值得。

另请注意,您建议的特定语法不会很好,因为无法从语法上区分一个案例函数定义和两个重载函数。您可能需要一些替代语法,类似于使用“ |”的 SML。

于 2013-02-11T10:57:02.443 回答
3

我不知道 SML 或 Erlang,但我知道 Haskell。它是一种没有方法重载的语言。方法重载与这种模式匹配相结合可能会导致歧义。想象以下代码:

def f(x: String) = "String "+x
def f(x: List[_]) = "List "+x

这应该是什么意思?它可能意味着方法重载,即方法是在编译时确定的。它也可以表示模式匹配。只有 af(x: AnyRef) 方法可以进行匹配。

Scala 也有命名参数,这可能也会被破坏。

我不认为 Scala 能够提供比您通常展示的更简单的语法。恕我直言,更简单的语法可能仅在某些特殊情况下有效。

于 2013-02-11T10:28:52.277 回答
3

至少有两个问题:

  1. [并且]是保留字符,因为它们用于类型参数。编译器允许它们周围有空格,因此这不是一个选择。
  2. 另一个问题是=返回Unit。所以后面的表达式|不会返回任何结果

我能想出的最接近的是这个(注意这对你的例子非常专业):

// Define a class to hold the values left and right of the | sign
class |[T, S](val left: T, val right: PartialFunction[T, T])

// Create a class that contains the | operator
class OrAssoc[T](left: T) {
  def |(right: PartialFunction[T, T]): T | T = new |(left, right)
}

// Add the | to any potential target
implicit def anyToOrAssoc[S](left: S): OrAssoc[S] = new OrAssoc(left)

object fun {

  // Use the magic of the update method
  def update[T, S](choice: T | S): T => T = { arg =>
    if (choice.right.isDefinedAt(arg)) choice.right(arg)
    else choice.left
  }
}

// Use the above construction to define a new method
val reverse: List[Int] => List[Int] =
  fun() = List.empty[Int] | {
    case x :: xs => reverse(xs) ++ List(x)
  }

// Call the method
reverse(List(3, 2, 1))
于 2013-02-11T10:32:07.690 回答