Reputation: 44038
Given the following types:
type Trip = {
From: string
To: string
}
type Passenger = {
Name: string
LastName: string
Trips: Trip list
}
I'm using the following builders:
type PassengerBuilder() =
member this.Yield(_) = Passenger.Empty
[<CustomOperation("lastName")>]
member __.LastName(r: Passenger, lastName: string) =
{ r with LastName = lastName }
[<CustomOperation("name")>]
member __.Name(r: Passenger, name: string) =
{ r with Name = name }
type TripBuilder() =
member __.Yield(_) = Trip.Empty
[<CustomOperation("from")>]
member __.From(t: Trip, f: string) =
{ t with From = f }
// ... and so on
to create records of type Passenger
, like so:
let passenger = PassengerBuilder()
let trip = TripBuilder()
let p = passenger {
name "john"
lastName "doe"
}
let t = trip {
from "Buenos Aires"
to "Madrid"
}
how would I go about combining the PassengerBuilder
and the TripBuilder
so that I can achieve this usage?
let p = passenger {
name "John"
lastName "Doe"
trip from "Buenos Aires" to "Madrid"
trip from "Madrid" to "Paris"
}
that returns a Passenger
record like:
{
LastName = "Doe"
Name = "John"
Trips = [
{ From = "Buenos Aires"; To = "Madrid" }
{ From = "Madrid"; To = "Paris" }
]
}
Upvotes: 2
Views: 123
Reputation: 243061
Is there any reason why you want to use computation expression builder? Based on your example, it does not look like you're writing anything computation-like. If you just want a nice DSL for creating trips, then you could quite easily define something that lets you write:
let p =
passenger [
name "John"
lastName "Doe"
trip from "Buenos Aires" towards "Madrid"
trip from "Madrid" towards "Paris"
]
This is pretty much exactly what you asked for, except that it uses [ .. ]
instead of { .. }
(because it creates a list of transformations). I also renamed to
to towards
because to
is a keyword and you cannot redefine it.
The code for this is quite easy to write and follow:
let passenger ops =
ops |> List.fold (fun ps op -> op ps)
{ Name = ""; LastName = ""; Trips = [] }
let trip op1 arg1 op2 arg2 ps =
let trip =
[op1 arg1; op2 arg2] |> List.fold (fun tr op -> op tr)
{ From = ""; To = "" }
{ ps with Trips = trip :: ps.Trips }
let name n ps = { ps with Name = n }
let lastName n ps = { ps with LastName = n }
let from n tp = { tp with From = n }
let towards n tp = { tp with To = n }
That said, I would still consider using normal F# record syntax - it is not that much uglier than this. The one drawback of the version above is that you can create passengers with empty names and last names, which is one thing that F# prevents you from!
Upvotes: 5
Reputation: 80744
I'm not sure this is what you wanted, but nothing prevents you from creating a new operation called trip
on your PassengerBuilder
:
[<CustomOperation("trip")>]
member __.Trip(r: Passenger, t: Trip) =
{ r with Trips = t :: r.Trips }
and then using it like this:
let p = passenger {
name "John"
lastName "Doe"
trip (trip { from "Buenos Aires"; to "Madrid" })
trip (trip { from "Madrid"; to "Paris" })
}
Arguably, you can even make it cleaner by dropping the TripBuilder
altogether:
let p = passenger {
name "John"
lastName "Doe"
trip { From = "Buenos Aires"; To = "Madrid" }
trip { From = "Madrid"; To = "Paris" }
}
If this is somehow not what you wanted, then please specify how. That is, what is missing or what is extra in this solution.
Upvotes: 4