В чем разница между =>, ()=> и Unit=>

Я пытаюсь представить функцию, которая не принимает аргументов и не возвращает значения (я имитирую функцию setTimeout в JavaScript, если вы должны знать.)

case class Scheduled(time : Int, callback :  => Unit)

не компилируется, говоря: "параметры val не могут быть названы по имени"

case class Scheduled(time : Int, callback :  () => Unit)  

компилируется, но должен вызываться странно, а не

Scheduled(40, { println("x") } )

Я должен сделать это

Scheduled(40, { () => println("x") } )      

Что также работает

class Scheduled(time : Int, callback :  Unit => Unit)

но вызывается еще менее разумным способом

 Scheduled(40, { x : Unit => println("x") } )

(Какой будет переменная типа Unit?) Конечно, мне нужен конструктор, который можно вызывать так, как я бы его вызывал, если бы это была обычная функция:

 Scheduled(40, println("x") )

Дай ребенку свою бутылочку!

3 ответа

Решение

Call-by-Name: => Тип

=> Type нотация обозначает вызов по имени, что является одним из многих способов передачи параметров. Если вы не знакомы с ними, я рекомендую потратить некоторое время на чтение этой статьи в Википедии, хотя в настоящее время это в основном вызов по значению и вызов по ссылке.

Это означает, что то, что передается, заменяется именем значения внутри функции. Например, возьмите эту функцию:

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

Если я назову это так

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

Тогда код будет выполняться так

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

Хотя это поднимает вопрос о том, что происходит, если есть столкновение имени идентификатора. В традиционном вызове по имени используется механизм, называемый заменой, избегающей перехвата, чтобы избежать конфликта имен. В Scala, однако, это реализовано другим способом с тем же результатом - имена идентификаторов внутри параметра не могут ссылаться или идентификаторы тени в вызываемой функции.

Есть несколько других моментов, связанных с именами, о которых я расскажу после объяснения двух других.

Функции 0-арности: () => Тип

Синтаксис () => Type обозначает тип Function0, То есть функция, которая не принимает параметров и что-то возвращает. Это эквивалентно, скажем, вызову метода size() - не принимает параметров и возвращает число.

Интересно, однако, что этот синтаксис очень похож на синтаксис литерала анонимной функции, что является причиной некоторой путаницы. Например,

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

является литералом анонимной функции арности 0, чей тип

() => Unit

Таким образом, мы могли бы написать:

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

Однако важно не путать тип со значением.

Unit => Type

Это на самом деле просто Function1, чей первый параметр имеет тип Unit, Другие способы написать это было бы (Unit) => Type или же Function1[Unit, Type], Дело в том... что это вряд ли когда-нибудь будет тем, что нужно. Unit Основная цель type - указывать значение, которое не интересно, поэтому не имеет смысла получать это значение.

Рассмотрим, например,

def f(x: Unit) = ...

Что можно сделать с x? Он может иметь только одно значение, поэтому его не нужно получать. Одним из возможных применений было бы возвращение функций цепочки Unit:

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

Так как andThen определяется только на Function1и возвращаемые нами функции возвращаются Unitмы должны были определить их как имеющие тип Function1[Unit, Unit] чтобы иметь возможность связать их.

Источники путаницы

Первым источником путаницы является то, что сходство между типом и литералом, которое существует для функций 0-арности, также существует для вызова по имени. Другими словами, думая, что, потому что

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

это буквальный () => Unit, затем

{ println("Hi!") }

будет буквальным для => Unit, Это не. Это блок кода, а не литерал.

Еще одним источником путаницы является то, что Unit значение типа записывается (), который выглядит как список параметров 0-арности (но это не так).

case class Scheduled(time : Int, callback :  => Unit)

case модификатор делает неявным val из каждого аргумента в конструктор. Следовательно (как кто-то заметил), если вы удалите case Вы можете использовать параметр call-by-name. Компилятор, возможно, мог бы разрешить это в любом случае, но он мог бы удивить людей, если бы он создал val callback вместо того, чтобы превратиться в lazy val callback,

Когда вы меняете на callback: () => Unit теперь ваш случай просто принимает функцию, а не параметр call-by-name. Очевидно, что функция может быть сохранена в val callback так что нет проблем.

Самый простой способ получить то, что вы хотите (Scheduled(40, println("x") ) где параметр call-by-name используется для передачи лямбды), вероятно, пропускает case и явно создать apply что вы не могли получить в первую очередь:

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

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

В использовании:

scala> Scheduled(1234, println("x"))
res0: Scheduled = Scheduled@5eb10190

scala> Scheduled(1234, println("x")).doit
x

В вопросе вы хотите смоделировать функцию SetTimeOut в JavaScript. Основываясь на предыдущих ответах, я пишу следующий код:

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
  }
}

В REPL мы можем получить что-то вроде этого:

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

Наше моделирование не ведет себя точно так же, как SetTimeOut, потому что наше моделирование является блокирующей функцией, а SetTimeOut не является блокирующим.

Я делаю это так (просто не хочу прерывать применение):

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

и назови это

Thing.of(..., your_value)
Другие вопросы по тегам