Сложный вывод типа Scala с лямбда-выражениями - PullRequest
4 голосов
/ 05 октября 2011

Я работаю над DSL для экспериментальной библиотеки, которую я создаю в Scala, и я столкнулся с некоторыми неприятными особенностями вывода типов в Scala, которые относятся к аргументам лямбда-выражений, которые, кажется, не рассматриваются вкнига Программирование в Scala .

В моей библиотеке есть черта Effect [-T], которая используется для представления временных модификаторов, которые можно применять к объекту типа TУ меня есть объект myEffects, у которого есть метод с именем + =, который принимает аргумент типа Effect [PlayerCharacter].Наконец, у меня есть универсальный метод, когда [T], который используется для создания условных эффектов, принимая условное выражение и другой эффект в качестве аргумента.Сигнатура выглядит следующим образом:

def when[T](condition : T => Boolean) (effect : Effect[T]) : Effect[T]

Когда я вызываю метод «когда» с вышеуказанной сигнатурой, передавая его результат методу + =, он не может вывести тип аргумента лямбда-выраженияВыражение.

myEffects += when(_.hpLow()) (applyModifierEffect) //<-- Compiler error

Если я объединю аргументы «когда» в один список параметров, Scala может точно определить тип лямбда-выражения.

def when[T](condition : T => Boolean, effect : Effect[T]) : Effect[T]

/* Snip */

myEffects += when(_.hpLow(), applyModifierEffect) //This works fine!

Этотакже работает, если я полностью удаляю второй параметр.

def when[T](condition : T => Boolean) : Effect[T]

/* Snip */

myEffects += when(_.hpLow()) //This works too!

Однако по эстетическим соображениям я действительно хочу, чтобы аргументы передавались методу «когда» в виде отдельных списков параметров.

Мое понимание из раздела 16.10 Программирование в Scala заключается в том, что компилятор сначала проверяет, известен ли тип метода, и если да, то использует его, чтобы вывести ожидаемый тип своих аргументов.В этом случае самым внешним вызовом метода является + =, который принимает аргумент типа Effect [PlayerCharacter].Поскольку тип возвращаемого значения, когда [T] равен Effect [T], а метод, которому передается результат, ожидает аргумент типа Effect [PlayerCharacter], он может заключить, что T - это PlayerCharacter, и, следовательно, тип лямбдавыражение, переданное в качестве первого аргумента «когда», является PlayerCharacter => Boolean.Похоже, это работает, когда аргументы предоставляются в одном списке параметров, так почему же разбивание аргументов на два списка параметров разбивает его?

Ответы [ 2 ]

2 голосов
/ 05 октября 2011

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

Вывод типа работает слева направо от одного списка параметров (не параметра) к следующему.Типичным примером является метод foldLeft в коллекциях (скажем, Seq [A])

def foldLeft[B] (z: B)(op: (B, A) => B): B

Тип z будет делать B известным, поэтому op может быть записан без указания B (ни A, который известен с самого начала,введите параметр этого).Если подпрограмма записана как

def foldLeft[B](z: B, op: (B,A) => B): B

или как

def foldLeft[B](op: (B,A) => B)(z: B): B

, она не будет работать, и нужно будет убедиться, что тип операции свободен, или передать BОбъяснение при вызове foldLeft.

В вашем случае, я думаю, что наиболее приятным для чтения эквивалентом было бы сделать when методом Effect (или сделать его похожим на метод с неявным преобразованием) тогда вы бы написали

Effects += applyModifierEffect when (_.hpLow())

. Как вы упомянули, что эффект контравариантен, when подпись недопустима для метода Effect (из-за T => Boolean функция контравариантна в своем первомвведите параметр, и условие появится как параметр, поэтому в контравариантном положении два контраварианта составляют ковариант), но это все равно можно сделать с неявным

object Effect {
  implicit def withWhen[T](e: Effect[T]) 
    = new {def when(condition: T => Boolean) = ...}
}
2 голосов
/ 05 октября 2011

Я относительно новичок в Scala, и у меня нет подробных технических знаний о том, как работает вывод типов.Так что лучше всего взять это с крошкой соли.

Я думаю, что разница в том, что у компилятора возникают проблемы с доказательством, что два T одинаковы в condition : T => Boolean и effect : Effect[T] вверсия со списком двух параметров.

Я полагаю, когда у вас есть несколько списков параметров (поскольку Scala рассматривает это как определение метода, который возвращает функцию, которая использует следующий список параметров), компилятор обрабатывает списки параметров один ввремя, а не все вместе, как в одной версии списка параметров.

Так что в этом случае:

def when[T](condition : T => Boolean, effect : Effect[T]) : Effect[T]

/* Snip */

myEffects += when(_.hpLow(), applyModifierEffect) //This works fine!

Тип applyModifierEffect и требуемый тип параметра myEffects += могутпомогите ограничить тип параметра _.hpLow();все T должны быть PlayerCharacter.Но в следующем:

myEffects += when(_.hpLow()) (applyModifierEffect)

Компилятор должен независимо определить тип when(_.hpLow()), чтобы он мог проверить, действительно ли он применим к applyModifierEffect.И сам по себе _.hpLow() не предоставляет достаточной информации для компилятора, чтобы сделать вывод, что это when[PlayerCharacter](_.hpLow()), поэтому он не знает, что возвращаемый тип является функцией типа Effect[PlayerCharacter] => Effect[PlayerCharacter], поэтому он не делаетЗнайте, что это правильно, чтобы применить эту функцию в этом контексте.Я думаю, что вывод типа просто не соединяет точки и выясняет, что существует только один тип, который позволяет избежать ошибки типа.

А что касается другого случая, который работает:

def when[T](condition : T => Boolean) : Effect[T]

/* Snip */

myEffects += when(_.hpLow()) //This works too!

Здесь тип возврата when и его тип параметра связаны более напрямую, без прохождения через тип параметра и тип возврата дополнительной функции, созданной при помощи каррирования.Поскольку для myEffects += требуется Effect[PlayerCharacter], T должно быть PlayerCharacter, что имеет метод hpLow, и компилятор готов.

Надеюсь, кто-то более знающий может исправить меня в деталяхили укажите, если я вообще лаю не на том дереве!

...