Вопрос

Последующее наблюдение Презентация Ника Партиджа при выводе скалаз, Я должен был посмотреть на этот пример, который просто потрясающий:

import scalaz._
import Scalaz._
def even(x: Int) : Validation[NonEmptyList[String], Int] 
    = if (x % 2 ==0) x.success else "not even: %d".format(x).wrapNel.fail

println( even(3) <|*|> even(5) ) //prints: Failure(NonEmptyList(not even: 3, not even: 5))

Я пытался понять, что за <|*|> метод был выполнен, вот исходный код:

def <|*|>[B](b: M[B])(implicit t: Functor[M], a: Apply[M]): M[(A, B)] 
    = <**>(b, (_: A, _: B))

Хорошо, это довольно запутанно (!) - но это ссылается на <**> метод, который объявляется таким образом:

def <**>[B, C](b: M[B], z: (A, B) => C)(implicit t: Functor[M], a: Apply[M]): M[C] 
    = a(t.fmap(value, z.curried), b)

Итак, у меня есть несколько вопросов:

  1. Почему этот метод, по-видимому, требует тип более высокого качества параметра одного типа (M[B]) но может быть передан Validation (который имеет пареметры двух типов)?
  2. Синтаксис (_: A, _: B) определяет функцию (A, B) => Pair[A,B] который ожидает 2-й метод: что происходит с Tuple2 / Pair в случае сбоя?Никакого кортежа не видно!
Это было полезно?

Решение

Конструкторы типов в качестве параметров типа

M является параметром типа для одного из основных pimps Scalaz, МА, который представляет конструктор типа (он же Тип более высокого типа) расширенного значения.Этот конструктор типа используется для поиска соответствующих экземпляров 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), которое относится к классу1 (§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[_, _].

Это неявное преобразование в масштабирование объекта преобразует значение типа Validation[X, Y] к a 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)
}

Который, в свою очередь, использует трюк с псевдонимом типа в Частичное применение1of2 чтобы частично применить конструктор типа 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 должны иметь связанные экземпляры Применять и Функтор.Это представляет собой Аппликативный функтор, который, подобно Монаде, является способом структурирования вычисления с помощью некоторого эффекта.В этом случае результатом является то, что дополнительные вычисления могут завершиться неудачей (и когда это происходит, мы накапливаем сбои).

Контейнер Validation[NonEmptyList[String], A] может обернуть чистое значение типа A в этом "эффекте".В <**> operator принимает два эффективных значения и чистую функцию и объединяет их с экземпляром 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, применяемый к действенным аргументам.Обратите внимание, что результат заключен в тот же эффект (или контейнер, если хотите), что и аргументы.

Вы можете использовать один и тот же шаблон во множестве контейнеров, с которыми связан Аппликативный функтор.Все монады автоматически являются аппликативными функторами, но их еще больше, например 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)

И за его пределами

Вот наши объединенные примеры для Аппликативный и Валидация.Я использовал немного другой синтаксис для использования Аппликативного функтора, (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)
  }
}

Монада или Аппликатив, как мне выбрать?

Все еще читаешь?(ДА.Эд)

Я показал, что субвычисления, основанные на Option или [A] Validation[E, A] может сочетаться с любым Apply или с Bind.Когда бы вы предпочли одно другому?

Когда вы используете Apply, структура вычисления фиксирована.Все дополнительные вычисления будут выполнены;результаты одного не могут повлиять на результаты других.Только "чистая" функция имеет обзор того, что произошло.Монадические вычисления, с другой стороны, позволяют первому дополнительному вычислению влиять на последующие.

Если бы мы использовали монадическую структуру проверки, первый сбой привел бы к короткому замыканию всей проверки, так как не было бы Success значение для ввода в последующую проверку.Тем не менее, мы рады, что дополнительные проверки являются независимыми, поэтому мы можем объединить их с помощью приложения и собрать все ошибки, с которыми мы сталкиваемся.Слабость Аппликативных функторов превратилась в силу!

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top