Тема Lambda, обязательная для использования типов с более высоким родством в языке программирования Scala, является темой этого поста. В этом посте мы обсудим применение типа лямбда в решении конкретных задач.
Фон
В этом разделе мы перечисляем несколько концепций, которые необходимы для нашей сегодняшней дискуссии.
Тип Члены
Scala позволяет определять элементы типа в traitили classследующим образом.
trait HList{
type Hd
....
}
Мы можем определить член абстрактного типа, Hdа также можем придать конкретное значение Hdиз его окружающего контекста, как показано далее.
class IntList extends HList {
type Hd = Int
....
}
Как отмечено в # 1 , приведенное выше определение Hdможет также использоваться в качестве псевдонима для типа Int. Например, мы можем определить псевдоним типа для Map[Int, V] asIntMap`
type IntMap[V] = Map[Int, V]
Обратите внимание, что в приведенном выше коде Vэто параметр параметрического типа и может варьироваться в зависимости от контекста IntMap.
Тип Проекция
Мы можем получить доступ к членам типа оператора classили traitс #оператором (аналогично .оператору в случае членов-значений), например IntList#Hd. Например, следующая строка утверждает, что IntList#Hdэто действительно Intтип.
implicitly[Int =:= IntList#Hd]
Типовые члены класса или признака могут быть повторно использованы в другом контексте, и соответствующие проверки типов выполняются статически —
val x: IntList#Hd = 10
Принцип дизайна
С точки зрения дизайна, члены типа особенно интересны, когда они развиваются вместе со своим типом включения, чтобы соответствовать поведению соответственно. Как отмечено в # 2 , это расценено как семейный полиморфизм или ковариантная специализация .
Тип приложения Лямбда
Тип лямбда часто рассматривается как лямбда типа . Как следует из названия, он похож на анонимные функции, но для типов . Фактически, лямбда-типы используются для определения анонимных / внутренних типов для данного контекста. Это особенно полезно, когда в конструкторе типов меньше параметров типа по сравнению с параметризованным типом, который мы хотим применить в этом контексте.
Далее мы объясним концепцию типа лямбда на примере.
описание проблемы
Рассмотрим следующий отличный пример Functorиз # 2 :
trait Functor[A, +M[_]]{
def map[B] (f: A => B): M[B]
}
Обратите внимание, что M[_]конструктор типов принимает только один параметр типа. Таким образом, это довольно просто, если мы хотим расширить Functorопределенные выше Seqтипы.
case class SeqFunctor[A](seq: Seq[A])
extends Functor[A, Seq]{
override def map[B](f: (A) => B): Seq[B] =
seq.map(f)
}
}
И мы можем использовать SeqFunctorследующим образом:
> val lst = List(1,2,3)
> ListFunctor(lst).map(_ * 10)
// prints List(10, 20, 30)
Однако, в случае Map[K, V], было бы сложно расширить, так как Map[K,V]имеет два параметра типа , а M[_]конструктор типа принимает только один.
Решение
Чтобы решить эту проблему, мы применяем тип lambda , который обрабатывает дополнительный параметр типа, как показано ниже.
case class MapFunctor[K,V](mapKV: Map[K,V])
extends Functor[V, ({type L[a] = Map[K,a]})#L]{
override def map[V2](f: V => V2): Map[K, V2] =
mapKV map{
case (k,v) => (k, f(v))
}
}
> MapFunctor(Map(1->1, 2->2, 3->3)).map(_ * 10)
// Result: Map(1 -> 10, 2 -> 20, 3 -> 30)
Здесь мы просто применяем fзначения mapKV. Таким образом, переменные типа действительно есть Vи V2.
Неофициально, мы расширим Functorдля Mapс типа лямбда следующим образом-
Functor[A, +M[_]] ==>
Functor[V,
(
{type L[a] = Map[K, a]} // structural type definition
)
#L // type projection
]
Здесь {type L[a] = Map[K, a]}обозначает структурный тип. По сути, он указывает псевдоним внутреннего / анонимного типа L[a], который затем сопоставляется с (внешним) #L. Параметр типа Kприменяется частично и в этом случае был разрешен из контекста. Применяя проекцию типа #L, мы получаем член типа из структурного типа и таким образом определяем псевдоним для Map[K,_]действующего.
Как отмечено в # 3 , пустой блок в позиции типа (как в M[_]) по существу создает анонимный структурный тип, и проекция типа позволяет нам получить член типа из него.
Дополнительные хитрости
Тип лямбда кажется немного пугающим; и обработка нескольких из них приводит к несколько сложному для понимания коду. Чтобы избежать этого, Дэн Розен предложил хитрость.
Чтобы продемонстрировать это, давайте рассмотрим предыдущий пример. Возвращаясь к типу lambda from MapFunctor, отметим, что единственным изменяющимся типом является Vи V2. Мы можем определить Functorдля Map, избегая type lambdaи используя тип члена, Map[K]как показано ниже.
case class ReadableMapFunctor[K,V](mapKV: Map[K,V]){
def mapFunctor[V2] = {
type `Map[K]`[V2] = Map[K, V2]
new Functor[V, `Map[K]`] {
override def map[V2](f: (V) => V2): `Map[K]`[V2] = mapKV map{
case (k,v) => (k, f(v))
}
}
}
}
> ReadableMapFunctor(Map(1->1, 2->2, 3->3)).mapFunctor.map(_*10)
//Map(1 -> 10, 2 -> 20, 3 -> 30)
Этот трюк обрабатывает дополнительный параметр типа Map[K, V]. Также обратите внимание на кавычки; они разрешают использование []в имени идентификатора № 4 .
После GIST очертаний примеры кода , используемые в этой статье.
прогноз
В целом, это, кажется, отличная особенность с конкретным вариантом использования, но довольно многословная. Иногда это довольно сложно обойти. Но , конечно, у него есть свои приложения и является хорошим бы иметь инструмент для разработчиков Scala.
Дайте мне знать, если у вас есть какие-либо вопросы. Спасибо за посещение этого поста и прохождение этого бессвязного.