Task
(scalaz或更好FS2)應滿足所有的要求,它不需要單子變壓器,因爲它是已經Either
內(Either
對於FS2,\/
爲scalaz)。它也具有您需要的快速失敗行爲,與右偏分離/異或相同。
以下是已知會我幾個實現:
不管單子變壓器沒有,你還在使用Task
時還挺需要提升:
但是,是的,它似乎比單子變壓器更簡單,尤其是單子幾乎不可組合的事實 - 爲了定義m onad變壓器,你必須知道一些關於你的類型的其他細節,除了是一個單子(通常它需要像comonad提取價值)。
僅用於廣告目的,我還會補充說Task
表示堆棧安全的蹦牀計算。
不過,也有一些項目專注於擴展單子組成,像EMM-單子:https://github.com/djspiewak/emm,這樣你就可以撰寫單子變壓器與Future
/Task
,Either
,Option
,List
等等等等。但是,國際海事組織與Applicative
相比仍然有限 - cats
提供了通用的Nested
數據類型,可以輕鬆組成任何應用程序,您可以找到一些示例in this answer - 這裏唯一的缺點是使用Applicative很難構建可讀的DSL。另一種選擇是所謂的「自由單體」:https://github.com/m50d/paperdoll,它基本上提供了更好的構圖,並且允許將不同的效果層分成不同的解釋器。
例如,如沒有FutureT
/變壓器你不能建立像type E = Option |: Task |: Base
(Option
從Task
)的效果,例如flatMap
將需要從Future
/Task
的值提取。
作爲一個結論,我可以說從我的經驗Task
真的出現在基於do-notation的DSL中:我有一個複雜的外部規則 - 如異步計算的DSL,當我決定將它全部遷移到Scala-嵌入式版本Task
真的幫助 - 我從字面上將外部DSL轉換爲Scala的for-comprehension
。我們考慮的另一件事是自定義類型,例如ComputationRule
,其中定義了一組類型類以及Task
/Future
或任何我們需要的轉換,但這是因爲我們沒有明確使用Free
-monad。
你可能甚至不需要在這裏Free
-monad假設你不需要切換口譯的能力(可能爲只是系統測試是真實的)。在這種情況下Task
可能是你唯一需要的東西 - 這是懶惰的(與未來比較),真正做到了功能和堆棧安全:
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
類型,所以你不能把它崩來點它很容易monad。
實現在運行時通過DSL特性的實例(而不是自然轉換)解決,您可以使用eta-expansion:test0 _
輕鬆進行抽象。 Java/Scala自然支持多態方法(put,count),但poly函數並不是那麼容易通過包含T => Task[Unit]
(對於put
操作)的DSL
實例,而不是使用自然變換DSLEntry ~> Task
來生成合成多態函數DSLEntry[T] => Task[Unit]
。
沒有明確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
爲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
代替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
'Task'(scalaz或更好FS2)應滿足所有的要求,它不需要單子變壓器,因爲它是已經有了要麼內(無論是FS2, \/for scalaz)。它也具有你需要的快速失敗行爲,與正確的偏向/異或行相同。 – dk14
我不知道「Task」,很好。這種方法似乎也暗示了人們如何在Scala世界中構建monad,忘記Haskell中的'lift'運算符,定義您需要混合的所有方面(例如併發和錯誤處理)的自己的類,並定義一個monad實例。 –
當使用'Task'時,你仍然需要解除,從值提升到Task,或者從Either到Task。但是,是的,它似乎比monad變換器更簡單,尤其是在monad幾乎不可組合的情況下(爲了定義monad變換器,除了作爲monad外,你還需要知道關於你的類型的一些其他細節 - 通常它需要像comonad提取價值)。僅僅爲了廣告的目的,我還要補充一點,'Task'表示堆棧安全的蹦牀計算。但是有一些項目着重於一元組合,比如'Emm-monad' – dk14