Какой тип лямбда-выражения в Scala и каковы их преимущества?
иногда я натыкаюсь на полу-таинственную нотацию
def f[T](..) = new T[({type l[A]=SomeType[A,..]})#l] {..}
в сообщениях блога Scala, которые дают ему" мы использовали этот тип-лямбда-трюк " handwave.
хотя у меня есть некоторая интуиция об этом (мы получаем анонимный параметр типа A без необходимости загрязнять определение с ним?), Я не нашел четкого источника, описывающего, что такое тип лямбда-трюк, и каковы его преимущества. Это просто синтаксический сахар, или он открывает некоторые новые измерения?
4 ответов:
лямбды типа жизненно важны довольно много времени, когда вы работаете с более высокими типами.
рассмотрим простой пример определения монады для правой проекции либо[A, B]. Тип монады выглядит так:
trait Monad[M[_]] { def point[A](a: A): M[A] def bind[A, B](m: M[A])(f: A => M[B]): M[B] }Теперь либо является конструктором типа двух аргументов, но для реализации Monad вам нужно дать ему конструктор типа одного аргумента. Решение этой проблемы заключается в использовании типа лямбда:
class EitherMonad[A] extends Monad[({type λ[α] = Either[A, α]})#λ] { def point[B](b: B): Either[A, B] def bind[B, C](m: Either[A, B])(f: B => Either[A, C]): Either[A, C] }Это пример каррирования в системе типов - вы каррировали тип либо, так что, когда вы хотите создать экземпляр EitherMonad, вы должны указать один из типов; другой, конечно, предоставляется в то время, когда вы вызываете point или bind.
трюк типа lambda использует тот факт, что пустой блок в позиции типа создает анонимный структурный тип. Затем мы используем синтаксис # для получения члена типа.
в некоторых случаях вам может понадобиться более сложный тип лямбд это боль, чтобы написать inline. Вот пример из моего кода с сегодняшнего дня:
// types X and E are defined in an enclosing scope private[iteratee] class FG[F[_[_], _], G[_]] { type FGA[A] = F[G, A] type IterateeM[A] = IterateeT[X, E, FGA, A] }этот класс существует исключительно для того, чтобы я мог использовать имя типа FG[F, G]#IterateeM для ссылки на тип монады IterateeT, специализирующейся на некоторой трансформаторной версии второй монады, которая специализирована на некоторой третьей монаде. Когда вы начинаете складывать, эти виды конструкций становятся очень необходимыми. Я никогда не создаю экземпляр FG, конечно; это просто хак, чтобы позволить мне выразить то, что я хочу в системе типов.
преимущества точно такие же, как те, которые предоставляются анонимными функциями.
def inc(a: Int) = a + 1; List(1, 2, 3).map(inc) List(1, 2, 3).map(a => a + 1)пример использования, с Scalaz 7. Мы хотим использовать
Functorкоторый может отображать функцию над вторым элементом вTuple2.type IntTuple[+A]=(Int, A) Functor[IntTuple].map((1, 2))(a => a + 1)) // (1, 3) Functor[({type l[a] = (Int, a)})#l].map((1, 2))(a => a + 1)) // (1, 3)Scalaz предоставляет некоторые неявные преобразования, которые могут вывести аргумент типа
Functor, поэтому мы часто избегаем писать их вообще. Предыдущая строка может быть переписана как:(1, 2).map(a => a + 1) // (1, 3)если вы используете IntelliJ, вы можете включить Настройки, Стиль Кода, Скала, Складывание, Тип Лямбды. Это тогда скрывает грубые части синтаксиса, и представляет более приемлемым:
Functor[[a]=(Int, a)].map((1, 2))(a => a + 1)) // (1, 3)будущая версия Scala может напрямую поддерживать такой синтаксис.
чтобы поместить вещи в контекст: этот ответ был первоначально опубликован в другом потоке. Вы видите его здесь, потому что два потока были объединены. Вопрос в указанном потоке был следующим:
как решить это определение типа: Pure [({type ?[a]=(R, a)})#?] ?
каковы причины использования такой конструкции?
Snipped происходит от scalaz библиотека:
trait Pure[P[_]] { def pure[A](a: => A): P[A] } object Pure { import Scalaz._ //... implicit def Tuple2Pure[R: Zero]: Pure[({type ?[a]=(R, a)})#?] = new Pure[({type ?[a]=(R, a)})#?] { def pure[A](a: => A) = (Ø, a) } //... }
ответ:
trait Pure[P[_]] { def pure[A](a: => A): P[A] }одно подчеркивание в полях после
Pподразумевает, что это конструктор типа принимает один тип и возвращает другой тип. Примеры конструкторов типов с таким типом:List,Option.дать
ListanInt, конкретный тип, и это дает вамList[Int], еще один конкретный тип. ДайListaStringи это дает вамList[String]. Так далее.и
List,Optionможно рассматривать как функции уровня типа arity 1. Формально мы говорим, что они имеют вид* -> *. Звездочка обозначает тип.теперь
Tuple2[_, _]- это конструктор типов с видом(*, *) -> *т. е. вам нужно дать ему два типа, чтобы получить новый тип.так как их подписи не совпадают, вы не можете заменить
Tuple2наP. Что вам нужно сделать, это частично применитьTuple2на одном из его аргументы, которые дадут нам конструктор типа с видом* -> *, и мы можем заменить его наP.к сожалению, Scala не имеет специального синтаксиса для частичного применения конструкторов типов, и поэтому нам приходится прибегать к чудовищности, называемой типом lambdas. (Что у вас есть в вашем примере.) Они называются так потому, что они аналогичны лямбда-выражениям, которые существуют на уровне значений.
следующий пример может справка:
// VALUE LEVEL // foo has signature: (String, String) => String scala> def foo(x: String, y: String): String = x + " " + y foo: (x: String, y: String)String // world wants a parameter of type String => String scala> def world(f: String => String): String = f("world") world: (f: String => String)String // So we use a lambda expression that partially applies foo on one parameter // to yield a value of type String => String scala> world(x => foo("hello", x)) res0: String = hello world // TYPE LEVEL // Foo has a kind (*, *) -> * scala> type Foo[A, B] = Map[A, B] defined type alias Foo // World wants a parameter of kind * -> * scala> type World[M[_]] = M[Int] defined type alias World // So we use a lambda lambda that partially applies Foo on one parameter // to yield a type of kind * -> * scala> type X[A] = World[({ type M[A] = Foo[String, A] })#M] defined type alias X // Test the equality of two types. (If this compiles, it means they're equal.) scala> implicitly[X[Int] =:= Foo[String, Int]] res2: =:=[X[Int],Foo[String,Int]] = <function1>Edit:
больше параллелей уровня значения и уровня типа.
// VALUE LEVEL // Instead of a lambda, you can define a named function beforehand... scala> val g: String => String = x => foo("hello", x) g: String => String = <function1> // ...and use it. scala> world(g) res3: String = hello world // TYPE LEVEL // Same applies at type level too. scala> type G[A] = Foo[String, A] defined type alias G scala> implicitly[X =:= Foo[String, Int]] res5: =:=[X,Foo[String,Int]] = <function1> scala> type T = World[G] defined type alias T scala> implicitly[T =:= Foo[String, Int]] res6: =:=[T,Foo[String,Int]] = <function1>в случае, если вы представили, параметр типа
Rявляется локальным для функцииTuple2Pureи поэтому вы не можете просто определитьtype PartialTuple2[A] = Tuple2[R, A], потому что просто нет места, где вы можете поместить этот синоним.чтобы справиться с таким случаем, я использую следующий трюк, который использует члены типа. (Надеюсь, пример само собой разумеется.)
scala> type Partial2[F[_, _], A] = { | type Get[B] = F[A, B] | } defined type alias Partial2 scala> implicit def Tuple2Pure[R]: Pure[Partial2[Tuple2, R]#Get] = sys.error("") Tuple2Pure: [R]=> Pure[[B](R, B)]
type World[M[_]] = M[Int]вызывает то, что мы помещаем вAinX[A]theimplicitly[X[A] =:= Foo[String,Int]]всегда верно, я думаю.
Comments