从现有的联合案例中创建新的联合案例

3

有没有更好的方法来编写下面的addValues函数呢?似乎可以使用模式匹配而不是FSharp.Reflection,但我看不到该如何做。

open System
open FSharp.Reflection

type Value =
    | Tag1 of decimal
    | Tag2 of decimal
    | Error of string

let addValues v1 v2 =
    let c1, f1 = FSharpValue.GetUnionFields(v1, v1.GetType())
    let c2, f2 = FSharpValue.GetUnionFields(v2, v2.GetType())
    let amt1 = (f1.[0]) :?> decimal
    let amt2 = (f2.[0]) :?> decimal

    if c1 = c2
        then ((FSharpValue.MakeUnion(c1, [|box (amt1 + amt2)|]))) :?> Value
        else Error "Mixed Tags"

这可以这样使用:
addValues (Tag1 22m) (Tag1 10m) //Value = Tag1 32M
addValues (Tag1 22m) (Tag2 10m) //Value = Error "Mixed Tags"

2
addValues (Error "e1") (Error "e2") 应该怎么处理? - Lee
1
你确定你需要在这种类型中使用“Error”吗? - FoggyFinder
1
请在这种情况下使用 Choice<'T1,'T2> 或者 Option - FoggyFinder
示例 - https://dotnetfiddle.net/SpF502 - FoggyFinder
3个回答

5

对于addValues (Error "e1") (Error "e2)如何处理还不清楚,但对于其他情况,您可以执行以下操作:

let addValues v1 v2 =
    match v1, v2 with
    | Tag1 d1, Tag1 d2 -> Tag1 (d1 + d2)
    | Tag2 d1, Tag2 d2 -> Tag2 (d1 + d2)
    | Error e1, Error e2 -> //???
    | _ -> Error "Mixed Tags"

我应该明确指定“不枚举匹配中的所有标签”。基本上,我想检查这两种情况是否相同,如果是,则返回一个新的值与总和。否则返回错误。至于这两个值是否为错误,在我的领域中返回任何一个错误都可以。 - jbeeko

4

虽然这不完全是原问题所涉及的,但是和其他评论者一样,我有一个直觉,即您选择的数据类型可能不是很合适。拥有一个Error情况意味着,一旦您有更多的标签,您的addValues函数将变得非常笨拙。如果我假设所有值都是十进制数,您可以重新定义如下:

type Tag = | Tag1 | Tag2
type ResultOrError = | Result of Tag * decimal | Error of string

或者使用其中一个Choice类型。然后addValues变成:

let addValues t1 t2 = 
    match t1, t2 with
    | Result (tag1, v1), Result (tag2, v2) when tag1 = tag2 -> Result (tag1, v1 + v2)
    | Result _, Result _ -> Error "Tag mismatch"
    | Result _, Error _ -> failwith "not implemented"
    | Error _, _ -> failwith "not implemented"

当您将标签类型扩展为type Tag = | Tag1 | Tag2 | Tag3时,addValues仍然有效。

这确实是我一直在尝试的另一种设计。它只是有点啰嗦,而不是写Tag1 33m来创建一个值,你需要写Result(Tag1, 33m) - jbeeko

1

我希望你能像Anton Schwaighofer在他的回答中一样表达同样的想法。关键信息必须包含在数据结构中,因为这将利用单子技术来进行提升; 否则,您需要分别调用Tag1Tag2的构造函数。

type Tag = Tag1 | Tag2
type ResultOrError = 
| Result of Tag * decimal
| Error of string

let bind2 f = function
| Result(tag, value) -> f tag value
| error -> error
let lift2EqualTag op mx my =
    bind2 (fun tagX x ->
        bind2 (fun tagY y ->
            if tagX = tagY then Result(tagX, op x y)
            else Error "Mixed tags" ) my ) mx
let add = lift2EqualTag (+)

add (Result(Tag1, 22m)) (Result(Tag1, 10m))
// val it : ResultOrError = Result (Tag1,32M)
add (Result(Tag1, 22m)) (Result(Tag2, 10m))
// val it : ResultOrError = Error "Mixed tags"

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接