ChatGPT解决这个技术问题 Extra ChatGPT

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

有什么区别:

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

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

两者都可以像 even(10) 一样调用。

您好,Int => Boolean 是什么意思?我认为定义语法是 def foo(bar: Baz): Bin = expr
@Ziu 这意味着函数 'even' 接收一个 Int 作为参数并返回一个 Boolean 作为值类型。所以你可以调用'even(3)',它的计算结果是布尔值'false'
@DenysLobur 感谢您的回复!有关此语法的任何参考?
@Ziu 我基本上是从 Odersky 的 Coursera 课程中发现的 - coursera.org/learn/progfun1。当你完成它时,你会明白什么是'Type =>类型'的意思

I
Ismail Marmoush

方法 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)),那么每次您将其用作函数时,编译器都会从您的方法中生成一个函数,因此性能可能会比使用 { 2}。


请您比较一下它们的性能吗?每次调用 even 时评估函数不是很重要吗?
def 可用于定义方法,这是最快的选项。 @A.卡里米
为了好玩:在 2.12,even eq even
是否有像 c++ 中的内联函数的概念?我来自 C++ 世界,所以请原谅我的无知。
@animageofmine Scala 编译器可以尝试内联方法。为此有 @inline attribute。但它不能内联函数,因为函数调用是对函数对象的虚拟 apply 方法的调用。在某些情况下,JVM 可能会去虚拟化和内联此类调用,但一般情况下不会。
J
Jatin

考虑一下:

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 方法的主体。但是使用 even2val,函数在声明时只初始化一次(因此它在第 4 行打印 val 并且不再打印)并且每次访问时都使用相同的输出。例如尝试这样做:

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

我认为您的解释可能暗示您不打算这样做。尝试调用 even2 两次,一次使用 1,一次使用 2。每次通话都会得到不同的答案。因此,虽然 println 不会在后续调用中执行,但您不会从对 even2 的不同调用中获得相同的 result。至于为什么没有再次执行println,这是一个不同的问题。
这实际上很有趣。就像在 val ie even2 的情况下,val 被评估为参数化值。所以是的,你用 val 评估函数,它的值。 println 不是评估值的一部分。它是评估的一部分,但不是评估值。这里的技巧是评估值实际上是一个参数化值,它取决于一些输入。聪明的东西
@melston 完全正确!这就是我所理解的,那么为什么 println 在输出改变时不会再次执行?
@aur even2 返回的实际上是一个函数(even2 定义末尾的括号表达式)。该函数实际上是在每次调用它时使用传递给 even2 的参数调用的。
A
Apurva Singh

看到这个:

  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 (even var) 被立即评估并赋值。现在将 val 更改为 def.. 它将打印 9! Def 是一个函数调用.. 每次调用它都会评估。


S
Sandi

val ie "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> 

G
Gaurav Khare

执行 def x = e 之类的定义不会计算表达式 e。相反,每当调用 x 时都会评估 e。

或者,Scala 提供了一个值定义 val x = e,它确实评估右侧作为定义评估的一部分。如果随后使用 x,它会立即被预先计算的 e 值替换,因此不需要再次计算表达式。


S
Sandipan Ghosh

此外,Val 是按值评估。这意味着在定义期间评估右侧表达式。其中 Def 是按名称评估。在使用之前不会进行评估。


j
jkdev

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

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


G
GraceMeng

在 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,在初始化时评估


对于这么老的问题,并且已经提交了这么多答案,解释您的答案与现有答案中提供的信息有何不同或增加了这些信息通常会很有帮助。
i
initvik

注意:Scala 中有不同类型的函数:抽象的、具体的、匿名的、高阶的、纯的、不纯的等...

解释 val 函数:

Scala 中的 val 函数是一个完整的对象。 Scala 中有一些特征来表示具有不同数量参数的函数:Function0、Function1、Function2 等。作为实现这些特征之一的类的实例,函数对象具有方法。其中一种方法是 apply 方法,它包含实现函数主体的代码。

当我们创建一个其值为函数对象的变量,然后我们引用该变量后跟括号时,这将转换为对函数对象的 apply 方法的调用。

解释方法即定义:

Scala 中的方法不是值,而是函数。

Scala 方法,就像在 Java 中一样,是类的一部分。它有一个名称、一个签名、可选的一些注释和一些字节码。

方法的实现是一个有序的语句序列,它产生一个必须与其返回类型兼容的值。