【问题标题】:What is the difference between "def" and "val" to define a function定义函数的“def”和“val”有什么区别
【发布时间】:2013-09-24 02:24:14
【问题描述】:

有什么区别:

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 => Type' 是什么意思

标签: scala


【解决方案1】:

方法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 时评估函数不是很重要吗?
  • def 可用于定义方法,这是最快的选项。 @A.Karimi
  • 为了好玩:2.12,even eq even.
  • 有没有像c++这样的内联函数的概念?我来自 c++ 世界,请原谅我的无知。
  • @animageofmine Scala 编译器可以尝试内联方法。为此有@inline attribute。但它不能内联函数,因为函数调用是对函数对象的虚拟apply 方法的调用。在某些情况下,JVM 可能会去虚拟化和内联此类调用,但一般情况下不会。
【解决方案2】:

考虑一下:

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 方法的主体。但是对于even2,即val,该函数在声明时只初始化一次(因此它在第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 的情况下一样,即 even2,val 被评估为参数化值。所以是的,你用 val 评估函数,它的值。 println 不是评估值的一部分。它是评估的一部分,但不是评估值。这里的诀窍是评估值实际上是一个参数化值,它取决于一些输入。聪明的东西
  • @melston 完全正确!这就是我所理解的,那么为什么 println 在输出更改时不会再次执行?
  • @a.u.r even2 返回的实际上是一个函数(even2 定义末尾的括号表达式)。该函数实际上是在每次调用时使用传递给 even2 的参数调用的。
【解决方案3】:

看这个:

  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 是一个函数调用。每次调用它都会计算。

【讨论】:

    【解决方案4】:

    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> 
    

    【讨论】:

      【解决方案5】:

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

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

      【讨论】:

        【解决方案6】:

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

        【讨论】:

          【解决方案7】:

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

          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

          【讨论】:

            【解决方案8】:

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

            【讨论】:

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

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

            解释 val 函数:

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

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

            说明方法即def:

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

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

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

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2015-08-07
              • 2018-08-15
              • 2016-06-14
              • 2016-10-02
              • 2017-06-29
              • 1970-01-01
              • 2014-07-07
              相关资源
              最近更新 更多