Inside an F# monad, if you say let!
, the compiler translates that to a Bind
member that you've defined on the monad builder.
Now I see there are Query monads, as shown here on MSDN, where you can say:
query {
for student in db.Student do
select student
count
}
and the select
and count
, for example, will be translated to the QueryBuilder
members Linq.QueryBuilder.Select and Linq.QueryBuilder.Count
.
My question is, is this mapping of keywords to members hardwired into the F# compiler, or is it extensible? For example, can I say something like:
FooMonadBuilder() {
bar
}
and somehow tell the F# compiler that bar
maps to a FooMonadBuilder.Bar()
method?
In F# 2.0 (that is Visual Studio 2010), there is no way to extend the keyword list (other than Ramon's extension). However, the query mechanism in F# 3.0 (Visual Sutdio 11) is extensible and you can define your own keywords similar to select
and count
.
Here is a basic example that defines something like seq
builder with reverse
keyword:
type SeqBuilder() =
// Standard definition for 'for' and 'yield' in sequences
member x.For (source : seq<'T>, body : 'T -> seq<'R>) =
seq { for v in source do yield! body v }
member x.Yield item =
seq { yield item }
// Define an operation 'select' that performs projection
[<CustomOperation("select")>]
member x.Select (source : seq<'T>, [<ProjectionParameter>] f: 'T -> 'R) : seq<'R> =
Seq.map f source
// Defines an operation 'reverse' that reverses the sequence
[<CustomOperation("reverse", MaintainsVariableSpace = true)>]
member x.Expand (source : seq<'T>) =
List.ofSeq source |> List.rev
let mseq = SeqBuilder()
The details how this works are not yet documented, but the CustomOperation
attribute says that the operation should be treated as a special syntax (you can set various properties to specify how it behaves - MaintainsVariableSpace
means that it does not change the values inside sequence). The Projectionparameter
attribute specifies that the expression following the keyword should be implicitly converted to a function.
Now, the mseq
builder supports both select
and reverse
:
let q = mseq { for i in 1 .. 10 do
select (i + 100)
reverse }
Short answer: no.
I've extended the compiler to support that, you're welcome to read my blog article http://ramon.org.il/wp/2011/04/taking-computation-expressions-one-step-further/