2016-11-18 76 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

最后一句话.... ;-)谢谢! –