#scala #type-inference #lambda
#scala #вывод типа #лямбда — выражение #лямбда
Вопрос:
Я работаю над DSL для экспериментальной библиотеки, которую я создаю в Scala, и я столкнулся с некоторыми неприятными особенностями вывода типов Scala, поскольку это относится к аргументам лямбда-выражения, которые, похоже, не описаны в книге «Программирование в Scala«.
В моей библиотеке у меня есть признак Effect[-T], который используется для представления временных модификаторов, которые могут быть применены к объекту типа T. У меня есть объект myEffects, у которого есть метод с именем =, который принимает аргумент типа Effect[PlayerCharacter] . Наконец, у меня есть общий метод when[T], который используется для построения условных эффектов путем принятия условного выражения и другого эффекта в качестве аргумента. Подпись выглядит следующим образом:
def when[T](condition : T => Boolean) (effect : Effect[T]) : Effect[T]
Когда я вызываю метод «when» с вышеуказанной сигнатурой, передавая его результат методу =, он не может определить тип аргумента для лямбда-выражения.
myEffects = when(_.hpLow()) (applyModifierEffect) //<-- Compiler error
Если я объединю аргументы «when» в один список параметров, 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!
Однако по эстетическим соображениям я действительно хочу, чтобы аргументы передавались методу «when» в виде отдельных списков параметров.
Насколько я понимаю из раздела 16.10 «Программирование на Scala«, компилятор сначала проверяет, известен ли тип метода, и если да, то он использует это для вывода ожидаемого типа его аргументов. В этом случае самым внешним вызовом метода является =, который принимает аргумент типа Effect[PlayerCharacter]. Поскольку возвращаемый тип when[T] является Effect[T] , а метод, которому передается результат, ожидает аргумент типа Effect[PlayerCharacter] , он может сделать вывод, что T является PlayerCharacter , и, следовательно, тип лямбда-выражения передается в качестве первого аргумента «when»является ли PlayerCharacter => логическим. Похоже, именно так это работает, когда аргументы предоставляются в одном списке параметров, так почему же разбиение аргументов на два списка параметров нарушает его?
Комментарии:
Ответ №1:
Я сам относительно новичок в Scala, и у меня нет подробных технических знаний о том, как работает вывод типов. Так что лучше отнеситесь к этому с недоверием.
Я думаю, разница в том, что компилятору трудно доказать самому себе, что два T
s одинаковы в 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
s должны быть 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
и его тип параметра связаны более напрямую, без прохождения через тип параметра и возвращаемый тип дополнительной функции, созданной с помощью currying . Поскольку myEffects =
требуется an Effect[PlayerCharacter]
, T
должно быть PlayerCharacter
, у которого есть hpLow
метод, и компилятор завершен.
Надеюсь, кто-то более осведомленный может поправить меня в деталях или указать, не ошибаюсь ли я в целом!
Комментарии:
1. Возможно, вы правы в этом. На самом деле я не думаю, что функции с несколькими списками параметров являются действительно каррированными, потому что при вызове вам нужно использовать символ подчеркивания вместо любых отсутствующих списков аргументов, тогда как если у вас есть функция, которая явно каррирована, например «def curried(x : Int) = (y : Int) => x y», вам не нужно использовать символы подчеркивания. Но, возможно, моя ментальная модель того, как Scala обрабатывает эти типы функций, нуждается в некотором пересмотре.
2. @Nimrand Ах, я не знал, что для этого нужно добавлять символы подчеркивания. Я знаю, что с несколькими списками параметров компилятор будет исправлять параметры типа полностью на основе параметров, предоставленных в первый список (иногда это полезно и позволяет избежать необходимости в явных подсказках, чтобы избежать двусмысленностей, иногда это вызывает ошибки типа, которые можно устранить с помощью аннотаций типа вручную). Средство вывода типов определенно обрабатывает несколько списков параметров по- разному , даже если они не обрабатываются так же, как явное использование функций.
Ответ №2:
Я немного смущен, потому что, на мой взгляд, ни одна из версий, о которых вы говорите, не должна работать, и, действительно, я не могу заставить ни одну из них работать.
Вывод типа выполняется слева направо от одного списка параметров (не параметров) к следующему. Типичным примером является метод foldLeft в коллекциях (скажем, Seq[A])
def foldLeft[B] (z: B)(op: (B, A) => B): B
Тип z будет делать B известным, поэтому op может быть записан без указания B (ни A, который известен с самого начала, введите параметр this).
Если процедура была записана как
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) = ...}
}
Комментарии:
1. Если
foldLeft
бы они были записаны какdef foldLeft[B](z: B, op: (B,A) => B): B
, разве это не сработало бы, потому что передача значения дляz
будет привязанаB
к его типу? Или тип всего в списке параметров должен быть выводимым независимо от других объектов в том же списке?2. Второй. Доступна только информация, полученная из предыдущих списков параметров.
3. Вы правы в том, что, когда параметр типа a выводится из его аргументов, используется только первый список аргументов. Я считаю, что причина, по которой он может выводить параметр типа «when», заключается в том, что его результат передается методу, который ожидает аргумент типа Effect[PlayerCharacter], и поэтому ожидаемый тип «when» — Effect [PlayerCharacter], и поэтому тип T долженбыть персонажем игрока (или, возможно, каким-то более широким типом). Если я просто вызываю «когда» сам по себе, ни один из примеров, которые я привел, не работает.
4. Спасибо за разъяснение, я немного волновался. В качестве второстепенной детали для вывода аргумента типа используется не только список параметров: def appOn[A,B](a: A)(f: A => B) = f(a) нормально, вы можете написать appOn(3)(_ 1) иB будет выведен из второго списка. Но тот факт, что A является Int , который выводится из первого списка, не будет доступен до второго списка, где это необходимо, если мы хотим, чтобы выводился тип аргумента функции