Тема Lambda, обязательная для использования типов с более высоким родством в языке программирования Scala, является темой этого поста. В этом посте мы обсудим применение типа лямбда в решении конкретных задач.
Фон
В этом разделе мы перечисляем несколько концепций, которые необходимы для нашей сегодняшней дискуссии.
Тип Члены
Scala позволяет определять элементы типа в trait
или class
следующим образом.
trait HList{
type Hd
....
}
Мы можем определить член абстрактного типа, Hd
а также можем придать конкретное значение Hd
из его окружающего контекста, как показано далее.
class IntList extends HList {
type Hd = Int
....
}
Как отмечено в # 1 , приведенное выше определение Hd
может также использоваться в качестве псевдонима для типа Int
. Например, мы можем определить псевдоним типа для Map[Int, V] as
IntMap`
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.
Дайте мне знать, если у вас есть какие-либо вопросы. Спасибо за посещение этого поста и прохождение этого бессвязного.