В чем разница между =>, () => и Unit => - PullRequest
138 голосов
/ 28 декабря 2010

Я пытаюсь представить функцию, которая не принимает аргументов и не возвращает значения (я симулирую функцию 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 ]

221 голосов
/ 28 декабря 2010

Call-by-Name: => Тип

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

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

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 - указывать значение, которое ему не интересно, поэтому не имеет смысла получать это значение.

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

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-арности (но это не так).

36 голосов
/ 28 декабря 2010
case class Scheduled(time : Int, callback :  => Unit)

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

Когда вы переключаетесь на callback: () => Unit, теперь ваш случай просто принимает функциюпараметр вызова по имени.Очевидно, что функция может храниться в 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
1 голос
/ 14 сентября 2018

В вопросе вы хотите смоделировать функцию 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 не блокирует.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...