Конструкторы типа как параметры типа
M
- это параметр типа для одного из главных сутенеров Скалаза, MA , который представляет конструктор типов (также называемый типом с более высоким типом) значения в сутенере. Этот конструктор типов используется для поиска соответствующих экземпляров Functor
и Apply
, которые являются неявными требованиями к методу <**>
.
trait MA[M[_], A] {
val value: M[A]
def <**>[B, C](b: M[B], z: (A, B) => C)(implicit t: Functor[M], a: Apply[M]): M[C] = ...
}
Что такое конструктор типов?
Из справочника по языку Scala:
Различаем первый порядок
типы и конструкторы типов, которые
взять параметры типа и типы доходности.
Подмножество типов первого порядка, называемое
типы значений представляют собой наборы
(первоклассные) ценности. Типы значений
будь конкретным или абстрактным. каждый
конкретный тип значения может быть представлен
как тип класса, то есть тип
обозначение (§3.2.3), которое относится к
class1 (§5.3), или как составной тип
(§3.2.7), представляющий пересечение
типов, возможно, с уточнением
(§3.2.7), что дополнительно ограничивает
типы ее членов. Абстрактное значение
типы вводятся по типу
параметры (§4.4) и абстрактный тип
привязки (§4.3). Скобки в типах
используются для группировки. Мы предполагаем, что
объекты и пакеты также неявно
определить класс (с тем же именем, что и
объект или пакет, но
недоступен для пользовательских программ).
Не-значения типа захватывают свойства
идентификаторы, которые не являются значениями
(§3.3). Например, тип
конструктор (§3.3.3) не напрямую
укажите тип значений. Тем не мение,
когда конструктор типа применяется к
правильные аргументы типа, это дает
тип первого порядка, который может быть
тип ценности. Неценовые типы
выражается косвенно в Scala. Например,
тип метода описывается письменно
вниз подпись метода, которая в
сам по себе не настоящий тип, хотя это
рождает соответствующую функцию
тип (§3.3.1). Тип конструкторы
другой пример, как можно написать тип
Поменять местами [m [_, _], a, b] = m [b, a], но
нет синтаксиса, чтобы написать
соответствующая функция анонимного типа
непосредственно.
List
является конструктором типов. Вы можете применить тип Int
, чтобы получить тип значения List[Int]
, который может классифицировать значение. Конструкторы других типов принимают более одного параметра.
Для признака scalaz.MA
требуется, чтобы его первый параметр типа был конструктором типа, который принимает один тип для возврата типа значения с синтаксисом trait MA[M[_], A] {}
. Определение параметра типа описывает форму конструктора типа, который называется его типом. Говорят, что List
имеет вид '* -> *
.
Частичное применение типов
Но как MA
может обернуть значения типа Validation[X, Y]
? Тип Validation
имеет вид (* *) -> *
и может быть передан только в качестве аргумента типа параметру типа, объявленному как M[_, _]
.
Это неявное преобразование в объект Scalaz преобразует значение типа Validation[X, Y]
в MA
:
object Scalaz {
implicit def ValidationMA[A, E](v: Validation[E, A]): MA[PartialApply1Of2[Validation, E]#Apply, A] = ma[PartialApply1Of2[Validation, E]#Apply, A](v)
}
который, в свою очередь, использует трюк с псевдонимом типа в PartialApply1Of2 , чтобы частично применить конструктор типа Validation
, исправляя тип ошибок, но оставляя тип успеха неприменимым.
PartialApply1Of2[Validation, E]#Apply
лучше написать как [X] => Validation[E, X]
. Недавно я предложил добавить такой синтаксис в Scala, это может произойти в 2.9.
Думайте об этом как об эквивалентном уровне типа:
def validation[A, B](a: A, b: B) = ...
def partialApply1Of2[A, B C](f: (A, B) => C, a: A): (B => C) = (b: B) => f(a, b)
Это позволяет объединить Validation[String, Int]
с Validation[String, Boolean]
, поскольку оба они совместно используют конструктор типов [A] Validation[String, A]
.
Аппликативные функторы
<**>
требует, чтобы конструктор типа M
должен был иметь связанные экземпляры Apply и Functor . Это составляет Аппликативный Функтор, который, как и Монада, является способом структурирования вычислений посредством некоторого эффекта. В этом случае эффект состоит в том, что подкомпьютеры могут потерпеть неудачу (и когда они это делают, мы накапливаем ошибки).
Theконтейнер Validation[NonEmptyList[String], A]
может обернуть чистое значение типа A
в этот «эффект». Оператор <**>
принимает два эффективных значения и чистую функцию и объединяет их с экземпляром Applicative Functor для этого контейнера.
Вот как это работает для аппликативного функтора Option
. «Эффект» здесь - это вероятность отказа.
val os: Option[String] = Some("a")
val oi: Option[Int] = Some(2)
val result1 = (os <**> oi) { (s: String, i: Int) => s * i }
assert(result1 == Some("aa"))
val result2 = (os <**> (None: Option[Int])) { (s: String, i: Int) => s * i }
assert(result2 == None)
В обоих случаях существует чистая функция типа (String, Int) => String
, применяемая к эффективным аргументам. Обратите внимание, что результат обернут в тот же эффект (или контейнер, если хотите), что и аргументы.
Вы можете использовать один и тот же шаблон для множества контейнеров, имеющих связанный Applicative Functor. Все монады автоматически являются аппликативными функторами, но их даже больше, например ZipStream
.
Option
и [A]Validation[X, A]
- обе монады, поэтому вы также можете использовать Bind
(он же flatMap):
val result3 = oi flatMap { i => os map { s => s * i } }
val result4 = for {i <- oi; s <- os} yield s * i
Коррекция с помощью `<| ** |>`
<|**|>
действительно похож на <**>
, но он предоставляет чистую функцию для вас, чтобы просто построить Tuple2 из результатов. (_: A, _ B)
является сокращением для (a: A, b: B) => Tuple2(a, b)
и далее
Вот наши примеры для Applicative и Validation . Я использовал немного другой синтаксис для использования Аппликативного Функтора, (fa ⊛ fb ⊛ fc ⊛ fd) {(a, b, c, d) => .... }
ОБНОВЛЕНИЕ: Но что происходит в случае отказа?
что происходит с Tuple2 / Pair в случае сбоя ?
Если какое-либо из вспомогательных вычислений не выполняется, предоставленная функция никогда не запускается. Он запускается только в том случае, если все дополнительные вычисления (в этом случае два аргумента, переданные в <**>
) являются успешными. Если это так, он объединяет их в Success
. Где эта логика? Это определяет экземпляр Apply
для [A] Validation[X, A]
. Мы требуем, чтобы тип X должен иметь доступное значение Semigroup
, которое представляет собой стратегию объединения отдельных ошибок каждого типа X
в агрегированную ошибку того же типа. Если вы выберете String
в качестве типа ошибки, Semigroup[String]
объединит строки; если вы выберете NonEmptyList[String]
, ошибки на каждом шаге будут объединены в более длинные NonEmptyList
ошибок. Эта конкатенация происходит ниже, когда два Failures
объединяются с использованием оператора ⊹
(который расширяется с последствиями, например, до Scalaz.IdentityTo(e1).⊹(e2)(Semigroup.NonEmptyListSemigroup(Semigroup.StringSemigroup))
.
implicit def ValidationApply[X: Semigroup]: Apply[PartialApply1Of2[Validation, X]#Apply] = new Apply[PartialApply1Of2[Validation, X]#Apply] {
def apply[A, B](f: Validation[X, A => B], a: Validation[X, A]) = (f, a) match {
case (Success(f), Success(a)) => success(f(a))
case (Success(_), Failure(e)) => failure(e)
case (Failure(e), Success(_)) => failure(e)
case (Failure(e1), Failure(e2)) => failure(e1 ⊹ e2)
}
}
Monad или Applicative, как мне выбрать?
Все еще читаете? ( Да. Ред. )
Я показал, что подвычисления, основанные на Option
или [A] Validation[E, A]
, можно комбинировать либо с Apply
, либо с Bind
. Когда бы вы выбрали один из других?
Когда вы используете Apply
, структура вычислений фиксируется. Все подвычисления будут выполнены; результаты одного не могут влиять на других. Только «чистая» функция имеет обзор того, что произошло. Монадические вычисления, с другой стороны, позволяют первым подсчетам влиять на более поздние.
Если бы мы использовали структуру проверки Monadic, первый сбой приведет к короткому замыканию всей проверки, так как не будет никакого значения Success
для последующей проверки. Тем не менее, мы рады, что суб-валидации были независимыми, поэтому мы можем объединить их с помощью Applicative и собрать все сбои, с которыми мы сталкиваемся. Слабость аппликативных функторов стала силой!