2015-07-10 61 views
1

我試圖在F#中將我的頭包裹在mon-,err,工作流中,同時我認爲我對基本的「Maybe」工作流有着非常紮實的理解,試圖實現一個狀態工作流程生成隨機數真的讓我難住。F#中的隨機/狀態工作流程#

我未完成的嘗試可以在這裏看到:

let randomInt state = 
    let random = System.Random(state) 
    // Generate random number and a new state as well 
    random.Next(0,1000), random.Next() 

type RandomWF (initState) = 
    member this.Bind(rnd,rest) = 
     let value, newState = rnd initState 
     // How to feed "newState" into "rest"?? 
     value |> rest 
    member this.Return a = a // Should I maybe feed "initState" into the computation here? 

RandomWF(0) { 
    let! a = randomInt 
    let! b = randomInt 
    let! c = randomInt 
    return [a; b; c] 
} |> printfn "%A" 

編輯:其實得到它的工作!不完全確定它是如何工作的,所以如果任何人想把它放在一個很好的答案中,它仍然值得爭取。這是我的工作代碼:

type RandomWF (initState) = 
    member this.Bind(rnd,rest) = 
     fun state -> 
      let value, nextState = rnd state 
      rest value nextState 

    member this.Return a = fun _ -> a 

    member this.Run x = x initState 
+0

你的問題是什麼? –

+0

如何解決它?或者一般來說應該如何構建這樣的monad /工作流程。 – monoceres

+1

這是一個很好的描述如何狀態monad的作品。它有點冗長(3部分),但值得一讀。也許它會幫助http://fsharpforfunandprofit.com/posts/monadster/ – Petr

回答

4

有兩件事情,使人們很難看到你的工作流程是這樣做的:

  1. 您使用的函數類型的單子的類型,
  2. 您的工作流程不僅可以建立計算,還可以運行它。

我認爲一旦你看到它沒有這兩個障礙就會看起來更清晰。下面是一個使用DU包裝類型定義的工作流:

type Random<'a> = 
    Comp of (int -> 'a * int) 

let run init (Comp f) = f init 

type Random<'a> with 
    member this.Run(state) = fst <| run state this 

type RandomBuilder() = 
    member this.Bind(Comp m, f: 'a -> Random<_>) = 
     Comp <| fun state -> 
      let value, nextState = m state 
      let comp = f value 
      run nextState comp    

    member this.Return(a) = Comp (fun s -> a, s) 

let random = RandomBuilder() 

這裏是你如何使用它:

let randomInt = 
    Comp <| fun state -> 
     let rnd = System.Random(state) 
     rnd.Next(0,1000), rnd.Next() 

let rand = 
    random { 
     let! a = randomInt 
     let! b = randomInt 
     let! c = randomInt 
     return [a; b; c ] 
    } 

rand.Run(0) 
|> printfn "%A" 

在這個版本中,你分別建立計算(並將其存儲在隨機型內) ,然後你運行它在初始狀態傳遞。看看如何推斷構建器方法上的類型,並將它們與MSDN documentation描述的內容進行比較。

編輯:一旦構建生成器對象和使用作爲排序的別名的結合主要是慣例,但它也有理由認爲是有意義的建設者是無狀態的。我可以看到爲什麼參數化構建器看起來像一個有用的功能,但我不能誠實地想象一個令人信服的用例。

單子的關鍵賣點是定義和計算的執行的分離。

在你的情況下 - 你想要做的就是獲得你的計算的表示並能夠以某種狀態運行它 - 也許是0,也許是42.你不需要知道初始狀態定義將使用它的計算。通過將狀態傳遞給構建器,您最終會模糊定義和執行之間的界限,這隻會使工作流程變得不那麼實用。

將工作流程與async進行比較 - 在​​編寫異步塊時,不要使代碼異步運行。您只創建一個Async<'a>對象,表示在運行時會生成'a的對象的計算 - 但您如何執行此操作取決於您。建造者不需要知道。

+0

最新回覆,但非常感謝!我終於開始理解這個東西,寫出綁定調用嵌套確實有幫助。圍繞工作流程獲得良好界面的機制也非常有用。有一個問題,似乎有一個約定不直接使用構建器類型,而是創建一個對象並使用它。任何具體原因?我認爲直接使用這種類型會給你一個有用的機會來提供例如初始狀態(就像我做的那樣)。 – monoceres

+0

@monoceres:我在編輯中給了你一個回覆(因爲它變得很長)。 – scrwtp