I have started to read stuff about computation expressions and so far as I understand - it has some hidden implementations that are default and custom.
I will provide things that I understand and please correct me.
For example, in this case we define a custom implementation to use let!. So that every expression bound to the let! inside the logger block will be logged to the console.
type LoggingBuilder() =
let log p = printfn "expression is %A" p
member this.Bind(x, f) =
log x
f x
member this.Return(x) = x
let logger = new LoggingBuilder()
let loggedWorkflow =
logger {
let! x = 42
let! y = 43
let! z = x + y
return z
}
I cant remember precise but I have read that if we don't provide an implementation to it - it has some default built in. For example some workflow that when it has received None, it will stop the whole workflow and will return just none, if it will return Some - the code will continue -> is this default or not?
Since keywords that are followed by the exclamation mark have some extra functionality behind the scenes, what is it inside the async {} block?
Take this example.
let printerAgent =
MailboxProcessor.Start
(fun inbox ->
// the message processing function
let rec messageLoop () =
async {
// read a message
let! msg = inbox.Receive()
// process a message
printfn "message is: %s" msg
// loop to top
return! messageLoop ()
}
// start the loop
messageLoop ())
I presume that the let! msg = inbox.Receive()
will stop the workflow if it receives a None. About return! I really have no idea.
No, there are no default implementations for computation expression methods. If you want want special behavior for Async<'T option>
, you can add an extension method to AsyncBuilder
. It looks like you want to short-circuit an Async<unit>
, so you would want something like this:
type FSharp.Control.AsyncBuilder with
member async.Bind(x: Async<'T option>, f: 'T -> Async<unit>) =
async.Bind(x, function
| Some x -> f x
| None -> async.Return())
The computation expression can resolve the overload between several Bind
implementations, although you need to be careful: if the types are ambiguous, F# will choose a method implemented on the type itself (in this case, the built-in Bind) over an extension method.
// Here `x` is used as an `int`, so F# knows that it needs to use
// my Bind implementation.
async {
let! x = myAsyncIntOption
let y = x + 1
return ()
}
// Here the type of `x` is unspecified, so F# chooses to use
// the built-in Bind implementation and `x` has type `int option`.
async {
let! x = myAsyncIntOption
return ()
}
Now, I've said what can be done, but I wouldn't recommend actually doing this. Instead I would do something more explicit:
let printerAgent =
MailboxProcessor.Start
(fun inbox ->
// the message processing function
let rec messageLoop () =
async {
// read a message
match! inbox.Receive() with
| None -> return ()
| Some msg ->
// process a message
printfn "message is: %s" msg
// loop to top
return! messageLoop ()
}
// start the loop
messageLoop ())