What's the difference between => , ()=>, and Unit=>

后端 未结 4 1749
深忆病人
深忆病人 2020-11-22 16:40

I\'m trying to represent a function that takes no arguments and returns no value (I\'m simulating the setTimeout function in JavaScript, if you must know.)

c         


        
相关标签:
4条回答
  • 2020-11-22 17:20
    case class Scheduled(time : Int, callback :  => Unit)
    

    The case modifier makes implicit val out of each argument to the constructor. Hence (as someone noted) if you remove case you can use a call-by-name parameter. The compiler could probably allow it anyway, but it might surprise people if it created val callback instead of morphing into lazy val callback.

    When you change to callback: () => Unit now your case just takes a function rather than a call-by-name parameter. Obviously the function can be stored in val callback so there's no problem.

    The easiest way to get what you want (Scheduled(40, println("x") ) where a call-by-name parameter is used to pass a lambda) is probably to skip the case and explicitly create the apply that you couldn't get in the first place:

    class Scheduled(val time: Int, val callback: () => Unit) {
        def doit = callback()
    }
    
    object Scheduled {
        def apply(time: Int, callback: => Unit) =
            new Scheduled(time, { () => callback })
    }
    

    In use:

    scala> Scheduled(1234, println("x"))
    res0: Scheduled = Scheduled@5eb10190
    
    scala> Scheduled(1234, println("x")).doit
    x
    
    0 讨论(0)
  • 2020-11-22 17:29

    In the question, you want to simulate SetTimeOut function in JavaScript. Based on previous answers, I write following code:

    class Scheduled(time: Int, cb: => Unit) {
      private def runCb = cb
    }
    
    object Scheduled {
      def apply(time: Int, cb: => Unit) = {
        val instance = new Scheduled(time, cb)
        Thread.sleep(time*1000)
        instance.runCb
      }
    }
    

    In REPL, we can get something like this:

    scala> Scheduled(10, println("a")); Scheduled(1, println("b"))
    a
    b
    

    Our simulation doesn't behave exactly the same as SetTimeOut, because our simulation is blocking function, but SetTimeOut is non-blocking.

    0 讨论(0)
  • 2020-11-22 17:38

    Call-by-Name: => Type

    The => Type notation stands for call-by-name, which is one of the many ways parameters can be passed. If you aren't familiar with them, I recommend taking some time to read that wikipedia article, even though nowadays it is mostly call-by-value and call-by-reference.

    What it means is that what is passed is substituted for the value name inside the function. For example, take this function:

    def f(x: => Int) = x * x
    

    If I call it like this

    var y = 0
    f { y += 1; y }
    

    Then the code will execute like this

    { y += 1; y } * { y += 1; y }
    

    Though that raises the point of what happens if there's a identifier name clash. In traditional call-by-name, a mechanism called capture-avoiding substitution takes place to avoid name clashes. In Scala, however, this is implemented in another way with the same result -- identifier names inside the parameter can't refer to or shadow identifiers in the called function.

    There are some other points related to call-by-name that I'll speak of after explaining the other two.

    0-arity Functions: () => Type

    The syntax () => Type stands for the type of a Function0. That is, a function which takes no parameters and returns something. This is equivalent to, say, calling the method size() -- it takes no parameters and returns a number.

    It is interesting, however, that this syntax is very similar to the syntax for a anonymous function literal, which is the cause for some confusion. For example,

    () => println("I'm an anonymous function")
    

    is an anonymous function literal of arity 0, whose type is

    () => Unit
    

    So we could write:

    val f: () => Unit = () => println("I'm an anonymous function")
    

    It is important not to confuse the type with the value, however.

    Unit => Type

    This is actually just a Function1, whose first parameter is of type Unit. Other ways to write it would be (Unit) => Type or Function1[Unit, Type]. The thing is... this is unlikely to ever be what one wants. The Unit type's main purpose is indicating a value one is not interested in, so doesn't make sense to receive that value.

    Consider, for instance,

    def f(x: Unit) = ...
    

    What could one possibly do with x? It can only have a single value, so one need not receive it. One possible use would be chaining functions returning Unit:

    val f = (x: Unit) => println("I'm f")
    val g = (x: Unit) => println("I'm g")
    val h = f andThen g
    

    Because andThen is only defined on Function1, and the functions we are chaining are returning Unit, we had to define them as being of type Function1[Unit, Unit] to be able to chain them.

    Sources of Confusion

    The first source of confusion is thinking the similarity between type and literal that exists for 0-arity functions also exists for call-by-name. In other words, thinking that, because

    () => { println("Hi!") }
    

    is a literal for () => Unit, then

    { println("Hi!") }
    

    would be a literal for => Unit. It is not. That is a block of code, not a literal.

    Another source of confusion is that Unit type's value is written (), which looks like a 0-arity parameter list (but it is not).

    0 讨论(0)
  • 2020-11-22 17:46

    I do it this way (just don't want to break apply):

    case class Thing[A](..., lazy: () => A) {}
    object Thing {
      def of[A](..., a: => A): Thing[A] = Thing(..., () => a)
    }
    

    and call it

    Thing.of(..., your_value)
    
    0 讨论(0)
提交回复
热议问题