Scala Call By Name Confusion

前端 未结 3 1267
渐次进展
渐次进展 2021-01-05 16:48

I am working on some call by name examples using the REPL and running the same examples in Eclipse.

Here is what in Eclipse:
Scenario 1:

val fun         


        
相关标签:
3条回答
  • 2021-01-05 17:14

    There is no difference in the output. The difference is in what you want. From Eclipse you ran two things:

    val funct = {println("Calling funct")} // prints Calling funct here
    takesFunct(funct)
    
    def takesFunct(f: => Unit)
    {
       val b = f
    }
    

    and

    val funct = {println("Calling funct")} // prints Calling funct here
    takesFunct({println("Calling funct")}
    
    def takesFunct(f: => Unit)
    {
       val b = f                           // prints Calling funct here
    }
    

    On the REPL, the same thing happened according to your own logs:

    scala> def takesFunct(f: => Unit)
    {
      val b = f
    }
    takesFunct: (f: => Unit)Unit
    
    scala> val funct = {println("Calling funct")}
    Calling funct
    funct: Unit = ()
    
    scala> takesFunct(funct)
    // No Output
    

    Now, in the second scenario all you did was this:

    scala> takesFunct({println("Calling funct")}
    Calling funct
    

    Since you did not repeat the val funct assignment, which was still present in Eclipse's second scenario, it did not print a message.

    Note that

    val funct = {println("Calling funct")}
    

    is, as a practical matter, equivalent to

    println("Calling funct")
    val funct = ()
    
    0 讨论(0)
  • 2021-01-05 17:26

    Updated after @IttayD's answer:

    The scenario 1 on Eclipse is right, you'll see why below. Scenario 2 is clearly an Eclipse bug. ScalaIDE on Eclipse is known for its brokenness. I wouldn't trust it (or use it). Use Intellij IDEA's Scala plugin if you must.

    The answer to both of your questions is that, {} is a block that returns the return type of it's last statement. It's exactly the same as Scheme's (begin) or Common Lisp's (progn). When you have:

    scala> def takesFunct(f: => Unit)
    {
      val b = f
    }
    takesFunct: (f: => Unit)Unit
    
    scala> val funct = {println("Calling funct")}
    Calling funct
    funct: Unit = ()
    
    scala> takesFunct(funct)
    // No Output
    

    funct's RHS has already been eagerly evaluated and returned a value () of type Unit to funct. Applying an already computed value to a call-by-name function and using it in the body doesn't cause reevaluation because the value is already a leaf.

    Further Update:

    def takesFunct(f: => Unit)
    

    has essentially the same semantics as

    def takesFunct(f: () => Unit)
    

    which is known as streaming or delayed evaluation in certain circles. There is one major difference though, which lies in the way you invoke the supplied argument. In the latter case, in order to get back a value from f, you have to invoke it as such - i.e f(). In the former case, f is a lazy expression that evaluates to a value when it is first referenced as such, hence call-by-name. You can think of the syntax f: => Unit as a way to automatically wrap whatever expression you supply in a container {}. The contents of which is retrieved when used like so:

    scala> val a = { 1 } // 1 wrapped in {}, and retrieved when assigned to a
    a: Int = 1
    

    So what about this?

    scala> takesFunct({println("Calling funct")})
    Calling funct
    

    This is because now you are creating a block in-place that is bound to the function's parameter f, and it is only evaluated when you use it in val b = f. Let's do one more experiment:

    scala> takesFunct(println("Calling funct"))
    Calling funct
    

    How come you ask? Because println(...) was wrapped in a {} that is bound to f. Referencing f retrieves the value inside the container, which is the value of println(...), which is ():Unit. In the previous example, f was bound to { { println(...) } }, which is the same as { println(...) }, so you get the same result. In fact you can nest {} indefinitely and still get the same thing back. The only difference is, manually supplying {} lets you put multiple statements inside like so:

    scala> takesFunct({ println("hello"); println("world") })
    hello
    world
    

    Hope this helps.

    0 讨论(0)
  • 2021-01-05 17:30

    Scenario 1: As expected. The output is from the first line, not calling the method.

    Scenario 2: I don't have Eclipse, but doesn't sound right.

    REPL Scenario 1: you can't define a call by name value. what you did is assign the last value of the expression {println("Calling funct")} to funct. That last value is the result of println, which is Unit. So takesFunct receives a thunk that evaluates to just Unit, so nothing is printed.

    REPL Scenario 2: As expected

    0 讨论(0)
提交回复
热议问题