3

Not entirely sure the title describes it ok, but I do have about the following code:

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

The point for this "excersise" was to write GetDB2 so that it does the same as GetDB1 but use operators (applicatives?), but I am at the moment not able to turn my head to manage this.

The above code compiles, but the signatures for GetDB1 and GetDB2 are not equal, and Im obviously doing something not right.

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>

I have tried several versions and orders of doing things in GetDB2 but I more or less always end at same signature as above.

The general idea I initially had was to write small function doing the stuff they should and then add exception handling (protect) and then "wrap" and "unwrap" accordingly.

That might of course not be entirely right idea too.

Are someone able to point me in some directions here for further studies, code examples or anything? Any comments of any type are in fact welcome at this point ;-)

FSharpx doc

Addendum

I think the following should be about same as above, but without the mongodb dependencies.

#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
Helge Rene Urholm
  • 1,190
  • 6
  • 16
  • Could you, perhaps, express this as an [MCVE](http://stackoverflow.com/help/mcve)? I don't feel like fiddling with MongoDB in order to research this question... – Mark Seemann Nov 18 '16 at 16:48
  • 1
    BTW, in F# `<!>` is often used in place of Haskell's `<$>`, which isn't a legal operator in F#. It's simply an infix version of `map` (`fmap` in Haskell). – Mark Seemann Nov 18 '16 at 16:49
  • @MarkSeemann hehe. it is in fact MCVE. or that is: no need to fiddle with the mongos here ;-) the above run without any mongodb installed or any fiddling at all, if the pakets are in place. but I will try to do some more to the bone MCVE... – Helge Rene Urholm Nov 18 '16 at 16:50
  • @MarkSeemann yes, i do have that understanding of <!>, the applicative is then more the <*> (I think). I did include the link to the doc of FSharpX for this reason... – Helge Rene Urholm Nov 18 '16 at 16:52
  • In Haskell, `Applicative` is defined by `<*>` and `pure`. AFAICT, in FSharpx.Choice, these are `<*>` and `returnM`. – Mark Seemann Nov 18 '16 at 16:52
  • FWIW, our last comments crossed (I didn't see yours when I wrote mine). Not trying to preach to you, or anything... – Mark Seemann Nov 18 '16 at 16:54
  • 2
    I think you want something like `(=<<)` instead of `(<!>)`. – kvb Nov 18 '16 at 16:55
  • @kvb yes i want something like =<< or >>= but I do not manage to make it work as i want still... – Helge Rene Urholm Nov 18 '16 at 17:03
  • @MarkSeemann hehe not my intention to make it seem like your preaching either. as I said initially, at this moment, Im very very happy anyone bothers to write any comments here. ;-) – Helge Rene Urholm Nov 18 '16 at 17:04
  • @kvb and some more testing I managed it apperently. The MCVE of MarkSeeman did also possibly help me... client >>= protect (getDb name) seems to do the trick.... should I delete this question? Or does anyone want to actually answer it? Does it have any value this question? Anyhow thanks both! – Helge Rene Urholm Nov 18 '16 at 17:09
  • You could answer it yourself :) – Mark Seemann Nov 18 '16 at 17:14
  • @MarkSeemann yes i could. with the "exact" code giving me the signature i wanted. the answer i really really want is probably more like a explanation of why (>>=) was right here, a general idea/code review, and evaluation of best practice and what not ;-) so somewhat to broad possibly... but it was not me answering it so do not want to "steal" it either... thanks again! – Helge Rene Urholm Nov 18 '16 at 17:16
  • 2
    I see. I don't know if I can give a useful explanation, but `>>=` is 'just' the standard *bind* operator that, together with `return`/`pure`, defines a monad. In F# computation expressions, [`let!` bindings are translated to `Bind`](https://learn.microsoft.com/en-us/dotnet/articles/fsharp/language-reference/computation-expressions). – Mark Seemann Nov 18 '16 at 17:26
  • @MarkSeemann is it? same as bind? there is a difference in this fishy kleisli composition and an "ordinary" bind. i think. but the comments here are not for this type of discussions i guess ;-) – Helge Rene Urholm Nov 18 '16 at 17:51
  • @MarkSeemann u are right and Im wrong. which kind of also set of all this initially. question and comments and all... – Helge Rene Urholm Nov 18 '16 at 17:59

1 Answers1

6

You are getting the compounding of types here because you have used the <!> operator, which is map. That is defined something like this:

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

This has the signature ('T -> 'U) -> Choice<'T,'Failure> -> Choice<'U,'Failure>, i.e. the function f is used as a map inside the choice type. For example:

map (sprintf "%d")

has type Choice<int, 'Failure> -> Choice<string, 'Failure>. This is good for applying functions which do not use the Choice type - there is only one possible point of failure, and that happened before the call to map.

Your next function, however, produces a Choice type, but it takes a non-Choice type. This means that you want the errors to propagate through - if there is an error in the value, then choose that. If the value is fine, but there's an error in the function, then use that. If everything is successful, use that. This requires the two error types to be the same, which for you they are (exn).

This is describing the bind operation, defined like this:

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

with signature ('T -> Choice<'U,'Failure>) -> Choice<'T,'Failure> -> Choice<'U,'Failure>.

Note that bind is very similar to map, except that the latter raises the result into a Choice1Of2 - the mapped function is always successful.

In FSharpX, you can access bind by the |>-like operator >>=, or the <|-like operator <<=.

Finally, protect is a fancy way of catching a thrown exception into a Choice2Of2 exn. It is similar to map in that the passed function is of type 'T -> 'U, but the function can also throw an exception and the passed type is not a Choice. protect is defined something like this:

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

so its signature is ('T -> 'U) -> 'T -> Choice<'U, exn>.

For more information on how everything is implemented, see the source of this computation expression.


Putting this all together, we can see why your example went wrong.

  • getDb name is a function Client -> DataBase
  • protect (getDb name) is a function Client -> Choice<DataBase, exn>
  • map (protect (getDb name)) is therefore a function Choice<Client, exn> -> Choice<Choice<DataBase, exn>, 'Failure>, because map works inside Choice.

What you want, though, is

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

or in operator form,

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

In general, if your mapping function has signature 'T -> 'U you want map. If it has 'T -> Choice<'U, 'Failure>, you want bind.

Jake Lishman
  • 907
  • 7
  • 18