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

139

Я пытаюсь представить функцию, которая не принимает никаких аргументов и не возвращает значения (я имитирую функцию 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
    Другой способ использовать классы дел с паролями по имени - поместить их в список вторичных параметров, например, case class Scheduled(time: Int)(callback: => Unit) . Это работает, потому что список вторичных параметров не предоставляется публично и не включается в сгенерированные методы equals / hashCode .
  • 0
    В этом вопросе и ответе есть еще несколько интересных аспектов, касающихся различий между параметрами по именам и функциями 0-арности. Это на самом деле то, что я искал, когда я нашел этот вопрос.
Теги:

2 ответа

206
Лучший ответ

Call-by-Name: = > Тип

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

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

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

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

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

Затем код будет выполняться следующим образом

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

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

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

0-arity Функции:() = > Тип

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

  • 8
    +1, я все еще ищу ответ от Даниэля, который не заслуживает голосования.
  • 0
    Возможно, мне придется быть первым, кто проголосует против после двух лет. Кто-то интересуется синтаксисом case => на Рождество, и я не могу рекомендовать этот ответ как канонический и полный! К чему мир приближается? Может быть, майя были только на неделю. Правильно ли они фигурируют в високосные годы? Дневного сбережения?
Показать ещё 7 комментариев
36
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
  • 3
    Почему бы не сохранить это case-class и просто переопределить применение по умолчанию? Кроме того, компилятор не может преобразовать имя по имени в ленивый val, поскольку они имеют по своей сути различную семантику, ленивый - самое большее один раз, а по имени - каждая ссылка
  • 0
    @ViktorKlang Как вы можете переопределить метод применения класса case по умолчанию? stackoverflow.com/questions/2660975/...
Показать ещё 2 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню