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\$
3
  • \$\begingroup\$ The provided testHttpGet function doesn't compile. Can you update the code so that we can see what you mean to do? \$\endgroup\$ Commented Jan 16, 2015 at 16:23
  • \$\begingroup\$ Oops yes offcourse! \$\endgroup\$
    – albertjan
    Commented Jan 16, 2015 at 16:36
  • \$\begingroup\$ @MarkSeemann edited and added a fiddle \$\endgroup\$
    – albertjan
    Commented Jan 16, 2015 at 16:51

2 Answers 2

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\$
1
  • \$\begingroup\$ Lazy computations seem like the way to go :) i'll have a look at it! Thanks. \$\endgroup\$
    – albertjan
    Commented Jan 16, 2015 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\$
2
  • \$\begingroup\$ Wow! Scott Wlaschin! :). Thanks. Why are funcs or properties called IsXXX bad practice in functional languages? \$\endgroup\$
    – albertjan
    Commented Jan 18, 2015 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
    Commented Jan 18, 2015 at 14:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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