簡體   English   中英

為什么以及如何繼續Monad解決回調地獄? 換句話說:RX或FRP = Continuation Monad? 如果不=,有什么區別?

[英]Why and how the Continuation Monad solves the callback hell? In other words : Is RX or FRP = Continuation Monad ? IF not =, what is the difference?

這里這里據說Continuation Monad解決了回調地獄。

RX和FRP也解決了Callback地獄。

如果所有這三個工具都能解決回調問題,那么就會產生以下問題:

在Erik的視頻中,據說RX = Continuation Monad。 這是真的嗎? 如果是,你能展示映射嗎?

如果RX不= =續。 Monad那么RX和Continuation Monad有什么區別?

同樣,FRP和Continuation Monad有什么區別?

換句話說,假設讀者知道什么是FRP或RX,讀者如何才能輕松理解Continuation Monad是什么?

通過將它與RX或FRP進行比較,是否可能/易於理解Continuation Monad是什么?

我不熟悉RX,但對於FRP和延續monad,它們是根本不同的概念。

功能反應式編程是解決與時間相關的值和事件的問題的解決方案。 對於事件,您可以通過對計算進行排序來解決回調問題,這樣一旦完成,就會發送一個事件並觸發下一個事件。 但是回調你真的不關心時間,你只想以特定的方式對計算進行排序,所以除非你的整個程序都是基於FRP,否則它不是最佳解決方案。

延續monad與時間完全沒有關系。 這是一個抽象的計算概念,可以(松散地說)獲取當前評估序列的“快照”,並使用它們“任意”“跳轉”到這些快照。 這允許創建復雜的控制結構,例如循環和協同程序 現在來看看continuation monad的定義:

newtype Cont r a = Cont { runCont :: (a -> r) -> r}

這本質上是一個回調函數! 這是一個接受的延續(回調)后,將繼續計算函數a生產。 所以如果你有幾個功能可以延續,

f1 :: (Int -> r) -> r

f2 :: Int -> (Char -> c) -> c

f3 :: Char -> (String -> d) -> d

他們的構圖變得有點凌亂:

comp :: String
comp = f1 (\a -> f2 a (\b -> f3 b id))

使用continuation,它變得非常簡單,我們只需要將它們包裝在cont ,然后使用monadic bind operaiton >>=對它們進行排序:

import Control.Monad.Cont

comp' :: String
comp' = runCont (cont f1 >>= cont . f2 >>= cont . f3) id

因此,continuation是回調問題的直接解決方案。

如果你看看在Haskell中如何定義continuation monad,它看起來像這樣:

data Cont r a =  Cont { runCont :: (a -> r) -> r }

就其本身而言,這是完全純粹的,並不代表現實世界的影響或時間。 即使在當前形式下,它可用於表示時間/ IO效果,只需選擇r作為涉及IO的類型即可。 然而,出於我們的目的,我們將做一些稍微不同的事情。 我們將使用類型參數替換具體的->

data Cont p r a = Cont { runCont :: p (p a r) r }

有什么用? 在Haskell中,我們只有將一些輸入域映射到輸出域的純函數。 在其它語言中,我們可以有這樣的功能,但是我們可以定義不純“功能”,它(除了生產對於給定輸入一些任意的輸出),可以隱式地執行的副作用。

以下是JS中的兩個示例:

// :: Int -> Int -> Int
const add = x => y => x + y

// :: String -!-> ()
const log = msg => { console.log(msg); }

請注意, log不是一個產生表示效果的值的純函數,這就是如Haskell這樣的純語言編碼的方式。 相反,效果與僅僅調用log 為了捕獲這個,我們可以在談論純函數和不純的“函數”(分別是->-!-> )時使用不同的箭頭。

所以,回到關於延續monad如何解決回調地獄的問題,事實證明(至少在JavaScript中),產生回調地獄的大多數API都可以很容易地按照形式Cont (-!->) () a值進行按摩Cont (-!->) () a ,我將在此稱之為Cont! a Cont! a

一旦你有一個monadic API,其余的很容易; 你可以將充滿連續的結構遍歷到結構的延續中,使用do notation編寫類似於async/await多步計算,使用monad變換器為continuation配備額外的行為(例如錯誤處理)等。

monad實例與Haskell中的實例看起來非常相似:

// :: type Cont p r a = p (p a r) r
// :: type Cont! = Cont (-!->) ()

// :: type Monad m = { pure: x -> m x, bind: (a -> m b) -> m a -> m b }

// :: Monad Cont!
const Cont = (() => {
  // :: x -> Cont! x
  const pure = x => cb => cb(x)
  // :: (a -> Cont! b) -> Cont! a -> Cont! b
  const bind = amb => ma => cb => ma(a => amb(a)(cb))

  return { pure, bind }
})()

以下是將Node JS中可用的setTimeoutreadFile API建模為不純的延續的幾個示例:

// :: FilePath -> Cont! (Either ReadFileError Buffer)
const readFile = path => cb => fs.readFile(path, (e, b) => cb(e ? Left(e) : Right(b)))

// :: Int -> v -> Cont! v
const setTimeout = delay => v => cb => setTimeout(() => cb(v), delay)

作為一個人為的例子,這里是我們使用標准API讀取文件時輸入的“回調地獄”,等待五秒鍾,然后讀取另一個文件:

fs.readFile("foo.txt", (e, b1) => {
  if (e) { throw e }
  setTimeout(() => {
    fs.readFile("bar.txt", (e, b2) => {
      if (e) { throw e }
      console.log(b1.toString("utf8") + b2.toString("utf8"))
    })
  }, 5000)
})

這是使用continuation monad的等效程序:

const ECont = EitherT(Cont)

const decode = buf => buf.toString("utf8")

const foo = readFile("foo.txt") |> ECont.map(decode)
const bar = readFile("bar.txt") |> ECont.map(decode)

// Imaginary do notation for JS for purposes of clarity
const result = do(ECont)([
  [s1, foo],
  ECont.lift(delay_(5000)),
  [s2, bar],
  ECont.pure(s1 + s2)
])

const panic = e => { throw e }
const log = v => { console.log(v) }

// No side effects actually happen until the next line is invoked
result(Either.match({ Left: panic, Right: log }))

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM