Что такое lambdas типа Scala и каковы их преимущества?
Когда-то я натыкаюсь на полу-таинственную нотацию
def f[T](..) = new T[({type l[A]=SomeType[A,..]})#l] {..}
в Scala сообщениях в блоге, которые дают ему "мы использовали ручную волну этого типа лямбда-трюка".
В то время как у меня есть некоторые интуиции об этом (мы получаем анонимный параметр типа A
, не загрязняя его определением?), я не нашел ясного источника, описывающего, что такое трюк лямбды типа и каковы его преимущества. Является ли это просто синтаксическим сахаром или открывает некоторые новые измерения?
Ответы
Ответ 1
Тип lambdas очень важен, когда вы работаете с более высокоподобными типами.
Рассмотрим простой пример определения монады для правой проекции Либо [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]
}
Это пример currying в системе типов - вы указали тип Либо, так что, когда вы хотите создать экземпляр EitherMonad, вам нужно указать один из типов; другой, конечно, предоставляется во время вызова точки или привязки.
Тип лямбда-трюка использует тот факт, что пустой блок в позиции типа создает анонимный структурный тип. Затем мы используем синтаксиС# для получения члена типа.
В некоторых случаях вам может потребоваться более сложный тип лямбда, который является болью для выписки из строки. Вот пример из моего кода с сегодняшнего дня:
// 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. это просто как хак, чтобы я мог выразить то, что хочу в системе типов.
Ответ 2
Преимущества в точности совпадают с преимуществами анонимных функций.
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, вы можете включить Настройки, Стиль кода, Scala, Складывать, Тип Lambdas. Таким образом, скрывает крутые части синтаксиса и представляет более приятные:
Functor[[a]=(Int, a)].map((1, 2))(a => a + 1)) // (1, 3)
Будущая версия Scala может напрямую поддерживать такой синтаксис.
Ответ 3
Поместить вещи в контекст: этот ответ был первоначально отправлен в другой поток. Вы видите это здесь, потому что эти два потока объединены. Запрос вопроса в указанном потоке был следующим:
Как разрешить определение этого типа: 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
.
Дайте List
a Int
, конкретный тип, и он даст вам List[Int]
другой конкретный тип. Дайте List
a String
, и он даст вам List[String]
. Etc.
Итак, 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)]
Ответ 4
type World[M[_]] = M[Int]
приводит к тому, что все, что мы помещаем в A
в X[A]
, всегда имеет значение implicitly[X[A] =:= Foo[String,Int]]
.