Когда-нибудь я наткнулся на полу-таинственную нотацию
def f[T](..) = new T[({type l[A]=SomeType[A,..]})#l] {..}
в сообщениях в блоге Scala, в которых говорится, что мы использовали «лямбда-трюк».
Хотя у меня есть некоторая интуиция по этому поводу (мы получаем параметр анонимного типа A
без необходимости загрязнять его определение?), Я не нашел четкого источника, описывающего, что такое лямбда-трюк типа и каковы его преимущества. Это просто синтаксический сахар, или он открывает новые измерения?
Ответы:
Тип лямбда очень важен в те моменты, когда вы работаете с типами с более высоким родом.
Рассмотрим простой пример определения монады для правильной проекции Either [A, B]. Класс типов монады выглядит так:
Теперь, Either является конструктором типов с двумя аргументами, но для реализации Monad вам нужно дать ему конструктор типов с одним аргументом. Решением этого является использование типа лямбда:
Это пример карри в системе типов - вы каррировали тип Either, так что, когда вы хотите создать экземпляр EitherMonad, вам нужно указать один из типов; другой, конечно, предоставляется в тот момент, когда вы звоните или связываете.
Лямбда-трюк типа использует тот факт, что пустой блок в позиции типа создает анонимный структурный тип. Затем мы используем синтаксис # для получения члена типа.
В некоторых случаях вам могут понадобиться более сложные лямбды типа, которые сложно выписать в строке. Вот пример из моего сегодняшнего кода:
Этот класс существует исключительно для того, чтобы я мог использовать имя, такое как FG [F, G] #IterateeM, для ссылки на тип монады IterateeT, специализированной для некоторой трансформаторной версии второй монады, которая специализирована для некоторой третьей монады. Когда вы начинаете складывать, эти виды конструкций становятся очень необходимыми. Конечно, я никогда не запускаю FG; это просто как хак, чтобы позволить мне выразить то, что я хочу в системе типов.
источник
bind
метод для вашегоEitherMonad
класса. :-) Кроме того, если я могу на секунду направить канал Adriaan, вы не будете использовать типы с более высоким родом в этом примере. Вы вFG
, но не вEitherMonad
. Скорее, вы используете конструкторы типов , которые имеют вид* => *
. Этот вид имеет порядок-1, который не «выше».*
был порядок-1, но в любом случае Монада имеет вид(* => *) => *
. Кроме того, вы заметите, что я указал «правильную проекциюEither[A, B]
» - реализация тривиальна (но хорошее упражнение, если вы не сделали этого раньше!)*=>*
выше, оправдывается аналогией о том, что мы не называем обычную функцию (которая отображает не функции в не функции, иными словами, простые значения в простые значения) функцию более высокого порядка.Type expressions with kinds like (*⇒*)⇒* are called higher-order typeoperators.
Преимущества точно такие же, как и у анонимных функций.
Пример использования со Scalaz 7. Мы хотим использовать функцию,
Functor
которая может сопоставить функцию со вторым элементом вTuple2
.Scalaz предоставляет некоторые неявные преобразования, которые могут выводить аргумент типа
Functor
, поэтому мы часто избегаем их написания. Предыдущая строка может быть переписана как:Если вы используете IntelliJ, вы можете включить Настройки, Стиль кода, Scala, Складывание, Тип Lambdas. Это тогда скрывает грубые части синтаксиса и представляет более приемлемый:
Будущая версия Scala может напрямую поддерживать такой синтаксис.
источник
(1, 2).map(a => a + 1)
в REPL: `<console>: 11: ошибка: карта значений не является членом (Int, Int) (1, 2) .map (a => a + 1) ^`Чтобы поместить вещи в контекст: Этот ответ был первоначально размещен в другой теме. Вы видите это здесь, потому что два потока были объединены. Постановка вопроса в указанной ветке была следующей:
Ответ:
Подчеркивание в полях после
P
подразумевает, что это конструктор типов, который принимает один тип и возвращает другой тип. Примеры конструкторов типа с таким родом:List
,Option
.Дайте , тип бетона, и это дает вам , еще один конкретный тип. Дайте и это дает вам . И т.п.
List
Int
List[Int]
List
String
List[String]
Таким образом,
List
,Option
можно рассматривать как функции уровня типа арности 1. Формально мы говорим, что они имеют вид* -> *
. Звездочка обозначает тип.Теперь
Tuple2[_, _]
это конструктор типа с типом,(*, *) -> *
т.е. вам нужно дать ему два типа, чтобы получить новый тип.Так как их подпись не совпадает, вы не можете заменить
Tuple2
наP
. Что вам нужно сделать, так это частично применитьTuple2
к одному из его аргументов, что даст нам конструктор типа с видом* -> *
, и мы можем заменить егоP
.К сожалению, в Scala нет специального синтаксиса для частичного применения конструкторов типов, поэтому нам приходится прибегать к чудовищу, называемому типом lambdas. (Что вы имеете в своем примере.) Они называются так, потому что они аналогичны лямбда-выражениям, которые существуют на уровне значений.
Следующий пример может помочь:
Редактировать:
Больше параллелей между уровнем значения и уровнем типа.
В представленном случае параметр типа
R
является локальным для функции,Tuple2Pure
и поэтому вы не можете просто определитьtype PartialTuple2[A] = Tuple2[R, A]
, потому что просто нет места, где вы можете поместить этот синоним.Чтобы справиться с таким случаем, я использую следующую уловку, которая использует члены типа. (Надеюсь, пример не требует пояснений.)
источник
type World[M[_]] = M[Int]
вызывает то , что все , что мы вкладываем вA
в всегда верно , я думаю.X[A]
implicitly[X[A] =:= Foo[String,Int]]
источник