f# - 参数化/提取有区别的联合案例

标签 f# discriminated-union

目前我正在玩游戏并且经常使用 Event/Observables,我遇到的一件事是消除一些冗余代码,但我没有找到方法来做到这一点。为了解释它,让我们假设我们有以下 DU 和这个 DU 的 Observable。

type Health =
    | Healed
    | Damaged
    | Died
    | Revived

let health = Event<Health>()
let pub    = health.Publish

我有很多这种结构。将所有“健康”消息组合在一起在某些情况下很有帮助和需要,但在某些情况下我只关心一个特殊的消息。因为这仍然经常需要我使用 Observable.choose将这些消息分开。然后我有这样的代码。
let healed = pub |> Observable.choose (function 
    | Healed -> Some ()
    | _      -> None
)

let damaged = pub |> Observable.choose (function
    | Damaged -> Some ()
    | _       -> None
)

编写这种代码实际上非常重复和烦人。我有很多这样的类型和消息。所以函数式编程的一个“规则”是“参数化所有的东西”。所以我写了一个函数 only只是帮助我。
let only msg pub = pub |> Observable.choose (function
    | x when x = msg -> Some ()
    | _              -> None
)

有了这样的功能,现在代码变得更短,编写起来也不那么烦人了。
let healed  = pub |> only Healed
let damaged = pub |> only Damaged
let died    = pub |> only Died
let revived = pub |> only Revived

编辑:
要注意的重要事项。 healed , damaged , died , revived现在是 IObservable<unit> 类型不是 IObservable<Health> .这个想法不仅仅是分离消息。这可以通过 Observable.filter 轻松实现.这个想法是提取每个案例额外的数据。对于不携带任何额外数据的 DU 情况,这很容易,因为我只需要写 Some ()Observable.choose功能。

但这只是有效,只要 DU 中的不同情况不期望额外的值。不幸的是,我也有很多带有附加信息的案例。例如代替 HealedDamaged我有 HealedBy of int .所以一条消息还包含额外的东西得到了多少治愈。在这种情况下,我正在做的是这样的事情。
let healedBy = pub |> Observable.choose (function
    | HealedBy x -> Some x
    | _          -> None
)

但我真正想要的是把它写成这样
let healedBy = pub |> onlyWith HealeadBy

我期待的是得到一个 Observable<int> .我没有找到任何方法来做到这一点。我无法编写像 only 这样的函数以上。因为当我尝试评估 msg在模式匹配中,它只是被视为模式匹配所有情况的变量。我不能这样说:“匹配变量内的大小写。”

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

编辑:
这个想法不仅仅是分离不同的消息。这可以通过 Observable.filter 实现.这里healedBy类型为 IObservable<int>不是 IObservable<Health>了。大想法是将消息分开 提取它携带的数据在没有太多样板的情况下做到这一点。我已经可以用 Observable.choose 一次性分离和提取它目前。只要案例没有任何额外数据,我就可以使用 only功能来摆脱样板。

但是一旦一个案例有额外的数据,我就会回来写重复的 Observable.Choose函数并再次执行所有模式匹配。事情是目前我有这样的代码。
let observ = pub |> Observable.choose (function 
    | X (a) -> Some a
    | _     -> None
)

我有很多消息和不同类型的东西。但唯一改变的是其中的“X”。所以我显然想参数化“X”,这样我就不必一次又一次地编写整个构造。充其量应该是
let observ = anyObservable |> onlyWith CaseIWantToSeparate

但是新的 Observable 是我分离的特定案例的类型。不是 DU 本身的类型。

最佳答案

您正在寻找的行为不存在,它在您的第一个示例中运行良好,因为您始终可以始终如一地返回 unit option .

let only msg pub = 
    pub |> Observable.choose (function
        | x when x = msg -> Some ()
        | _              -> None)

请注意,它的类型为:'a -> IObservable<'a> -> IObservable<unit>
现在,让我们想象一下,为了创建一个清晰的示例,我定义了一些可以包含多种类型的新 DU:
type Example =
    |String of string
    |Int of int
    |Float of float

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

??? 不能是上述任何具体类型,因为类型都不同,也不能是通用类型,原因相同。

由于不可能为这个函数提出一个合理的类型签名,这是一个非常强烈的暗示,这不是这样做的方法。

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

因此,您唯一的选择是明确处理每种情况,您已经知道或已经看到了如何执行此操作的几种选择。就个人而言,我认为定义一些要使用的辅助函数并没有什么可怕的:
let tryGetHealedValue = function
    |HealedBy hp -> Some hp
    |None -> None

let tryGetDamagedValue = function
    |DamagedBy dmg -> Some dmg
    |None -> None

关于f# - 参数化/提取有区别的联合案例,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35537160/

相关文章:

f# - 如何避免基于长模式匹配的函数?

typescript - 通过添加新类型对可区分的联合进行类型检查

f# - 访问 DU 成员的命名字段

TypeScript:区分元组中的联合和值

c# - 如何在 F# 中创建值类型的联合类型?

f# - 处理没有可变性的元组流?

c# - 在 F# 中处理资源清理的功能方法

f# - fparsec 中的位置信息

.net - 在 F# 中从字符串中去除字符

javascript - 使用 Typescript 在 Opaque 上区分联合