Task
(scalaz oder besser fs2) sollten alle Anforderungen erfüllen, ist es nicht Monade-Transformator benötigen, wie es schon ist hat Either
innen (Either
für fs2, \/
für scalaz). Es hat auch ein Fast-Fail-Verhalten, das Sie benötigen, genau wie rechtsbündige Disjunktion/Xor.
Hier sind mehrere Implementierungen sind, die mir bekannt sind:
Unabhängig von Monade-Transformator Abwesenheit, haben Sie noch ein bisschen Heben benötigen, wenn Task
mit:
- von Wert zu
Task
oder
- von
Either
zu Task
Aber ja, es scheint einfacher zu sein als Monadetransformatoren, vor allem im Hinblick darauf, dass Monaden kaum zusammensetzbar sind - um m zu definieren Onad Transformator müssen Sie einige andere Details über Ihren Typ neben einer Monade wissen (in der Regel erfordert es etwas wie comonad, um Wert zu extrahieren).
Nur für Werbezwecke, würde ich auch hinzufügen, dass Task
Stack-sichere Trampolin-Berechnung darstellt.
Allerdings gibt es einige Projekte konzentrierten sich auf erweiterte monadischen Zusammensetzung, wie Emm-Monade: https://github.com/djspiewak/emm, so können Sie Monade Transformatoren mit Future
/Task
, Either
, Option
, List
und so weiter und so fort komponieren. Aber, IMO, es ist immer noch im Vergleich zu Applicative
Zusammensetzung begrenzt - cats
bietet universellen Nested
Datentyp, mit dem leicht zu jeder Applicative komponieren können, finden Sie einige Beispiele - der einzige Nachteil hier ist, dass es schwer ist, eine lesbare DSL mit Applicative zu bauen. Eine andere Alternative ist die sogenannte "Freer-Monade": https://github.com/m50d/paperdoll, die im Grunde eine bessere Zusammensetzung bereitstellt und es ermöglicht, verschiedene Effektschichten in verschiedene Interpreter zu trennen.
Zum Beispiel, wie es kein FutureT
/ Transformator nicht Effekte wie type E = Option |: Task |: Base
(Option
von Task
) als solche flatMap
würde bauen können von den Future
/Task
Extraktion von Wert erfordern.
Als Fazit kann ich sagen, dass aus meiner Erfahrung Task
wirklich kommt für do-notation-basierte DSLs: Ich hatte eine komplexe externe Regel-DSL für Async-Berechnungen und als ich beschloss, alles auf Scala migrieren Embedded-Version Task
wirklich geholfen - ich konvertierte buchstäblich externen DSL zu Scala for-comprehension
. Eine andere Sache, die wir in Betracht gezogen haben, ist ein benutzerdefinierter Typ, wie ComputationRule
mit einer Reihe von definierten Typklassen und Konvertierungen zu Task
/Future
oder was auch immer wir brauchen, aber das war, weil wir Free
-monad nicht explizit verwendet haben.
Sie könnten sogar brauchen hier nicht Free
-monad vorausgesetzt, Sie Dolmetscher keine Fähigkeit benötigen zu wechseln (die für nur Systemtests wahr sein könnte). In diesem Fall Task
vielleicht das einzige, was Sie brauchen - es ist faul (im Vergleich mit Zukunft), wirklich funktionell und Stack-safe:
trait DSL {
def put[E](e: E): Task[Unit]
def count[E](e: E): Task[Int]
}
object Implementation1 extends DSL {
...implementation
}
object Implementation2 extends DSL {
...implementation
}
//System-test script:
def test0(dsl: DSL) = {
import dsl._
for {
_ <- put("Apple")
_ <- put("Orange")
_ <- put("Pinneaple")
nApples <- count("Apple")
nPears <- count("Pear")
nBananas <- count("Banana")
} yield List(("Apple", nApples), ("Pears", nPears), ("Bananas", nBananas))
}
So können Sie Implementierung wechseln, indem verschiedene „Dolmetscher“ hier:
test0(Implementation1).unsafeRun
test0(Implementation2).unsafeRun
Unterschiede/Nachteile (im Vergleich zu http://typelevel.org/cats/datatypes/freemonad.html):
- Sie mit
Task
Art stecken, so dass Sie es zu einigen nicht kollabieren o Ther Monade leicht.
Implementierung wird in der Laufzeit aufgelöst, wenn Sie eine Instanz von DSL-Eigenschaft übergeben (anstelle von natürlichen Transformation), können Sie es einfach abstrahieren mit Eta-Erweiterung: test0 _
. Polymorphe Methoden (put, count) werden natürlich von Java/Scala unterstützt, aber Poly-Funktionen sind nicht so einfach zu übergeben Instanz DSL
enthält T => Task[Unit]
(für put
Betrieb) als die synthetische polymorphe Funktion DSLEntry[T] => Task[Unit]
mit Natural-Transformation DSLEntry ~> Task
.
keine explizite AST als statt Muster innerhalb natürliche Transformation passend - wir verwenden statische Dispatch (explizit eine Methode aufrufen, die faul Berechnung zurück) innerhalb DSL Merkmal
Eigentlich kann man sogar loswerden hier von Task
:
trait DSL[F[_]] {
def put[E](e: E): F[Unit]
def count[E](e: E): F[Int]
}
def test0[M[_]: Monad](dsl: DSL[M]) = {...}
hier So könnte es auch eine Frage der Präferenz besonders, wenn Sie nicht eine Open-Source-Bibliothek zu schreiben.
es Putting alles zusammen:
import cats._
import cats.implicits._
trait DSL[F[_]] {
def put[E](e: E): F[Unit]
def count[E](e: E): F[Int]
}
def test0[M[_]: Monad](dsl: DSL[M]) = {
import dsl._
for {
_ <- put("Apple")
_ <- put("Orange")
_ <- put("Pinneaple")
nApples <- count("Apple")
nPears <- count("Pear")
nBananas <- count("Banana")
} yield List(("Apple", nApples), ("Pears", nPears), ("Bananas", nBananas))
}
object IdDsl extends DSL[Id] {
def put[E](e: E) =()
def count[E](e: E) = 5
}
Beachten Sie, dass Katzen für Id
eine Monad
definiert haben, so:
scala> test0(IdDsl)
res2: cats.Id[List[(String, Int)]] = List((Apple,5), (Pears,5), (Bananas,5))
einfach funktioniert. Natürlich können Sie Task
/Future
/Option
oder jede Kombination wählen, wenn Sie bevorzugen. Wie in der Tat, können Sie Applicative
statt Monad
verwenden:
def test0[F[_]: Applicative](dsl: DSL[F]) =
dsl.count("Apple") |@| dsl.count("Pinapple apple pen") map {_ + _ }
scala> test0(IdDsl)
res8: cats.Id[Int] = 10
|@|
ein paralleler Operator ist, so dass Sie cats.Validated
statt Xor
verwenden können, bewusst sein, dass |@|
für Aufgabe nicht ausgeführt wird (zumindest in ältere Scalaz-Version) parallel (Paralleloperator nicht gleich Parallelberechnung). Sie können auch eine Kombination aus beidem:
import cats.syntax._
def test0[M[_]:Monad](d: DSL[M]) = {
for {
_ <- d.put("Apple")
_ <- d.put("Orange")
_ <- d.put("Pinneaple")
sum <- d.count("Apple") |@| d.count("Pear") |@| d.count("Banana") map {_ + _ + _}
} yield sum
}
scala> test0(IdDsl)
res18: cats.Id[Int] = 15
'Task' (scalaz oder besser fs2) alle Anforderungen erfüllen sollte, es Monade-Transformator nicht braucht, da es bereits Entweder innen (entweder für fs2, \/für Scalaz). Es hat auch Fast-Fail-Verhalten, das Sie benötigen, genau wie rechtsgerichtete Disjunktion/Xor. – dk14
Ich wusste nicht über 'Task', nett. Dieser Ansatz scheint auch darauf hinzuweisen, wie Menschen in der scala-Welt Monaden bilden, die 'lift'-Operatoren von Haskell vergessen, eigene Klassen mit allen Aspekten, die Sie benötigen (z. B. Parallelität und Fehlerbehandlung) definieren und eine Monade definieren Beispiel dafür. –
Sie müssen noch etwas heben, wenn Sie 'Task' verwenden, von Wert zu 'Aufgabe' oder von 'Entweder' zu' Aufgabe' heben. Aber ja, es scheint einfacher zu sein als Monadetransformatoren, insbesondere in Bezug auf die Tatsache, dass Monaden kaum zusammensetzbar sind (um Monadtransformatoren zu definieren, müssen Sie einige andere Details über Ihren Typ wissen, abgesehen davon, dass sie eine Monade sind - normalerweise erfordert sie etwas wie comonad um den Wert zu extrahieren). Nur für Werbezwecke würde ich hinzufügen, dass "Task" eine stapel-sichere, trampoline Berechnung darstellt. Es gibt jedoch einige Projekte, die sich auf monadische Kompositionen wie "Emm-monad" konzentrieren – dk14