В чем разница между =>, ()=> и 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)