构造复杂对象图的计算表达式

时间:2018-03-05 18:29:33

标签: f# computation-expression

考虑以下类型:

type Trip = {
  From: string
  To: string
}

type Passenger = {
   Name: string
   LastName: string
   Trips: Trip list
}

我正在使用以下构建器:

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

创建Passenger类型的记录,如下所示:

let passenger = PassengerBuilder()
let trip = TripBuilder()

let p = passenger {
  name "john"
  lastName "doe"
}

let t = trip {
  from "Buenos Aires"
  to "Madrid"
}

我如何组合PassengerBuilderTripBuilder以便我可以实现这种用法?

let p = passenger {
    name "John"
    lastName "Doe"
    trip from "Buenos Aires" to "Madrid"
    trip from "Madrid" to "Paris"
}

返回Passenger记录,如:

{
   LastName = "Doe"
   Name = "John"
   Trips = [
       { From = "Buenos Aires"; To = "Madrid" }
       { From = "Madrid"; To = "Paris" }
   ]
}

2 个答案:

答案 0 :(得分:5)

您是否有任何理由要使用计算表达式构建器?根据您的示例,它看起来不像您正在编写类似计算的任何内容。如果你只是想要一个漂亮的DSL来创建旅行,那么你可以很容易地定义一些让你写的东西:

let p = 
  passenger [
    name "John"
    lastName "Doe"
    trip from "Buenos Aires" towards "Madrid"
    trip from "Madrid" towards "Paris"
  ]

这几乎就是你要求的,除了它使用[ .. ]而不是{ .. }(因为它创建了一个转换列表)。我还将to重命名为towards,因为to是关键字,您无法重新定义它。

这个代码非常容易编写和遵循:

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 }

那就是说,我仍然会考虑使用普通的F#记录语法 - 它没有那么多丑陋。上述版本的一个缺点是你可以用空名和姓名创建乘客,这是F#阻止你的一件事!

答案 1 :(得分:4)

我不确定这是您想要的,但没有什么能阻止您在trip上创建名为PassengerBuilder的新操作:

  [<CustomOperation("trip")>]
  member __.Trip(r: Passenger, t: Trip) = 
    { r with Trips = t :: r.Trips }

然后像这样使用它:

let p = passenger {
    name "John"
    lastName "Doe"
    trip (trip { from "Buenos Aires"; to "Madrid" })
    trip (trip { from "Madrid"; to "Paris" })
}

可以说,你甚至可以通过完全删除TripBuilder来使它变得更干净:

let p = passenger {
    name "John"
    lastName "Doe"
    trip { From = "Buenos Aires"; To = "Madrid" }
    trip { From = "Madrid"; To = "Paris" }
}

如果这不是你想要的,那么请详细说明。也就是说,这个解决方案中缺少什么或者什么是额外的。