Function syntax puzzler in scalaz

Po obejrzeniu prezentacji Nicka Partidge ' a na temat scalaz , rzuciłem okiem na ten przykład, który jest po prostu super:

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))

Próbowałem zrozumieć, co robi metoda <|*|>, Oto kod źródłowy:

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

OK, to jest dość mylące (!)- ale odwołuje się do metody <**>, która jest zadeklarowana w ten sposó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)

Więc mam kilka pytań:

  1. dlaczego metoda wydaje się przyjmować Typ wyższy jednego parametru typu (M[B]), ale może zostać przekazany Validation (który ma dwa paremetry typu)?
  2. składnia (_: A, _: B) definiuje funkcję (A, B) => Pair[A,B], której oczekuje druga metoda: co się dzieje z Krotką 2/Pair w przypadku błędu? Nie ma tuple w zasięgu wzroku!
Author: oxbow_lakes, 2010-03-30

1 answers

Konstruktory typu jako parametry typu

M jest to parametr typu dla jednego z głównych pimpów Scalaza, MA , który reprezentuje Konstruktor typu (aka Typ Higher Kinded) wartości pimped. Ten konstruktor typu służy do wyszukiwania odpowiednich instancji Functor i Apply, które są niejawnymi wymaganiami metody <**>.

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] = ...
}

Czym jest konstruktor typu?

Z odniesień do języka Scala:

Rozróżniamy pierwsze zamówienie typy i konstruktory typów, które weź parametry typu i typy wydajności. Podzbiór typów pierwszego rzędu zwany typy wartości reprezentują zbiory (pierwsza klasa) wartości. Typy wartości to albo konkretny, albo abstrakcyjny. Każdy konkretny typ wartości może być reprezentowany jako typ klasy, czyli typ oznaczenie (§3.2.3), które odnosi się do klasa1 (§5.3), lub jako typ złożony (§3.2.7) reprezentujące przecięcie typów, ewentualnie z dopracowaniem (§3.2.7) że dalej ogranicza rodzaje itsmembers. Wartość abstrakcyjna typy wprowadza się według typu parametry (§4.4) i typ abstrakcyjny wiązania (§4.3). Nawiasy w typach są używane do grupowania. Zakładamy, że obiekty i pakiety również pośrednio zdefiniuj klasę (o tej samej nazwie co obiekt lub pakiet, ale niedostępne dla programów użytkownika).

Typy bez wartości przechwytują właściwości identyfikatory, które nie są wartościami (§3.3). Na przykład Typ konstruktor (§3.3.3) nie bezpośrednio Określ typ wartości. Jednakże, gdy konstruktor typu jest stosowany do poprawnych argumentów typu, daje typu pierwszego rzędu, który może być typ wartości. Typy bez wartości to wyrażona pośrednio w Scali. Np. a Typ metody opisywany jest poprzez zapis w dół podpis metody, który w sam w sobie nie jest prawdziwym typem, chociaż daje początek odpowiedniej funkcji Typ (§3.3.1). Konstruktorami typu są kolejny przykład, jak można pisać Typ Swap [m [ _ ,_], a, b] = m [b,a], ale nie ma składni do napisania odpowiadająca funkcja anonimowego typu bezpośrednio.

List jest konstruktorem typu. Możesz zastosować typ Int, aby uzyskać Typ wartości, List[Int], który może sklasyfikować wartość. Inne konstruktory typu przyjmują więcej niż jeden parametr.

Cecha scalaz.MA wymaga, aby pierwszym parametrem typu był konstruktor typu, który pobiera pojedynczy typ, aby zwrócić typ wartości, ze składnią trait MA[M[_], A] {}. Definicja parametru typu opisuje kształt konstruktora typu, który jest określany jako jego rodzaj. List mówi się, że mają rodzaj " * -> *.

Częściowe zastosowanie typów

Ale jak MA owijać wartości typu Validation[X, Y]? Typ Validation mA typ (* *) -> * i może być przekazany tylko jako argument type do parametru type zadeklarowanego jak M[_, _].

Ta ukryta konwersja w obiekt Scalaz zamienia wartość typu Validation[X, Y] na 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)
}

Który z kolei wykorzystuje sztuczka z aliasem typu w PartialApply1Of2, aby częściowo zastosować konstruktor typu Validation, naprawiając Typ błędów, ale pozostawiając Typ sukcesu niezatwierdzony.

PartialApply1Of2[Validation, E]#Apply byłoby lepiej napisane jako [X] => Validation[E, X]. Ostatnio zaproponowałem dodanie takiej składni do Scali, może się to zdarzyć w 2.9.

