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.
2 Answers 2
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.
-
\$\begingroup\$ Lazy computations seem like the way to go :) i'll have a look at it! Thanks. \$\endgroup\$albertjan– albertjan2015年01月16日 18:42:51 +00:00Commented Jan 16, 2015 at 18:42
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.
-
\$\begingroup\$ Wow! Scott Wlaschin! :). Thanks. Why are funcs or properties called
IsXXX
bad practice in functional languages? \$\endgroup\$albertjan– albertjan2015年01月18日 12:59:11 +00:00Commented 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– Grundoon2015年01月18日 14:22:51 +00:00Commented Jan 18, 2015 at 14:22
testHttpGet
function doesn't compile. Can you update the code so that we can see what you mean to do? \$\endgroup\$