Почему параметр call-by-name ожидает параметр типа Int вместо () => Int

Я немного запутался в использовании параметров вызова по имени в Scala. Пожалуйста, помогите мне понять, что здесь происходит. Рассмотрим следующий пример использования параметра вызова по имени:

  def param = {println("Param evaluates"); 40}
  def lazyEval(x: => Int) = {
    println("lazy"); 
    x 
  }
  val s = lazyEval(param + 2)
  // s = 42

У меня есть несколько вопросов, связанных друг с другом по этому поводу:

  1. Метод lazyEval ожидает параметр типа => Int, так почему же операция param + 2 допустима? Почему мы можем добавить целое число 2 к объекту с типом => Int (в моем понимании под капотом это <function0>) при вызове метода lazyEval? Как мне подсказывает IDE, функция lazyEval ожидает параметр типа Int вместо => Int (какого черта?).

  2. Почему после изменения типа обратного вызова с => Int на () => Intкод не компилируется? Это 2 типа разные? Хотя короткая версия ('=> Int') - это просто синтаксический сахар.

  3. Немного поигравшись с кодом, я, наконец, смог изменить код, чтобы он компилировался с () => Int. Этот способ для меня более интуитивен.

.

  def param = {println("Param evaluates"); 40}
  def lazyEval(x: () => Int) = { // changed type to '() => Int'
    println("lazy"); 
    x() // explicitly calling function using parentheses '()'
  }    
  val s = lazyEval(param _) // adding underscore after method name and removing `+2`

Чем эта версия отличается от первой (с типом обратного вызова => Int)? Почему в этой версии нельзя сделать сложение целого числа со значением 2 и функцию param (имею в виду thislazyEval(param _ + 2))? А что означает подчеркивание после имени метода? (Думаю, раньше он передавал сам метод, а не возвращаемое значение)

Спасибо в помощи


person MyTitle    schedule 22.03.2015    source источник


Ответы (3)


так почему операция param + 2 является законной? Почему мы можем добавить целое число 2 к объекту с типом => Int

Мы можем добавить 2 к param, потому что это оценивается как Int, вы добавляете Int с Int. param — это не функция => Int, это метод, поэтому param + 2 является => Int. т.е. выражение, которое оценивается как Int.

Почему после изменения типа обратного вызова с => Int на () => Intcode не компилируется? Это 2 типа разные? Хотя короткая версия ('=> Int') - это просто синтаксический сахар.

=> Int и () => Int не означают одно и то же. Одна — это все, что оценивается как Int, а другая — как функция от Unit до Int. 2 не () => Int, а () => 2 есть. Или вы могли бы также сказать, что 2 равно => Int.

Почему в этой версии нельзя сделать сложение целого числа со значением 2 и функцию param (имею в виду thislazyEval(param _ + 2))? А что означает подчеркивание после имени метода? (Думаю, раньше он передавал сам метод, а не возвращаемое значение)

param — это метод, а не функция. Подчеркивание в этом случае превращает param в функцию. Итак, param _ — это функция () => Int. И именно поэтому мы не можем добавить к нему 2, потому что вы не можете добавить 2 к функции. В основном точная причина, по которой вы думаете, что (1) не должно работать.


В итоге:

def lazyEval(x: => Int) – это метод с параметром x, которым может быть любой результат, равный Int. Это может быть любой метод, который возвращает Int, конкретное значение Int или некоторый блок кода, который разрешается в Int и т. д.

lazyEval(x: () => Int) — это метод с параметром x, который может только быть функцией без параметров, возвращающей Int. Это может означать, что метод param поднят до функции, или что-то странное, например () => 2. Но это должно быть функцией. Так что просто передать значение типа 2 не получится.

person Michael Zajac    schedule 22.03.2015
comment
Я не знаю, почему вы сказали, что => Int не является типом или что это просто синтаксис. - person som-snytt; 22.03.2015
comment
@som-snytt Я проснулся в 4:30 утра после того, как мечтал написать этот ответ, и лихорадочно проверил его на своем телефоне, чтобы найти ваш только что сделанный комментарий. Естественно, я подключился к своему рабочему компьютеру со своего телефона, чтобы использовать REPL и проверить его, поскольку я не мог снова заснуть, не зная. Теперь, когда я в более ясном состоянии, я исправил свой ответ, чтобы не говорить таких неправильных вещей о => Int. - person Michael Zajac; 22.03.2015
comment
Ха. Похоже, ТАК работает так, как должно. - person som-snytt; 22.03.2015

Как указал @m-z, value: => T можно рассматривать как синтаксис для создания метода, обертывающего заданное выражение:

object Magician {
  def magic(param: => Int) = param
}

object App {
  val result: Int = Magician.magic(3 + 3)
}

переводится (примерно):

object App {
    private[this] def magic$param$1: Int = 3 + 3
    val result: Int = Magician.magic(magic$param$1 _)
}

Поведение параметра, вызываемого по имени, похоже на определение метода без параметров — обращение к любому из них приводит к вызываемому методу:

def paramlessMethod = 3 + 3
def callByName(param: => Int) = param + paramlessMethod
def test() = callByName(5 + 5) //  16, always

В обоих случаях вы можете «поднять» метод до Function0 (или «отложить оценку метода», если вы предпочитаете так думать), используя магию _:

def paramlessMethod = 3 + 3
val functionWrapper: Function0[Int] = paramlessMethod _
functionWrapper() // 6

def callByName(param: => Int) = param _
val functionFromParam: Function0[Int] = callByName(3 + 3)
functionFromParam() // 6
person Sean Vieira    schedule 22.03.2015
comment
Я не знаю, почему вы сказали, что => Int не является типом или что это просто синтаксис. - person som-snytt; 22.03.2015
comment
@som-snytt - я бы не сказал, что у него нет типа, но этот тип нельзя создать каким-либо другим способом (это внутренний тип компилятора). Поэтому я решил вместо того, чтобы сосредотачиваться на типизации => T, попробовать представить его как синтаксическое преобразование, чтобы посмотреть, поможет ли это пониманию. - person Sean Vieira; 22.03.2015
comment
Я понимаю, что вы имеете в виду, но вы можете перегрузить f(i: Int) и f(i: => Int), так что это не теоретический или артефакт реализации. Это настоящий тип. Тот факт, что вы не можете использовать его в описании типа, таком как f(42: => Int), этого не меняет. - person som-snytt; 23.03.2015

Тип def i: Int объясняется здесь:

http://www.scala-lang.org/files/archive/spec/2.11/03-types.html#method-types

Тип параметра по имени i: => Int объясняется здесь:

http://www.scala-lang.org/files/archive/spec/2.11/04-basic-declarations-and-definitions.html#by-name-parameters

Тогда тип такого параметра — это тип метода без параметров => T.

Другими словами, это то же самое, что и тип метода def i: Int.

person som-snytt    schedule 22.03.2015