Статьи

Частичное применение конструкторов типа с лямбдами типа

Тема 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.

Дайте мне знать, если у вас есть какие-либо вопросы. Спасибо за посещение этого поста и прохождение этого бессвязного.

Ссылки

  1. Программирование в Scala Odersky et al.
  2. Программирование Scala Дином Уэмплером и Алексом Пейном .
  3. Ответ Криса Наттикомба из Stackoverflow о преимуществах Type Lambda в Scala.
  4. Более читаемый лямбда-трюк типа Дэн Розен