2016-11-18 57 views
3

不能完全確定標題所描述的那樣好,但我也有關於下面的代碼:計算表達式VS合用的函子,什麼不能

paket.dependencies:

source https://www.nuget.org/api/v2 
nuget fsharpx.extras 
nuget mongodb.driver 

some.fsx:

#r @".\packages\MongoDB.Bson\lib\net45\MongoDB.Bson.dll" 
#r @".\packages\MongoDB.Driver\lib\net45\MongoDB.Driver.dll" 
#r @".\packages\MongoDB.Driver.Core\lib\net45\MongoDB.Driver.Core.dll" 

#r @".\packages\FSharpX.Extras\lib\net45\FSharpX.Extras.dll" 


open MongoDB 
open MongoDB.Driver 
open MongoDB.Bson 
open MongoDB.Bson.Serialization 

open FSharpx.Choice 

let private createClient (connectString:string) = MongoClient(connectString) 
let CreateClient = protect createClient 

let private getDb name (client:IMongoClient) = client.GetDatabase(name) 
let GetDB1 name client = 
    choose { 
     let! c = client 
     return! (protect (getDb name) c) 
    } 

let GetDB2 name (client:Choice<IMongoClient, exn>) = 
    protect (getDb name) 
    <!> client 

這個「excersise」的要點是編寫GetDB2,使其與GetDB1一樣,但使用運算符(applicatives?),但我目前無法轉動頭來管理它。

上面的代碼編譯,但對於 GetDB1和GetDB2簽名是不相等的,和Im顯然做的事情權。

val GetDB1 : 
    name:string -> 
    client:Choice<#MongoDB.Driver.IMongoClient,exn> -> 
     Choice<MongoDB.Driver.IMongoDatabase,exn> 

val GetDB2 : 
    name:string -> 
    client:Choice<MongoDB.Driver.IMongoClient,exn> -> 
     Choice<Choice<MongoDB.Driver.IMongoDatabase,exn>,exn> 

我嘗試了好幾種版本,並在GetDB2做事的訂單,但我或多或少總是在相同的簽名上方結束。

我最初的想法是編寫小函數來完成他們應該做的事情,然後添加異常處理(protect),然後相應地「包裝」和「解包」。

這可能當然不完全正確的想法了。

是否有人能指出我在某些方向上這裏繼續深造,代碼示例或什麼?任何類型的任何意見,其實都是歡迎在這一點;-)

FSharpx doc

附錄

我認爲以下應約與上面相同,但是沒有MongoDB的依賴關係。

#r @".\packages\FSharpX.Extras\lib\net45\FSharpX.Extras.dll" 

type DataBase = 
    { 
     Name: string 
    } 

type Client = 
    { 
     connectString: string 
    } with member this.GetDatabase name = { 
         Name = name 
        } 

open FSharpx.Choice 
let private createClient (connectString:string) = { 
    connectString= connectString 
} 

let CreateClient = protect createClient 

let private getDb name (client:Client) = client.GetDatabase name 

let GetDB1 name client = 
    choose { 
     let! c = client 
     return! (protect (getDb name) c) 
    } 

let GetDB2 name client = 
    protect (getDb name) 
    <!> client 
+0

你可能會表示這是[MCVE](http://stackoverflow.com/help/mcve)?我不喜歡用MongoDB來研究這個問題...... –

+1

順便說一句,在F#中''經常用來代替Haskell的'<$>',它不是F#中的合法運算符。它僅僅是'map'的中綴版本(Haskell中的'fmap')。 –

+0

@MarkSeemann hehe。它實際上是MCVE。或者就是:不需要在這裏擺弄mongos ;-)上面的運行沒有安裝任何mongodb,或者根本沒有任何的擺弄,如果這些包裝已經就位。但我會嘗試做一些更多的骨MCVE ... –

回答

5

如果您聽到這裏種的配合,因爲你已經使用了<!>運營商,這是map。被定義是這樣的:

let map f = function 
    | Choice1Of2 value = Choice1Of2 (f value) 
    | Choice2Of2 fail = Choice2Of2 fail 

這具有的簽名('T -> 'U) -> Choice<'T,'Failure> -> Choice<'U,'Failure>,即函數f用作地圖內部choice類型。例如:

map (sprintf "%d") 

有型號Choice<int, 'Failure> -> Choice<string, 'Failure>。這適用於不使用Choice類型的函數 - 只有一個可能的失敗點,並且在調用map之前發生。

但是,您的下一個函數會生成Choice類型,但它的類型不是Choice。這意味着你想要傳播的錯誤 - 如果有錯誤的價值,然後選擇。如果這個值很好,但是函數有錯誤,那就使用它。如果一切順利,請使用它。這要求兩種錯誤類型相同,對你而言(exn)。

這是描述bind操作,這樣定義:

let bind f = function 
    | Choice1Of2 value = f value 
    | Choice2Of2 fail = Choice2Of2 fail 

與簽名('T -> Choice<'U,'Failure>) -> Choice<'T,'Failure> -> Choice<'U,'Failure>

請注意,bindmap非常相似,只是後者將結果提升爲Choice1Of2 - 映射的函數始終成功。

在FSharpX,您可以通過|>式的操作>>=,或<|式的操作<<=訪問bind

最後,protect是將拋出的異常捕獲到Choice2Of2 exn中的一種奇特方式。它類似於map,因爲傳遞函數的類型爲'T -> 'U,但該函數也可以拋出異常,並且傳遞的類型爲而不是 a Choiceprotect的定義是這樣的:

let protect f x = 
    try 
     Choice1Of2 (f x) 
    with 
     exn -> Choice2Of2 exn 

所以它的簽名是('T -> 'U) -> 'T -> Choice<'U, exn>

有關如何實現所有功能的更多信息,請參閱the source of this computation expression


綜合起來,我們可以看到爲什麼你的例子出錯了。

  • getDb name是一個功能Client -> DataBase
  • protect (getDb name)是一個函數,因此Client -> Choice<DataBase, exn>
  • map (protect (getDb name))是一個功能Choice<Client, exn> -> Choice<Choice<DataBase, exn>, 'Failure>,因爲map作品裏面Choice

你想要什麼,雖然是

let GetDB name client = 
    bind (protect (getDb name)) client 

或運營商的形式,

let GetDB name client = client >>= protect (getDb name) 

一般來說,如果你的映射函數簽名'T -> 'U你想map。如果它有'T -> Choice<'U, 'Failure>,則需要bind

+0

最後一句話.... ;-)謝謝! –