Domanda

A seguito di guardare Nick Partidge presentazione su derivanti scalaz, Ho avuto modo di guardare questo esempio, che è semplicemente fantastico:

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

Stavo cercando di capire che cosa il <|*|> metodo stava facendo, qui è il codice sorgente:

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

OK, che è abbastanza confuso (!) - ma si fa riferimento al <**> il metodo, che è dichiarata così:

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)

Così ho un paio di domande:

  1. Come mai il metodo sembra un superiore-variegata tipo di un parametro di tipoM[B] ma è possibile ottenere passato un Validation (che ha tipo due reportfilter)?
  2. La sintassi (_: A, _: B) definisce la funzione (A, B) => Pair[A,B] che il 2 ° metodo prevede: ciò che sta accadendo al Tuple2/Coppia in caso di fallimento?Non c'è nessuna tupla in vista!
È stato utile?

Soluzione

Tipo di Costruttori come Parametri di Tipo

M è un parametro di tipo a uno dei Scalaz principali protettori, MA, che rappresenta il Tipo del Costruttore (aka Superiore Variegata Tipo) del pimped valore.Questo tipo di costruttore viene utilizzato per cercare le opportune istanze di Functor e Apply, che sono impliciti i requisiti per il metodo <**>.

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

Che cosa è un Costruttore di Tipo?

Dalla Scala di Riferimento del Linguaggio:

Si distinguono tra di primo ordine tipi e tipo di costruttori, che prendere il tipo di parametri di rendimento e di tipi.Un sottoinsieme del primo ordine i tipi di chiamata tipi di valore rappresenta insiemi di (prima classe) di valori.I tipi di valore sono sia concreto o astratto.Ogni calcestruzzo tipo di valore può essere rappresentato come un tipo di classe, cioèun tipo di designatore (§3.2.3) che si riferisce ad un class1 (§5.3), o come un composto tipo (§3.2.7) che rappresenta un incrocio di tutti i tipi, possibilmente con una raffinatezza (§3.2.7) che ha ulteriormente vincola l' tipi di itsmembers.Abstract valore tipi sono introdotti dal tipo di parametri (§4.4) e di tipo astratto associazioni (§4.3).Tra parentesi i tipi di sono utilizzati per il raggruppamento.Si assume che oggetti e pacchetti anche in modo implicito definire una classe (con lo stesso nome l'oggetto o il pacchetto, ma inaccessibile ai programmi utente).

Non tipi di valore di proprietà dell'acquisizione di identificatori che non sono i valori (§3.3).Per esempio, un tipo di costruttore (§3.3.3) non direttamente specificare il tipo di valori.Tuttavia, quando un costruttore di tipo viene applicato a il corretto tipo di argomenti, si rese un primo tipo di ordine, che può essere un tipo di valore.Non i tipi di valore sono espresso indirettamente in Scala.E. g., un il tipo di metodo è descritto da scrivere giù firma di un metodo, che in di per sé non è un vero e proprio tipo, sebbene dà luogo ad una corrispondente funzione tipo (§3.3.1).I costruttori di tipo sono un altro esempio, come si può scrivere tipo Swap[m[_, _], a,b] = m[b, a], ma non c'è la sintassi per scrivere il corrispondente anonimo tipo di funzione direttamente.

List è un costruttore di tipo.È possibile applicare il tipo di Int per ottenere un Tipo di Valore, List[Int], che può classificare un valore.Altro tipo di costruttori di prendere più di un parametro.

Il tratto scalaz.MA richiede che il primo tipo di parametro deve essere un tipo di costruttore che accetta un solo tipo di restituire un valore di tipo, con la sintassi trait MA[M[_], A] {}.Il parametro di tipo definizione che descrive la forma di tipo di costruttore, che è indicato come suo Genere. List si dice che il genere '* -> *.

L'Applicazione parziale di Tipi

Ma come si può MA avvolgere un valori di tipo Validation[X, Y]?Il tipo Validation è un tipo (* *) -> *, e potrebbe solo essere passato come argomento di tipo di un tipo di parametro dichiarato come M[_, _].

Questa conversione implicita nel oggetto Scalaz converte un valore di tipo Validation[X, Y] per un 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)
}

Che a sua volta utilizza un trucco con un alias del tipo in PartialApply1Of2 parzialmente applicare il costruttore di tipo Validation, fissando il tipo di errori, ma lasciando il tipo di successo non applicati.