Potraktuj to jako odpowiednik poziomu typu tego:

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)

To pozwala połączyć Validation[String, Int] Z Validation[String, Boolean], ponieważ oba współdzielą konstruktor typu [A] Validation[String, A].

Funktory Aplikacyjne

<**> wymaga, aby konstruktor typu {[6] } miał powiązane instancje Apply i Functor . Jest to funktor aplikacyjny, który, podobnie jak Monada, jest sposobem na uporządkowanie obliczeń poprzez pewien efekt. W tym przypadku efekt jest taki, że podliczniki mogą się nie udać (a gdy tak się stanie, kumulujemy awarie).

Kontener Validation[NonEmptyList[String], A] może zawinąć czystą wartość typu A w ten 'efekt'. Operator <**> pobiera dwie efektywne wartości oraz czystą funkcję i łączy je z instancją Applicative Functor dla tego kontenera.

Oto jak to działa dla Option funkcji aplikacyjnej. "Efektem" jest tutaj możliwość niepowodzenia.

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)

W obu przypadkach istnieje czysta funkcja typu (String, Int) => String, stosowana do efektywnych argumentów. Zauważ, że wynik jest zawinięty w ten sam efekt (lub kontener, jeśli chcesz), jak argumenty.

Możesz użyj tego samego wzorca w wielu kontenerach, które mają powiązany funktor aplikacyjny. Wszystkie monady są automatycznie funkcjami aplikacyjnymi, ale jest ich jeszcze więcej, Jak ZipStream.

Option I [A]Validation[X, A] są obie monady, więc można również użyć Bind (aka flatMap):

val result3 = oi flatMap { i => os map { s => s * i } }
val result4 = for {i <- oi; s <- os} yield s * i

Tupling z``

<|**|> jest bardzo podobny do <**>, ale zapewnia czystą funkcję, dzięki której można po prostu zbudować Tuple2 na podstawie wyników. (_: A, _ B) jest skrótem od (a: A, b: B) => Tuple2(a, b)

I dalej

Oto nasze przykładowe aplikacje Applicative i Validation. Używałem nieco innej składni, aby użyć funktora aplikacyjnego, (fa ⊛ fb ⊛ fc ⊛ fd) {(a, b, c, d) => .... }

Aktualizacja: ale co się dzieje w przypadku awarii?

co się dzieje z Tuple2 / Pair w przypadku awarii ?

Jeśli którekolwiek z pod-obliczeń nie powiedzie się, podana funkcja nigdy nie zostanie uruchomiona. Jest uruchamiany tylko wtedy, gdy wszystkie podliczniki (w tym case, dwa argumenty przekazane <**>) są pomyślne. Jeśli tak, to łączy je w Success. Gdzie ta logika? Definiuje to instancję Apply dla [A] Validation[X, A]. Wymagamy, aby Typ X miał Semigroup Dostępny, który jest strategią łączenia pojedynczych błędów, każdego typu X, w zagregowany błąd tego samego typu. Jeśli wybierzesz {[52] } jako typ błędu, Semigroup[String] połączy ciągi znaków; jeśli wybierzesz NonEmptyList[String], błędy z każdego kroku zostaną połączone w dłuższy NonEmptyList błędów. Ta konkatenacja dzieje się poniżej, gdy dwa Failures są połączone, używając operatora (który rozszerza się z domniemanymi do, na przykład, 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 czy Applicative, jak mam wybrać?

Nadal czytasz? (tak. Ed )

Pokazałem, że podliczniki oparte na Option lub [A] Validation[E, A] mogą być łączone z Apply lub z Bind. Kiedy wybierzesz jedno nad drugim?

Kiedy używasz Apply, struktura obliczenia są stałe. Wszystkie podliczniki zostaną wykonane; wyniki jednego nie mogą wpływać na inne. Tylko funkcja "czysta" ma przegląd tego, co się stało. Z kolei obliczenia monadyczne pozwalają na wpływ pierwszych podliczników na późniejsze.

Jeśli użylibyśmy Monadycznej struktury walidacji, pierwsza awaria spowodowałaby zwarcie całej walidacji, ponieważ nie byłoby Success wartości, która mogłaby zostać przekazana do następnej walidacji. Cieszymy się jednak z sub-walidacje, aby były niezależne, abyśmy mogli połączyć je za pomocą aplikatora i zebrać wszystkie napotkane błędy. Słabość funkcji aplikacyjnych stała się siłą!

 64
Author: retronym,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-11-18 09:52:05