Какие лямбды типа в Scala и каковы их преимущества? - PullRequest
148 голосов
/ 05 января 2012

Иногда я натыкаюсь на полузадачную нотацию

def f[T](..) = new T[({type l[A]=SomeType[A,..]})#l] {..} 

в сообщениях в блоге Scala, которая придает ей «мы использовали этот трюк типа лямбда».

Пока яИмея некоторую интуицию по этому поводу (мы получаем параметр анонимного типа A, не загрязняя его определением?), я не нашел четкого источника, описывающего, что такое лямбда-трюк типа и каковы его преимущества.Это просто синтаксический сахар или он открывает новые измерения?

Ответы [ 4 ]

146 голосов
/ 05 января 2012

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

Рассмотрим простой пример определения монады для правильной проекции Либо [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]
}

Это пример карри в системе типов - вы каррировали тип Either, так что, когда вы хотите создать экземпляр 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; это просто как хак, чтобы позволить мне выразить то, что я хочу в системе типов.

52 голосов
/ 05 января 2012

Преимущества точно такие же, как и у анонимных функций.

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, Складывание, Тип лямбды. Затем скрывает грубые части синтаксиса и представляет более приемлемый:

Functor[[a]=(Int, a)].map((1, 2))(a => a + 1)) // (1, 3)

Будущая версия Scala может напрямую поддерживать такой синтаксис.

40 голосов
/ 17 октября 2012

Чтобы поместить вещи в контекст: Этот ответ был первоначально размещен в другой теме. Вы видите это здесь, потому что два потока были объединены. Постановка вопроса в указанной ветке выглядела следующим образом:

Как разрешить определение этого типа: 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]. И т.д.

Итак, 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)]
0 голосов
/ 02 декабря 2014

type World[M[_]] = M[Int] приводит к тому, что все, что мы вставим в A в X[A], implicitly[X[A] =:= Foo[String,Int]] всегда верно, я думаю.

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