1

I need my state to be passed along while being able to chain functions with the maybe workflow. Is there a way for 2 workflows to share the same context? If no, what is the way of doing it?

UPDATE:

Well, I have a state that represents a segment of available ID's for the entities that I am going to create in the database. So once an ID is acquired the state has to be transformed to a newer state with the next available ID and thrown away so that nobody can use it again. I don't want to mutate the state for the sake of being idiomatic. The State monad looks like a way to go as it hides the transformation and passes the state along. Once the state workflow is in place I cannot use the Maybe workflow which is something I use everywhere.

Gus
  • 25,839
  • 2
  • 51
  • 76
Trident D'Gao
  • 18,973
  • 19
  • 95
  • 159
  • 1
    In F#, computation expressions are not typically used in this way. If you give more information about the actual problem you are trying to solve, then someone might be able to suggest an alternative, more idiomatic solution. – Tomas Petricek Oct 02 '13 at 03:17
  • It is hard to say without knowing more, but F# directly supports both (mutable) state and exceptions, so there might not be a real need for handling these with computation expressions. – Tomas Petricek Oct 02 '13 at 03:18
  • @TomasPetricek, I just added some details – Trident D'Gao Oct 02 '13 at 03:29

3 Answers3

4

As stated in the previous answer, one way to combine workflows in F# (Monads in Haskell) is by using a technique called Monad Transformers.

In F# this is really tricky, here is a project that deals with that technique.

It's possible to write the example of the previous answer by automatically combining State and Maybe (option), using that library:

#r @"c:\packages\FSharpPlus-1.0.0\lib\net45\FSharpPlus.dll"

open FSharpPlus
open FSharpPlus.Data

// Stateful computation
let computation =
    monad {
        let! x = get
        let! y = OptionT (result (Some 10))
        do! put (x + y)
        let! x = get
        return x
    }

printfn "Result: %A" (State.eval (OptionT.run computation) 1)

So this is the other alternative, instead of creating your custom workflow, use a generic workflow that will be automatically inferred (a-la Haskell).

Gus
  • 25,839
  • 2
  • 51
  • 76
2

In F# you cannot easily mix different types of computation expressions as you would do in Haskell by using Monad Transformers or similar techniques. You could however build your own Monad, embedding state threading and optional values, as in:

type StateMaybe<'T> = 
    MyState -> option<'T> * MyState

// Runs the computation given an initial value and ignores the state result.
let evalState (sm: StateMaybe<'T>) = sm >> fst

// Computation expression for SateMaybe monad.
type StateMaybeBuilder() =
    member this.Return<'T> (x: 'T) : StateMaybe<'T> = fun s -> (Some x, s)
    member this.Bind(sm: StateMaybe<'T>, f: 'T -> StateMaybe<'S>) = fun s ->
        let mx,s' = sm s
        match mx with
        | Some x    -> f x s'
        | None      -> (None, s)

// Computation expression builder.
let maybeState = new StateMaybeBuilder()

// Lifts an optional value to a StateMaybe.
let liftOption<'T> (x: Option<'T>) : StateMaybe<'T> = fun s -> (x,s)

// Gets the current state.
let get : StateMaybe<MyState> = fun s -> (Some s,s)

// Puts a new state.
let put (x: MyState) : StateMaybe<unit> = fun _ -> (Some (), x)

Here's an example computation:

// Stateful computation
let computation =
    maybeState {
        let! x = get
        let! y = liftOption (Some 10)
        do! put (x + y)
        let! x = get
        return x
    }

printfn "Result: %A" (evalState computation 1)

StateMaybe may be generalized further by making the type of the state component generic.

esevelos
  • 376
  • 2
  • 8
1

Others already gave you a direct answer to your question. However, I think that the way the question is stated leads to a solution that is not very idiomatic from the F# perspective - this might work for you as long as you are the only person working on the code, but I would recommend against doing that.

Even with the added details, the question is still fairly general, but here are two suggestions:

  • There is nothing wrong with reasonably used mutable state in F#. For example, it is perfectly fine to create a function that generates IDs and pass it along:

    let createGenerator() = 
      let currentID = ref 0
      (fun () -> incr currentID; !currentID)
    
  • Do you really need to generate the IDs while you are building the entities? It sounds like you could just generate a list of entities without ID and then use Seq.zip to zip the final list of entities with list of IDs.

  • As for the maybe computation, are you using it to handle regular, valid states, or to handle exceptional states? (It sounds like the first, which is the right way of doing things - but if you need to handle truly exceptional states, then you might want to use ordinary .NET exceptions).

Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553
  • I thought of mutating the state and this is what I will end up with probably because monad-transformers are daunting and look like an overkill. Zipping ID's at the end doesn't seem like an option because it's rather a graph of entities and ID's are used as foreign keys that set the relations in that graph. So all of them must be connected before sending them to the database. I could use some sort of surrogate IDs, which is something to be replaced with a real ID at zipping, but it seems like more work. As for maybe, I see what you mean, no worries, the maybe workflow handles the valid states. – Trident D'Gao Oct 03 '13 at 16:29