F# 具有可选递归组件的可区分联合

F# Discriminated union with optional recurcive component

我正在使用 F#,但在构建我的业务模型时遇到了困难。 假设我有一个浮点数列表和我可以对其应用的两种类型的转换。 例如:

类型转换= | period:Period 的 SMA | period:Period

的 P2MA

然后我定义了一个函数 let compute Transformation list 来计算列表上的任何类型的转换。

使用上述转换类型,我可以通过示例创建 SMA(3) 或 P2SMA(5)。

我希望能够以我可以通过示例编写 SMA(3, P2SMA(5, SMA(10))) 的方式嵌套转换。但我也希望仍然能够只编写 SMA(2)。 我尝试使用选项,但我认为编写 SMA(3, None) 或 SMA(3, Some(P2SMA(5))) 太冗长了。

有什么办法吗?也许我的方法是错误的,因为我是 F# 的新手,我可能会以错误的方式解决问题?

非常感谢您的帮助

I tried using options, but I think writing SMA(3, None) or SMA(3, Some(P2SMA(5))) is too verbose.

您可以使用带有可选参数的静态成员:

type Transformation =
    | [<EditorBrowsable(EditorBrowsableState.Never)>]
      SMAInternal of period:int * inner: Transformation option
    ...
    static member SMA(period:int, ?t:Transformation) =
        SMAInternal(period, t)

然后您可以写:Transformation.SMA(3)Transformation.SMA(3, Transformation.P2SMA(5))。这需要更多的字符,但结构更少。您可能会或可能不会认为它更简洁。

I'm new in F#, I may tackle the problem by the wrong way?

如果您要在一个代码文件中定义数百个这样的东西,那么使用上述方法并缩短名称 Transformation 可能是个好主意。否则只需使用 Somes 和 Nones。冗长是一个可以忽略不计的考虑因素,如果你开始担心它,可怕的事情就会开始发生。

我不太明白你要建模的是什么,但这里有一些类似的东西。

我的转换是数字的倍数或相加

type Trans = 
    | Mult of period: int
    | Add of period: int

我现在可以编写一个解释函数,给定一个数字和一个转换,我可以解释它

let interpret x trans = 
    match trans with
    | Mult p -> p * x
    | Add p -> p + x

所以我们现在可以做简单的 让 x = 解释 1 (Mult 2)

但是你想链式转换? 所以让我们允许..

let interprets xs x = 
    List.fold (fun state trans -> 
        interpret state trans) x xs

我们可以去...

let foo = [ Mult 3; Add 2 ]

let bar = interprets foo 1

好的,如果你真的想统一处理这些转换列表的组合,这可能很好(有点像函数组合)。

然后我很想去(注意我正在尝试遵循您的编码风格) (这里有很多东西要吸收,所以也许坚持上面的方法,直到你对 F# 理解得更好一点感到高兴)。

type Trans = 
    | Mult of period: int
    | Add of period: int
    | Compose of chain: List<Trans>
 
let rec interpret x trans = 
    let interprets xs x = 
        List.fold (fun state trans -> 
            interpret state trans) x xs
    match trans with
    | Mult p -> p * x
    | Add p -> p + x
    | Compose ps -> 
        interprets ps x
        
let two = interpret 1 (Mult 2)

let three = interpret 1 (Compose [ Mult 2; Add 1 ])

现在我认为您有一个“有效”的数据模型,而且非常简单。

然后我不会尝试更改数据模型以使您的代码更方便,我会创建实用程序函数(智能构造函数)来做到这一点。

例如

let multThen x trans = Compose [ Mult x; trans ]
let addThen x trans = Compose [ Add x; trans ]

虽然建议是让数据模型以最简单的方式对数据进行建模,然后使用函数使您的代码优雅,并映射进出该模型,但通常两者看起来完全不同。

警告:我还没有测试过部分代码

Try my answer here.

不可能完全按照您想要的方式重载受歧视的联合案例。但是如果你接受一个非常不同的语法,你可以这样做:

type Period = int

type SmaTransform =
    | Sma of Period
    | Sma' of Period * Transform

and P2smaTransform =
    | P2sma of Period
    | P2sma' of Period * Transform

and Transform =
    | OfSma of SmaTransform
    | OfP2Sma of P2smaTransform

let SMA(period) =
    Sma(period) |> OfSma

let SMA'(period, transform) =
    Sma'(period, transform) |> OfSma

let P2SMA(period) =
    P2sma(period) |> OfP2Sma

let P2SMA'(period, transform) =
    P2sma'(period, transform) |> OfP2Sma

let transforms =
    [|
        SMA(3)
        P2SMA(5)
        SMA'(3, P2SMA'(5, SMA(10)))
    |]

for transform in transforms do
    printfn "%A" transform

与您所需语法的唯一区别是表示嵌套转换的撇号。