Яка різниця між =>, () => та Unit =>


153

Я намагаюся представляти функцію, яка не бере аргументів і не повертає значення (я моделюю функцію 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методи.
nilskp

Ще кілька цікавих аспектів щодо відмінностей між параметрами назви та функціями 0-arity ви знайдете у цьому запитанні та відповіді. Це насправді те, що я шукав, коли знайшов це питання.
lex82

Відповіді:


234

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-arity: () => Тип

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

Однак цікаво, що цей синтаксис дуже схожий на синтаксис анонімної функції літерал , що є причиною певної плутанини. Наприклад,

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

- це анонімна функція, буквальна ариті 0, тип якої

() => Unit

Тож ми могли написати:

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

Важливо не плутати тип зі значенням.

Одиниця => Тип

Це насправді просто а 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-arity, існує також для виклику по імені. Іншими словами, думаючи, що, тому що

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

то є буквальним для () => Unit, то

{ println("Hi!") }

було б буквальним для => Unit. Це не. Це блок коду , а не буквальний.

Іншим джерелом плутанини є те, що записано значенняUnit типу , яке виглядає як список параметрів 0-arity (але це не так).()


Можливо, мені доведеться стати першим, хто проголосував через два роки. Хтось цікавиться синтаксисом case => на Різдво, і я не можу рекомендувати цю відповідь як канонічну та повну! До чого приходить світ? Можливо, майя були тільки щотижнями. Чи правильно вони розраховували у високосні роки? Літній час?
сом-снітт

@ som-snytt Ну, запитання про це не ставили case ... =>, тому я його не згадував. Сумно але правда. :-)
Даніель К. Собрал

1
@Daniel C. Sobral чи не могли б ви пояснити "Це блок коду, а не буквальний". частина. Отже, чим саме відрізняється двоє?
nish1013

2
@ nish1013 "Літерал" - це значення (ціле число 1, символ 'a', рядок "abc"або функція () => println("here")для деяких прикладів). Його можна передавати як аргумент, зберігати у змінних тощо. "Блок коду" - це синтаксичне розмежування висловлювань - це не значення, його не можна передавати навколо чи щось подібне.
Даніель К. Собрал

1
@Alex Це така ж різниця, що і (Unit) => Typeпроти () => Type- перший є a Function1[Unit, Type], а другий - a Function0[Type].
Даніель К. Собрал

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
Чому б не зберегти його в регістрі і просто замінити застосований за замовчуванням? Крім того, компілятор не може перевести прізвище на ледачий val, оскільки вони мають суттєво різну семантику, ледачий - якнайбільше одного разу, а ім'я - при кожному посиланні
Віктор Кланг

@ViktorKlang Як ​​ви можете змінити метод застосування класу випадку за замовчуванням класу? stackoverflow.com/questions/2660975/…
Сойєр

об’єкт ClassName {def apply (…):… =…}
Віктор Кланг

Через чотири роки, і я розумію, що відповідь, яку я вибрав, відповіла лише на запитання в заголовку, а не на те, що я насправді мав (на що цей відповідає).
Мальволіо

1

У питанні ви хочете імітувати функцію 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 не блокує.


0

Я роблю це так (просто не хочу порушувати застосувати):

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

і зателефонуйте

Thing.of(..., your_value)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.