我想使用计算表达式语法构造一些对象。理想情况下,我希望有一个计算表达式,并让编译器找出正确的一组重载以使类型正常工作。
这个想法是有一个构建器,用户可以在其中编写两个部分:
program {
// Input section
// Read inputs (statically analyzable)
let! x = Input.read "x"
and! y = Input.read "y"
// ...
// Instruction section
// Take actions
// ...
do! InstrFree.show message
// Result section
return x + y
}
使用普通函数可以做到这一点,但我想看看可以使用什么语法。
下面是一个独立的示例,无法编译。
是否可以调整构建器对象以使示例正常工作?
type Input<'a> =
{
Keys : Set<string>
TryRead : Map<string, obj> -> 'a option
}
module Input =
let read (key : string) : Input<'a> =
{
Keys = Set.singleton key
TryRead =
fun m ->
match Map.tryFind key m with
| Some (:? 'a as a) -> Some a
| _ -> None
}
let map2 (f : 'a -> 'b -> 'c) (x : Input<'a>) (y : Input<'b>) =
{
Keys = Set.union x.Keys y.Keys
TryRead =
fun m ->
match x.TryRead m with
| Some a ->
match y.TryRead m with
| Some b -> Some (f a b)
| None -> None
| None -> None
}
type Instr<'a> =
| Load of string * (string -> 'a)
| Show of string * (unit -> 'a)
module Instr =
let map (f : 'a -> 'b) (x : Instr<'a>) : Instr<'b> =
match x with
| Load (key, unpack) -> Load (key, unpack >> f)
| Show (key, unpack) -> Show (key, unpack >> f)
type InstrFree<'a> =
| Just of 'a
| Free of Instr<InstrFree<'a>>
module InstrFree =
let just x =
Just x
let rec bind (f : 'a -> InstrFree<'b>) (x : InstrFree<'a>) : InstrFree<'b> =
match x with
| Just a -> f a
| Free instr ->
Instr.map (bind f) instr
|> Free
let show (message : string) =
InstrFree.Free (Instr.Show (message, fun () -> Just ()))
type Program<'i, 'o> =
{
Input : Input<'i>
Output : 'i -> InstrFree<'o>
}
module Program =
let chain (x : Program<'i, 'j>) (y : Program<'j, 'o>) : Program<'i, 'o> =
{
Input = Input.map2 (fun a _ -> a) x.Input y.Input
Output =
fun i ->
let f = x.Output i
f
|> InstrFree.bind (fun j -> y.Output j)
}
type ProgramBuilder() =
member this.MergeSources(x, y) =
Input.map2 (fun a b -> a, b) x y
member this.BindReturn(x, y) =
{
Input = x
Output = y
}
member this.BindReturn(x, f) =
{
Input = x
Output = fun i -> InstrFree.just (f i)
}
member this.Bind(x, f) =
{
Input = x.Input
Output =
fun i ->
let a = x.Output i
a |> InstrFree.bind f
}
member this.Combine(a, b) =
Program.chain a b
let program = ProgramBuilder()
let a =
program {
let! x = Input.read "x"
return x + 1
}
// Does not compile
let b =
program {
let! x = Input.read "x"
and! y = Input.read "y"
return x * y
}
// Does not compile
let c =
program {
let! x = Input.read "x"
and! y = Input.read "y"
let message = $"x + y = %i{x + y}"
do! InstrFree.show message
}
抱歉,这个例子很长;我无法想出一个更小的例子!
它遵循此处规定的设计模式。
通过在构建器类中提供多个
.Source
成员,可以让编译器在计算表达式中的重载之间进行选择。
不幸的是,这似乎没有记录在 Microsoft 文档中。
您可以在此处查看示例:
在这种情况下,AsyncResult 构建器可以绑定(使用
let!
)多种类型,包括 Async<'a>
、Result<'a,'e>
等。
快乐编码,
罗兰