是否可以控制计算表达式匹配顺序以获得细粒度控制?

问题描述 投票:0回答:1

我想使用计算表达式语法构造一些对象。理想情况下,我希望有一个计算表达式,并让编译器找出正确的一组重载以使类型正常工作。

这个想法是有一个构建器,用户可以在其中编写两个部分:

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
  }

抱歉,这个例子很长;我无法想出一个更小的例子!

它遵循此处规定的设计模式。

f#
1个回答
0
投票

通过在构建器类中提供多个

.Source
成员,可以让编译器在计算表达式中的重载之间进行选择。

不幸的是,这似乎没有记录在 Microsoft 文档中。

您可以在此处查看示例:

https://github.com/demystifyfp/FsToolkit.ErrorHandling/blob/master/src/FsToolkit.ErrorHandling/AsyncResultCE.fs

在这种情况下,AsyncResult 构建器可以绑定(使用

let!
)多种类型,包括
Async<'a>
Result<'a,'e>
等。

快乐编码,

罗兰

© www.soinside.com 2019 - 2024. All rights reserved.