在Scala中定义函数时,应该使用“def”还是“val”?

9

我正在大学学习编程范式,并阅读讲师提供的课程材料,其中定义了一个函数:

val double = (x: Int) => 2 * x
double: Int => Int = <function1>

但是从我的研究中,我发现并习惯于这样定义同一个函数:

def d (x: Int) = 2 * x
d: (x: Int)Int

我是Scala的新手。这两个定义都会产生一个结果:

res21: Int = 8

当参数为4时。现在我的主要问题是为什么讲师更喜欢使用val来定义函数?我认为这样做更长,而且不是必要的,除非使用val会带来一些我不知道的额外优势。此外,我了解到使用val会使某些名称成为占位符,因此在程序后期,我可能会错误地编写val double = 5,从而导致函数消失!在这个阶段,我相信我学到了一种更好的定义函数的方法,除非有人告诉我其他的方法。


我没有资格对Scala发表决定性的意见,但您尝试在函数定义为“double”之后实际执行val double = 5了吗?我有印象一旦名称被定义就不可能重新定义。无论如何,valdef在这里产生相同的结果。区别只是风格上的差异。通常,使用def定义函数更清晰,但有时可以使用val来定义它也是有意义的。如果您不确定,我建议您选择def,因为这不太可能引起任何混淆。 - kqr
是的,我执行了 val double = 5,输入了 double,然后得到了 res24: Int = 5 - emi
可能是Scala中方法和函数的区别的重复问题。 - 0__
还有这个问题。大多数情况下,你可以使用两种变体,所以这是一个风格问题。我同意你更喜欢方法,但这只是一种偏好。 - 0__
1
val 使某个名称成为占位符” - 这并不重要,def d 也引入了一个名为 d 的符号,你可以通过重新定义它来遮蔽它。在这方面,两者之间没有真正的区别。 - 0__
3个回答

14
严格来说,def d(x: Int) = 2 * x是一个方法,而不是函数。但是,Scala可以透明地将方法转换(提升)为函数。这意味着您可以在任何需要Int => Int函数的地方使用d方法。
执行此转换会产生一些小开销,因为每次都会创建一个新的函数实例。我们可以在这里看到这种情况发生:
val double = (x: Int) => 2 * x
def d (x: Int) = 2 * x

def printFunc(f: Int => Int) = println(f.hashCode())

printFunc(double)
printFunc(double)
printFunc(d)
printFunc(d)

这将导致如下输出:

1477986427
1477986427
574533740
1102091268

当我们使用val来显式定义一个函数时,程序只会创建一个函数并在将其作为参数传递给printFunc时重复使用它(我们看到相同的哈希码)。而当我们使用def时,每次将其传递给printFunc时都会将其转换为函数,并创建多个具有不同哈希码的函数实例。试一试

尽管如此,性能开销很小,通常不会对程序产生任何真正的影响,因此def通常用于定义函数,因为许多人认为它们更简洁易读。


2
最近有个人进行了基准测试,显示“eta扩展”(将方法提升为函数并将其作为参数传递)实际上并不会导致性能损失。原文链接:http://japgolly.blogspot.com.au/2013/10/scala-methods-vs-functions.html - 0__
不错的帖子。我猜垃圾收集也可能会增加,但我想那也会是微不足道的/不存在的。 - theon

5
在Scala中,函数值是单态的(即它们不能具有类型参数,也称为“泛型”)。如果您想要一个多态函数,您需要通过某种方式绕过这个限制,例如使用方法进行定义:
def headOption[A]: List[A] => Option[A] = {
  case Nil   => None
  case x::xs => Some(x)
}

val headOption[A] 不是有效的语法。请注意,这并没有创建一个多态函数值,它只是一个多态方法,返回一个适当类型的单态函数值。


A是指什么?我有时会看到**def nameT**,但我不明白在Scala中它的意思是什么。 - Meas

0

因为你可能会有类似以下的内容:

abstract class BaseClass {
  val intToIntFunc: Int => Int
}

class A extends BaseClass {
  override val intToIntFunc = (i: Int) => i * 2
}

因此,它的目的可能在非常简单的示例中不明显。但是,该函数值本身可以传递给高阶函数:接受函数作为参数的函数。如果您查看Scala集合文档,您将看到许多接受函数作为参数的方法。这是一个非常强大和多功能的工具,但是您需要达到一定的算法复杂性和熟悉度,才能明显地看到成本/效益。

我还建议不要使用“double”作为标识符名称。虽然在Scala中是合法的,但很容易将其与类型Double混淆。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接