Я пытаюсь представить функцию, которая не принимает никаких аргументов и не возвращает значения (я имитирую функцию 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") )
Дайте ребенку свою бутылку!
Обозначение => Type
означает вызов по имени, который является одним из параметров many ways. Если вы не знакомы с ними, я рекомендую потратить некоторое время на то, чтобы прочитать эту статью в Википедии, хотя в настоящее время это, главным образом, призыв к значению и вызов по ссылке.
Что это значит, то, что передано, заменяется именем значения внутри функции. Например, возьмите эту функцию:
def f(x: => Int) = x * x
Если я назову это так
var y = 0
f { y += 1; y }
Затем код будет выполняться следующим образом
{ y += 1; y } * { y += 1; y }
Хотя это поднимает вопрос о том, что происходит, если существует столкновение имени идентификатора. В традиционном вызове по имени используется механизм, называемый замещением захвата, чтобы избежать столкновений имен. Однако в Scala это реализовано по-другому с тем же результатом - имена идентификаторов внутри параметра не могут ссылаться или теневые идентификаторы в вызываемой функции.
Есть несколько других вопросов, связанных с вызовом по имени, о котором я буду говорить после объяснения двух других.
Синтаксис () => Type
обозначает тип a Function0
. То есть, функция, которая не принимает никаких параметров и что-то возвращает. Это эквивалентно, например, вызову метода size()
- он не принимает никаких параметров и возвращает число.
Интересно, однако, что этот синтаксис очень похож на синтаксис для анонимного литерала функции, что является причиной некоторой путаницы. Например,
() => println("I'm an anonymous function")
является анонимным литералом функции arity 0, тип которого
() => Unit
Итак, мы могли бы написать:
val f: () => Unit = () => println("I'm an anonymous function")
Однако важно не путать тип со значением.
На самом деле это просто 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-arity, также существует для вызова по имени. Другими словами, думая, что, поскольку
() => { println("Hi!") }
является литералом для () => Unit
, тогда
{ println("Hi!") }
будет литералом для => Unit
. Это не. Это блок кода, а не буквальный.
Другим источником путаницы является то, что значение типа Unit
записывается ()
, которое выглядит как список параметров 0-arity (но это не так).
case class Scheduled(time : Int, callback : => Unit)
Модификатор case
делает неявный val
из каждого аргумента конструктору. Следовательно (если кто-то заметил), если вы удалите case
, вы можете использовать параметр по имени. Возможно, компилятор может это разрешить, но может удивить людей, если он создал val callback
вместо того, чтобы преобразовать в lazy val callback
.
При переходе на callback: () => Unit
теперь ваш случай просто выполняет функцию, а не параметр по имени. Очевидно, что функция может быть сохранена в val callback
, поэтому проблем нет.
Самый простой способ получить то, что вы хотите (Scheduled(40, println("x") )
, где параметр "по имени" используется для передачи лямбда), вероятно, должен пропустить 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
case class Scheduled(time: Int)(callback: => Unit)
. Это работает, потому что список вторичных параметров не предоставляется публично и не включается в сгенерированные методыequals
/hashCode
.