2016-11-18 19 views
3

Не совсем уверен, что название описывает это хорошо, но у меня есть примерно следующий код:Расчетные выражения против аппликативных функторов и то, что не

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, но использовал операторов (аппликаторы?), Но я в настоящий момент не могу повернуть голову, чтобы справиться с этим.

Приведенный выше код компилируется, но подписи для 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, но я более или менее всегда заканчивается на той же подписью, как указано выше.

Общая идея, которую я изначально имела, заключалась в том, чтобы написать небольшую функцию, делающую то, что им нужно, а затем добавить обработку исключений (защищать), а затем «обернуть» и «развернуть» соответственно.

Это, конечно, не совсем правильная идея.

Могут ли кто-нибудь указать мне несколько направлений здесь для дальнейших исследований, примеров кода или чего-нибудь еще? Любые комментарии любого типа на самом деле прием в этот момент ;-)

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

BTW, в F # '' часто используется вместо Haskell '<$>', который не является юридическим оператором в F #. Это просто инфиксная версия 'map' (' fmap' в Haskell). –

+0

@MarkSeemann хе-хе. это на самом деле MCVE. или то есть: нет необходимости возиться с монго здесь ;-) вышеупомянутый бег без монгодба, установленного или любого возиться вообще, если пакеты на месте. но я постараюсь сделать еще кое-что в кости 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>.

Обратите внимание, что bind очень похож на map, за исключением того, что последний возвращает результат в Choice1Of2 - отображаемая функция всегда успешна.

В FSharpX, вы можете получить доступ к bind по |> -как оператора >>= или оператора <<=<| -like.

Наконец, protect - это причудливый способ поймать выброшенное исключение в Choice2Of2 exn. Он аналогичен map тем, что переданная функция имеет тип 'T -> 'U, но функция также может генерировать исключение, а переданный тип - не a Choice. protect определяется что-то вроде этого:

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

Это последнее предложение .... ;-) Спасибо! –