PartialApply1Of2[Validation, E]#Apply sarebbe meglio scritto come [X] => Validation[E, X].Recentemente ho proposto di aggiungere una sintassi di Scala, potrebbe accadere in 2.9.

Pensate a questo come un tipo di livello equivalente a questo:

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)

Ciò consente di combinare Validation[String, Int] con un Validation[String, Boolean], perché entrambi condividono il costruttore di tipo [A] Validation[String, A].

Applicativi Functors

<**> esige il costruttore di tipo M deve avere le relative istanze di Applicare e Funtore.Questo costituisce un Applicativo Funtore, che, come una Monade, è un modo di strutturare un programma di calcolo attraverso un qualche effetto.In questo caso, l'effetto è che il sub-calcoli può fare a meno (e quando lo fanno, si accumulano i fallimenti).

Il contenitore Validation[NonEmptyList[String], A] in grado di avvolgere un puro valore di tipo A in questo 'effetto'.Il <**> operatore prende due effectful valori, e una pura funzione, e li combina con l'Applicativo Funtore istanza per il contenitore.

Ecco come funziona per il Option applicativi funtore.L'effetto " qui c'è la possibilità di errore.

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)

In entrambi i casi, vi è una pura funzione del tipo di (String, Int) => String, essere applicate ad effectful argomenti.Si noti che il risultato è avvolto da lo stesso effetto (o contenitore, se vi piace), gli argomenti.

È possibile utilizzare lo stesso modello attraverso una moltitudine di contenitori che hanno associato un Applicativo Funtore.Tutte le Monadi sono automaticamente Applicativi Functors, ma ci sono anche di più, come ZipStream.

Option e [A]Validation[X, A] sono entrambi Monadi, così si potrebbe anche usato Bind (aka flatMap):

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

Tupling con `<|**|>`

<|**|> è molto simile a quella di <**>, ma fornisce la funzione pura per voi semplicemente costruire un Tuple2 dai risultati. (_: A, _ B) è una scorciatoia per (a: A, b: B) => Tuple2(a, b)

E al di là

Ecco la nostra bundle esempi per Applicativi e Convalida.Ho usato una sintassi leggermente diversa per utilizzare l'Applicativo Funtore, (fa ⊛ fb ⊛ fc ⊛ fd) {(a, b, c, d) => .... }

AGGIORNAMENTO:Ma cosa succede in Caso di Fallimento?

ciò che sta accadendo al Tuple2/Coppia in caso di errore?

Se uno qualsiasi dei sub-calcoli di esito negativo, la funzione fornita non viene mai eseguita.Non solo viene eseguito se tutti i sub-calcoli (in questo caso, i due argomenti passati <**>) sono di successo.Se è così, si combina in un Success.Dov'è la logica?Questo definisce il Apply istanza per [A] Validation[X, A].Abbiamo bisogno che il tipo di X deve avere un Semigroup a disposizione, che è la strategia per la combinazione di errori individuali, di ogni tipo X, in un aggregato di errore dello stesso tipo.Se si sceglie String come il vostro tipo di errore, il Semigroup[String] concatena le stringhe;se si sceglie NonEmptyList[String], l'errore(s) per ogni fase sono concatenati in un più NonEmptyList di errori.Questa concatenazione succede quando due di seguito Failures sono combinati, utilizzando il operatore (che si espande con implicits, per esempio, 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)
  }
}

Monade o Applicativo, come posso scegliere?

Ancora leggendo?(Sì.Ed)

Ho dimostrato che il sub-calcoli basati su Option o [A] Validation[E, A] può essere combinato sia con Apply o con Bind.Quando si sceglie uno o l'altro?

Quando si utilizza Apply, la struttura di calcolo è stato risolto.Tutti i sub-calcoli saranno eseguiti;i risultati di uno non influenza le altre.Solo i "puri" che funzione ha una panoramica di ciò che è accaduto.Monadico calcoli, d'altra parte, consentire il primo sub-calcolo di influenzare quelle successive.

Se abbiamo utilizzato un Monadico convalida struttura, il primo guasto di corto circuito l'intero convalida, come non ci sarebbe Success valore alimentare la successiva validazione.Tuttavia, siamo contenti per il sub-convalide di essere indipendente, in modo che possiamo combinare, attraverso l'Applicativo, e raccogliere tutti gli errori che possiamo incontrare.La debolezza di Applicativi Functors è diventato un punto di forza!

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top