Is there a way to make different implementation of do! and let! in a computation expression?
I need a different behavior for do! and let! in my custom computation expression.
I try to achieve this in the following way:
type FooBuilder() = class
member b.Bind<'T, 'U>(x:'T, f:unit->'U):'U = failwith "not implemented" //do! implementation
member b.Bind<'T, 'U>(x:'T, f:'T->'U):'U = failwith "not implemented" //let! implementation
member b.Return<'T>(开发者_开发技巧x:'T):'T = failwith "not implemented" //return implementation
end
let foo = FooBuilder()
let x = foo {
do! ()
return 2
}
But compiler gives me an error:
A unique overload for method 'Bind' could not be determined based on type information prior to this program point. The available overloads are shown below (or in the Error List window). A type annotation may be needed.
Is there a way to have a different implementation of do! and let!?
If you want to keep the Bind
operation used in let!
generic, then there is no way to say that F# should use different implementation when translating do!
(the overloads will necessarily have to overlap).
In general, if you want to get different behavior for let!
and for do!
then it suggests that your computation expression is probably incorrectly defined. The concept is quite flexible and it can be used for more things than just for declaring monads, but you may be stretching it too far. If you can write more information about what you want to achieve, that would be useful. Anyway, here are some possible workarounds...
You can add some additional wrapping and write something like do! wrap <| expr
.
type Wrapped<'T> = W of 'T
type WrappedDo<'T> = WD of 'T
type FooBuilder() =
member b.Bind<'T, 'U>(x:Wrapped<'T>, f:'T->'U):'U = failwith "let!"
member b.Bind<'T, 'U>(x:WrappedDo<unit>, f:unit->'U):'U = failwith "do!"
member b.Return<'T>(x:'T):Wrapped<'T> = failwith "return"
let wrap (W a) = WD a
let bar arg = W arg
let foo = FooBuilder()
// Thanks to the added `wrap` call, this will use the second overload
foo { do! wrap <| bar()
return 1 }
// But if you forget to add `wrap` then you still get the usual `let!` implementation
foo { do! wrap <| bar()
return 1 }
Another alternative would be to use dynamic type tests. This is a bit inefficient (and a bit inelegant), but it may do the trick, depending on your scenario:
member b.Bind<'T, 'U>(x:Wrapped<'T>, f:'T->'U):'U =
if typeof<'T> = typeof<unit> then
failwith "do!"
else
failwith "let!"
However, this would still use the do!
overload when you write let! () = bar
.
You could try something else, a bit ugly, but should work:
let bindU (x, f) = f x // you must use x, or it'll make the Bind method less generic.
let bindG (x, f) = f x
member b.Bind(x : 'a, f : 'a -> 'b) =
match box x with
| :? unit -> bindU (x, f)
| _ -> bindG (x, f)
It boxes a (converts it to obj
) and checks if it is of type unit
, then redirects to the correct overload.
精彩评论