定义函数的“ def”和“ val”有什么区别


214

之间有什么区别?

def even: Int => Boolean = _ % 2 == 0

val even: Int => Boolean = _ % 2 == 0

两者都可以称为even(10)


嗨,什么Int => Boolean意思?我认为定义语法是def foo(bar: Baz): Bin = expr
Ziu

@Ziu表示函数“偶数”接收一个Int作为参数,并返回一个布尔值作为值类型。因此,您可以调用“ even(3)”,其结果为布尔值“ false”
Denys Lobur

@DenysLobur感谢您的回复!关于此语法的任何参考吗?
Ziu

@Ziu我基本上是从Odersky的Coursera课程-coursera.org/learn/progfun1中发现的。当您完成操作时,您将了解“类型=>类型”的含义
Denys Lobur

Answers:


325

方法def even每次调用时都会求值并创建新函数(的新实例Function1)。

def even: Int => Boolean = _ % 2 == 0
even eq even
//Boolean = false

val even: Int => Boolean = _ % 2 == 0
even eq even
//Boolean = true

有了它,def您可以在每次调用时获得新功能:

val test: () => Int = {
  val r = util.Random.nextInt
  () => r
}

test()
// Int = -1049057402
test()
// Int = -1049057402 - same result

def test: () => Int = {
  val r = util.Random.nextInt
  () => r
}

test()
// Int = -240885810
test()
// Int = -1002157461 - new result

val定义def时评估-调用时:

scala> val even: Int => Boolean = ???
scala.NotImplementedError: an implementation is missing

scala> def even: Int => Boolean = ???
even: Int => Boolean

scala> even
scala.NotImplementedError: an implementation is missing

请注意,还有第三个选项:lazy val

它在第一次调用时进行评估:

scala> lazy val even: Int => Boolean = ???
even: Int => Boolean = <lazy>

scala> even
scala.NotImplementedError: an implementation is missing

FunctionN每次都会返回相同的结果(在这种情况下为的相同实例):

lazy val even: Int => Boolean = _ % 2 == 0
even eq even
//Boolean = true

lazy val test: () => Int = {
  val r = util.Random.nextInt
  () => r
}

test()
// Int = -1068569869
test()
// Int = -1068569869 - same result

性能

val 定义时评估。

def对每个呼叫都进行评估,因此性能可能会比val多次呼叫差。一个电话,您将获得相同的性能。而且,无需调用,您将不会从中获得任何开销def,因此即使您不打算在某些分支中使用它,也可以对其进行定义。

使用,lazy val您会得到一个懒惰的评估:即使您不打算在某些分支中使用它,也可以对其进行定义,并且评估一次或永远不会评估,但是通过对您的每次访问进行双重检查锁定,您会得到一些开销lazy val

正如@SargeBorsch所指出的,您可以定义方法,这是最快的选择:

def even(i: Int): Boolean = i % 2 == 0

但是,如果您需要一个函数(而不是方法)来进行函数组合或需要更高阶的函数(例如filter(even)),则编译器会在每次将其用作函数时从您的方法中生成一个函数,因此性能可能会稍差于val


您能否将它们的性能进行比较?每次even调用时评估函数不是很重要。
阿米尔·卡里米

2
def可用于定义方法,这是最快的选择。@ A.Karimi
显示名称

2
为了好玩:上2.12 even eq even
som-snytt's

是否有像c ++这样的内联函数概念?我来自c ++世界,请原谅我的无知。
animageofmine17年

2
@animageofmine Scala编译器可以尝试内联方法。有此@inline属性。但是它不能内联函数,因为函数调用是apply对函数对象的虚方法的调用。在某些情况下,JVM可能会取消虚拟化并内联此类调用,但一般情况下不会。
senia

24

考虑一下:

scala> def even: (Int => Boolean) = {
             println("def"); 
             (x => x % 2 == 0)
       }
even: Int => Boolean

scala> val even2: (Int => Boolean) = {
             println("val");
             (x => x % 2 == 0)
       }
val //gets printed while declaration. line-4
even2: Int => Boolean = <function1>

scala> even(1)
def
res9: Boolean = false

scala> even2(1)
res10: Boolean = false

你看得到差别吗?简而言之:

def:对于的每次调用even,它都会even再次调用方法的主体。但是使用even2ie val,该函数仅在声明时初始化一次(因此它val在第4行打印,并且不再打印),并且每次访问时都使用相同的输出。例如,尝试执行以下操作:

scala> import scala.util.Random
import scala.util.Random

scala> val x = { Random.nextInt }
x: Int = -1307706866

scala> x
res0: Int = -1307706866

scala> x
res1: Int = -1307706866

x被初始化,则返回值被Random.nextInt设置为最终值x。下次x再次使用时,它将始终返回相同的值。

