1
\$\begingroup\$

I have this type:

type Future<'a> () =
    let mutable _value: 'a option = None

    member x.Resolve value =
        if _value.IsSome then failwith "can only resolve once"
        _value <- Some value

    member x.IsResolved with get () = _value.IsSome

    member x.Value 
        with get () = 
            match _value with 
            | Some v -> v
            | None -> failwith "hasn't resolved yet"

And I need something like this because I have something along the lines of:

let testHttpGet response =
    let urlFuture = Future<string>()

    let get url = 
        urlFuture.Resolve url
        response

    (get, urlFuture)

Call that function and set the return value of the returned function

let get, urlFuture = testHttpGet "resp"

Call the returned func, thus resolving the Future

let response = get "http://test"

In the end we have the result of the function

response |> should equal "resp"

And we have the value with which the func was called

urlFuture.IsResolved |> should be True
urlFuture.Value |> should equal "http://test"

To me this looks like a sound approach but I also have a feeling that something like it must exist in f# allready

Here's where I use them.

Here is a fiddle.

\$\endgroup\$
  • \$\begingroup\$ The provided testHttpGet function doesn't compile. Can you update the code so that we can see what you mean to do? \$\endgroup\$ – Mark Seemann Jan 16 '15 at 16:23
  • \$\begingroup\$ Oops yes offcourse! \$\endgroup\$ – albertjan Jan 16 '15 at 16:36
  • \$\begingroup\$ @MarkSeemann edited and added a fiddle \$\endgroup\$ – albertjan Jan 16 '15 at 16:51
3
\$\begingroup\$

This looks pretty close to a Clojure Promise. On .NET, the similar type is (very Microsoftish) called TaskCompletionSource<TResult>.

However, you should consider if F# Lazy Computations isn't what you need.

\$\endgroup\$
  • \$\begingroup\$ Lazy computations seem like the way to go :) i'll have a look at it! Thanks. \$\endgroup\$ – albertjan Jan 16 '15 at 18:42
3
\$\begingroup\$

Forget about the mutable for a moment. To me, using exceptions and methods called IsXXX are a bit of code smell in a functional language.

Here's how I personally would rewrite the Future class:

  • Remove IsResolved completely.
  • Change Value to return an Option. You can get the value and whether it is resolved in one step.
  • Change Resolve to not throw an exception if already resolved, but instead return a boolean. This also means you can avoid having to have a hard-coded error string -- let the caller decide the error message instead.

.

type Future<'a> () =
    let mutable _value: 'a option = None

    /// Set the value to resolve
    /// If already resolved, return false, else true.
    member x.Resolve value =
        match _value with
        | Some _ -> 
            false
        | None -> 
            _value <- Some value
            true

    /// Return the value to resolve
    /// If not resolved, return None, else Some value
    member x.Value with get () = _value 

Now you can create the testHttpGet function as before.

But because Resolve now returns a bool, you have to explicitly handle it. For now, I've used ignore, but you can see that the potential for a bug has been made more visible to the caller.

let testHttpGet response =
    let urlFuture = Future<string>()

    let get url = 
        urlFuture.Resolve url |> ignore  // code smell!
        response

    (get, urlFuture)

Now use the testHttpGet function:

let get, urlFuture = testHttpGet "resp"
let response = get "http://test"

Finally, in the test assertions, the IsResolved method is not needed, as the Value method can do double duty.

//urlFuture.IsResolved |> should be True  // not needed
urlFuture.Value |> should equal (Some "http://test")

Going back to the question of a mutable now, I'm not sure why your design calls for separating a task from its result?

Why not combine them (that is, use Lazy as suggested by Mark), or use something like Async where you can chain a series of "callbacks" together with no need to test whether they have resolved or not.

\$\endgroup\$
  • \$\begingroup\$ Wow! Scott Wlaschin! :). Thanks. Why are funcs or properties called IsXXX bad practice in functional languages? \$\endgroup\$ – albertjan Jan 18 '15 at 12:59
  • 1
    \$\begingroup\$ If the value exists you're going to do something, almost always with that value. So why have two methods when you can just use an option to capture both? It's the lack of options in C# and Java that has led to that IsXXX pattern. See also existentialtype.wordpress.com/2011/03/15/boolean-blindness (a bit technical, but his points are good) \$\endgroup\$ – Grundoon Jan 18 '15 at 14:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.