参数化/提取受歧视的联合案例

Dav*_*aab 6 f# discriminated-union

目前我正在游戏中使用Event/Observables,我遇到的一件事是消除一些冗余代码,我没有找到办法.为了解释它,我们假设我们跟随DU和这个DU的Observable.

type Health =
    | Healed
    | Damaged
    | Died
    | Revived

let health = Event<Health>()
let pub    = health.Publish
Run Code Online (Sandbox Code Playgroud)

我有很多这种结构.将所有"健康"消息组合在一起是有帮助的,并且在某些情况下需要,但在某些情况下我只关心特殊的消息.因为仍然经常需要我Observable.choose用来分隔这些信息.我有这样的代码.

let healed = pub |> Observable.choose (function 
    | Healed -> Some ()
    | _      -> None
)

let damaged = pub |> Observable.choose (function
    | Damaged -> Some ()
    | _       -> None
)
Run Code Online (Sandbox Code Playgroud)

编写这种代码实际上是非常重复和烦人的.我有很多这些类型和消息.因此,函数式编程的一个"规则"是"参数化所有事物".所以我写了一个函数only来帮助我.

let only msg pub = pub |> Observable.choose (function
    | x when x = msg -> Some ()
    | _              -> None
)
Run Code Online (Sandbox Code Playgroud)

有了这样的功能,现在代码变得更短,写起来也不那么烦人了.

let healed  = pub |> only Healed
let damaged = pub |> only Damaged
let died    = pub |> only Died
let revived = pub |> only Revived
Run Code Online (Sandbox Code Playgroud)

编辑: 重要的是要注意.healed,damaged,died,revived现在的类型IObservable<unit>IObservable<Health>.这个想法不仅仅是分开信息.这可以很容易地实现Observable.filter.这个想法是提取每个案例的数据.对于不带任何附加数据的DU情况,这很容易,因为我只需要Some ()Observable.choose函数中写入.

但这只能起作用,只要DU中的不同情况不期望附加值.不幸的是,我也有很多带有附加信息的案例.例如,而不是HealedDamagedHealedBy of int.所以一条消息还包含了多少东西得到了治愈.在这种情况下,我正在做的是这样的事情.

let healedBy = pub |> Observable.choose (function
    | HealedBy x -> Some x
    | _          -> None
)
Run Code Online (Sandbox Code Playgroud)

但我真正想要的是写下这样的东西

let healedBy = pub |> onlyWith HealeadBy
Run Code Online (Sandbox Code Playgroud)

我期待的是得到一个Observable<int>.我没有找到任何方法如何做到这一点.我不能写only上面这样的函数.因为当我尝试msg在模式匹配中进行评估时,它只被视为模式匹配所有情况的变量.我不能说:"匹配变量内的案例."

我可以检查一个变量是否属于某种特定情况.我可以做if x = HealedBy then但在那之后,我无法从中提取任何类型的数据x.我真正需要的是类似"不安全"提取的选项,例如提供它optional.Value.是否有任何方法可以实现这样的"onlyWith"函数来删除样板?


编辑: 这个想法不只是分开不同的消息.这可以通过实现Observable.filter.这healedByIObservable<int>NOT 类型IObservable<Health>.大思路是分开的消息提取它随身携带数据做它没有太多的样板.我现在已经可以一次性分离和提取它了Observable.choose.只要一个案例没有任何附加数据,我就可以使用该only函数来摆脱样板.

但是一旦一个案例有了额外的数据,我就会重新编写重复的Observable.Choose函数并重新进行所有的模式匹配.目前我的代码是这样的.

let observ = pub |> Observable.choose (function 
    | X (a) -> Some a
    | _     -> None
)
Run Code Online (Sandbox Code Playgroud)

我有很多消息和不同类型的东西.但唯一改变的是它中的"X".所以我显然想要参数化"X",所以我不必一次又一次地编写整个构造.它应该是最好的

let observ = anyObservable |> onlyWith CaseIWantToSeparate
Run Code Online (Sandbox Code Playgroud)

但是新的Observable是我分离的具体案例的类型.不是DU本身的类型.

The*_*ght 3

您正在寻找的行为并不存在,它在您的第一个示例中工作正常,因为您始终可以一致地返回unit option.

let only msg pub = 
    pub |> Observable.choose (function
        | x when x = msg -> Some ()
        | _              -> None)
Run Code Online (Sandbox Code Playgroud)

请注意,它的类型为:'a -> IObservable<'a> -> IObservable<unit>

现在,为了创建一个清晰的示例,让我们想象一下,我定义了一些可以包含多种类型的新 DU:

type Example =
    |String of string
    |Int of int
    |Float of float
Run Code Online (Sandbox Code Playgroud)

想象一下,作为一个思维练习,我现在尝试定义一些与上述功能相同的通用函数。它的类型签名可能是什么?

Example -> IObservable<Example> -> IObservable<???>
Run Code Online (Sandbox Code Playgroud)

???不能是上面的任何具体类型,因为类型都是不同的,也不能是泛型类型,因为同样的原因。

由于不可能为这个函数提供一个合理的类型签名,这非常强烈地暗示这不是实现它的方法。

您遇到的问题的核心是您无法在运行时决定返回类型,返回可以是几种不同的可能但定义的情况的数据类型正是歧视联合帮助您解决的问题。

因此,您唯一的选择是明确处理每种情况,您已经知道或已经看到了如何执行此操作的多种选项。就我个人而言,我不认为定义一些要使用的辅助函数有什么太可怕的:

let tryGetHealedValue = function
    |HealedBy hp -> Some hp
    |None -> None

let tryGetDamagedValue = function
    |DamagedBy dmg -> Some dmg
    |None -> None
Run Code Online (Sandbox Code Playgroud)