您也可以延迟初始化x。即第一次使用它被初始化而不是在声明时。例如:

scala> lazy val y = { Random.nextInt }
y: Int = <lazy>

scala> y
res4: Int = 323930673

scala> y
res5: Int = 323930673

6
我认为您的解释可能暗示您无意这样做。尝试致电even2两次,一次致电一次,一次致电1一次 2。您将在每个电话中得到不同的答案。因此,虽然println在后续调用中未执行,但您从调用的不同不会得到相同的结果even2。至于为什么println不再次执行,则是另一个问题。
梅尔斯顿,2015年

1
这实际上非常有趣。就像在val即even2的情况下一样,val被评估为参数化值。所以用val可以对功能进行评估,评估其价值。println不是评估值的一部分。它是评估的一部分,但不是评估值。这里的技巧是,求值实际上是参数化值,取决于某些输入。聪明的东西
MaatDeamon 2015年

1
完全是@melston!这就是我的理解,那么为什么在输出更改时println不能再次执行?
2015年

1
@aur even2返回的实际上是一个函数(even2定义末尾的带括号的表达式)。实际上,每次调用该函数时,都会使用传递给even2的参数来调用该函数。
梅尔斯顿

5

看到这个:

  var x = 2 // using var as I need to change it to 3 later
  val sq = x*x // evaluates right now
  x = 3 // no effect! sq is already evaluated
  println(sq)

令人惊讶的是,它将打印4而不是9!val(偶数var)将立即求值并赋值。
现在将val更改为def.。它将打印9!Def是一个函数调用..它将在每次调用时求值。


1

val即“ sq”是由Scala定义固定的。它是在声明时进行评估的,以后不能更改。在其他示例中,even2也为val,但是它使用函数签名即“(Int => Boolean)”声明,因此它不是Int类型。它是一个函数,其值通过以下表达式设置

   {
         println("val");
         (x => x % 2 == 0)
   }

根据Scala val属性,您无法将其他函数分配给even2,与sq相同。

关于为什么调用eval2 val函数不能一次又一次地打印“ val”?

原始代码:

val even2: (Int => Boolean) = {
             println("val");
             (x => x % 2 == 0)
       }

我们知道,在Scala中,上述表达式的最后一条语句(在{..}内部)实际上返回到左侧。因此,最终将even2设置为“ x => x%2 == 0”函数,该函数与为even2 val类型声明的类型匹配,即(Int => Boolean),因此编译器很满意。现在,even2仅指向“((x => x%2 == 0)”函数(在println(“ val”)等之前没有其他任何语句。使用不同参数调用event2实际上会调用“(x => x%2 == 0)“代码,因为只有与event2一起保存的代码。

scala> even2(2)
res7: Boolean = true

scala> even2(3)
res8: Boolean = false

为了进一步说明这一点,下面是不同版本的代码。

scala> val even2: (Int => Boolean) = {
     |              println("val");
     |              (x => { 
     |               println("inside final fn")
     |               x % 2 == 0
     |             })
     |        }

会发生什么 ?在这里,当您调用even2()时,我们看到一次又一次打印“ inside final fn”。

scala> even2(3)
inside final fn
res9: Boolean = false

scala> even2(2)
inside final fn
res10: Boolean = true

scala> 

1

执行诸如这样的定义def x = e将不会对表达式e求值。每当调用x时,都会评估e。

另外,Scala提供了一个值定义val x = e,该定义的确会评估右侧,这是定义 评估的一部分。如果随后使用x,则立即将其替换为e的预先计算的值,这样就无需再次评估表达式。


0

同样,Val是按价值评估。这意味着在定义过程中将评估右侧表达式。Def是按名称评估的。使用前不会评估。


0

除了上述有用的答复外,我的发现是:

def test1: Int => Int = {
x => x
}
--test1: test1[] => Int => Int

def test2(): Int => Int = {
x => x+1
}
--test2: test2[]() => Int => Int

def test3(): Int = 4
--test3: test3[]() => Int

上面显示“ def”是一种方法(带有零个参数),在调用时返回另一个函数“ Int => Int”。

方法到函数的转换在这里有很好的解释:https : //tpolecat.github.io/2014/06/09/methods-functions.html


0

在REPL中,

scala> def even: Int => Boolean = { _% 2 == 0 }
even: Int => Boolean

scala> val even: Int => Boolean = { _% 2 == 0 }
even: Int => Boolean = $$Lambda$1157/1017502292@57a0aeb8

def平均call-by-name,按需评估

val平均值call-by-value,在初始化时求值


对于这么古老的问题,并且已经提交了如此多的答案,通常有助于解释您的答案与现有答案中提供的信息有何不同或有所补充。
jwvh
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.