2

Is it possible to create methods or stand-alone functions in a computation expression that can later be used by one of the canonical methods of a computation expression?

I want something like this:

type FormletBuilder(ctx : HttpContext) =

    let get_int = 
        match Int32.TryParse (ctx.Request.["foo"]) with
        | (true, n) -> Some n
        | _ -> None

    //similar definitions for get_date, get_non_empty_str, etc...

    member x.Bind (read : 'a option, f : 'a -> option 'a) =
        match read with
        | Some x -> f(x)
        | None -> None      

    member x.Return (obj) = Some obj
    member x.Zero () = None



    let person = formlet ctx {

        let! id = get_int "id"
        let! name = get_non_empty_str "fullname"

        return Person(id, name)
    }

But the compiler complains that get_int is not defined.

Rodrick Chapman
  • 5,437
  • 2
  • 31
  • 32
  • What are you actually trying to do here? It seems to me that what you want to have is a reader monad, with HttpContext being the environment. In which case it won't be the builder's argument, but part of the state that's threaded through the computation. Then you could have your get_int etc. in a separate module, since they would no longer be tied to a builder, but would get HttpContext from outside. – scrwtp Nov 12 '14 at 20:39
  • @scrwtp - I rather thought this was the Maybe monad. The idea is that we're reading *and* parsing values from an HttpContext object, but one or both of those operations may fail (e.g. we read a value called "id", but it fails to parse as an int). The example code is a bit simplified; instead of `Option`, my actual code uses the type `ReadAttempt<'t> = Success of 't | Failed of string`. – Rodrick Chapman Nov 13 '14 at 05:43
  • Ok, fair enough. I'll add some of my thoughts as an answer. – scrwtp Nov 13 '14 at 08:02

2 Answers2

2

let bindings in class definitions are always private. You can define a member instead.

For an easy solution, you could do:

let formlet = FormletBuilder(ctx)
let person = formlet {
    let! id = formlet.get_int "id"
    ...
}
Daniel
  • 47,404
  • 11
  • 101
  • 179
1

I understand now that what you actually want is a maybe monad, and the workflow argument is there just to make use of some syntactic sugar? If so, there are a couple other things you can consider doing:

  1. Go Haskell on it all the way and implement a MaybeReader monad, so that both the maybe and the reader parts of it are explicit in type,
  2. Put the sugar away - I understand you don't actually need the context in any core builder members? If so, than maybe it had no business being an argument to the builder in the first place. Have a 'clean' maybe monad, move get_int etc. into a proper module and have them take HttpContext explicitly as an argument.
  3. If you're using F# 3.0 or later, you can define get_int etc. as custom operations of the workflow, which should effectively give you the nice syntax you want to have. Here's a good post about it by Tomas Petricek.
  4. Combine 2. and 3. - instead of a large number of custom operations, have one - ask - which will take an HttpContext -> 'a function and apply ctx to it. Effectively a bastardized version of reader. Then you can move your get_int etc. into a proper module.
scrwtp
  • 13,437
  • 2
  • 26
  • 30
  • Thanks you for the thoughtful answer. You're right, it was mostly for sugar. Before posting my question, I did come across the custom operators, but they didn't seem like a good fit since you need to use the `for..in..do` construct. I like #2 the best: I'll simply extend `HttpContext` to support the methods that I need. – Rodrick Chapman Nov 13 '14 at 16:31
  • @RodrickChapman: that's suprising about custom operations, I would not expect them to be tied to loops. Never used them myself though. – scrwtp Nov 13 '14 at 17:39