Task
Either
(scalaz またはより良い fs2) は、すべての要件を満たす必要があります。既に内部にあるため、monad -transformer は必要ありません( Either
fs2 の場合\/
、scalaz の場合)。また、右バイアス分離/xor と同じように、必要なファスト フェイル動作も備えています。
私が知っているいくつかの実装を次に示します。
モナドトランスフォーマーの不在に関係なく、使用するときはまだ持ち上げる必要がありますTask
:
- 値から
Task
または
- から
Either
までTask
しかし、はい、特にモナドはほとんど構成可能ではないという事実に関しては、モナド変換子よりも単純であるように思われます-モナド変換子を定義するには、モナドであることに加えて、型に関する他の詳細を知る必要があります(通常、それには comonad のようなものが必要です)値を抽出します)。
宣伝目的で、Task
スタックセーフなトランポリン計算を表す も追加します。
ただし、Emm-monad: https://github.com/djspiewak/emmなど、拡張されたモナド構成に焦点を当てたプロジェクトがいくつかあるため、 Future
/ Task
、Either
、Option
などでモナド変換子を構成できますList
。しかし、IMO、それは構成と比較してまだ制限されてApplicative
います-Applicativeを簡単に構成できるcats
ユニバーサルデータ型を提供します。この回答でNested
いくつかの例を見つけることができます-ここでの唯一の欠点は、Applicativeを使用して読み取り可能なDSLを構築するのが難しいことです. 別の代替手段は、いわゆる「Freer モナド」です: https://github.com/m50d/paperdoll、これは基本的により良い構成を提供し、異なるエフェクトレイヤーを異なるインタープリターに分離することを可能にします.
たとえば、FutureT
/トランスフォーマーがないため、 ( from ) のTaskT
ような効果を構築することはできません。これは、 /から値を抽出する必要があるためです。type E = Option |: Task |: Base
Option
Task
flatMap
Future
Task
結論として、私の経験から言えば、Task
do 記法ベースの DSL に本当に適していると言えます。非同期計算用の複雑な外部ルールのような DSL があり、それをすべて Scala 組み込みバージョンに移行することにしたとき、Task
本当に役に立ちました -私は文字通り external-DSL を Scala の に変換しましfor-comprehension
た。私たちが検討したもう 1 つのことは、/または必要なものComputationRule
への変換とともに定義された型クラスのセットなど、いくつかのカスタム型を持つことですが、これは-monadを明示的に使用しなかったためです。Task
Future
Free
Free
インタープリターを切り替える機能が必要ないと仮定すると、ここでは -monadさえ必要ないかもしれません(これはシステムテストだけに当てはまるかもしれません)。その場合Task
、あなたが必要とする唯一のものかもしれません - それは(Futureと比較して)怠惰で、真に機能的でスタックセーフです:
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))
}
したがって、ここで別の「インタープリター」を渡すことで実装を切り替えることができます。
test0(Implementation1).unsafeRun
test0(Implementation2).unsafeRun
相違点/欠点 ( http://typelevel.org/cats/datatypes/freemonad.htmlとの比較):
- 型に固執して
Task
いるため、他のモナドに簡単に折りたたむことはできません。
実装は、(自然な変換ではなく) DSL-trait のインスタンスを渡すと実行時に解決されるため、 eta-expansion: を使用して簡単に抽象化できますtest0 _
。ポリモーフィック メソッド (put、count) は Java/Scala で自然にサポートされていますが、ポリ関数はサポートされていないため、natural-transform を使用して合成ポリモーフィック関数を作成するよりも(操作のために) コンテナーのインスタンスDSL
を 渡す方が簡単です。T => Task[Unit]
put
DSLEntry[T] => Task[Unit]
DSLEntry ~> Task
自然な変換内のパターン マッチングの代わりに明示的な AST はありません - DSL トレイト内で静的ディスパッチ (遅延計算を返すメソッドを明示的に呼び出す) を使用します。
実際、ここでも取り除くことができ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]) = {...}
したがって、特にオープンソース ライブラリを作成していない場合は、好みの問題になることさえあります。
すべてを一緒に入れて:
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
}
ネコにはMonad
for が定義されていることに注意してくださいId
。
scala> test0(IdDsl)
res2: cats.Id[List[(String, Int)]] = List((Apple,5), (Pears,5), (Bananas,5))
単に動作します。もちろん、必要に応じてTask
/ Future
/Option
または任意の組み合わせを選択できます。実際のところ、次Applicative
の代わりに使用できMonad
ます。
def test0[F[_]: Applicative](dsl: DSL[F]) =
dsl.count("Apple") |@| dsl.count("Pinapple apple pen") map {_ + _ }
scala> test0(IdDsl)
res8: cats.Id[Int] = 10
|@|
cats.Validated
は並列演算子なので、の代わりに使用できますが、 for Task は (少なくとも古いバージョンの scalaz では) 並列で実行されないXor
ことに注意してください (並列演算子は並列計算と等しくありません)。|@|
両方を組み合わせて使用することもできます。
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