【问题标题】:Is a pattern match the only way to get data associated with a union case?模式匹配是获取与联合案例关联的数据的唯一方法吗?
【发布时间】:2015-05-27 15:34:24
【问题描述】:

假设你有这个联合:

type Thing = 
| Eagle
| Elephant of int

您的代码有一个大象列表,如

let l = [Elephant (1000); Elephant (1200)]

并且您想遍历l,并打印出与每个Elephant 关联的数据。有没有办法在不使用模式匹配的情况下做到这一点?

【问题讨论】:

  • 为什么模式匹配不好?
  • 你可以添加members 或使用函数......但这些需要使用模式匹配来解构 - 但你还能期待什么?

标签: f# pattern-matching


【解决方案1】:

在您的示例中,您说您有一个大象列表 - 在这种情况下是正确的 - 但 l 的类型实际上是 Thing 值的列表,因此它可以同时包含大象和鹰。这就是为什么您需要使用模式匹配来处理所有可能的情况。

如果您经常需要使用包含 个大象的列表,那么定义一个单独的大象类型可能是有意义的。比如:

type ElephantInfo = { Size : int }

type Thing = 
  | Elephant of ElephantInfo
  | Eagle

现在您可以创建一个 list<ElephantInfo> 类型的列表,它可以只包含大象,因此您不需要模式匹配:

let l1 = [ {Size=1}; {Size=2} ]
for el in l1 do printfn "%d" el.Size

另一方面,如果你想混合大象和鹰,你将创建list<Thing>,然后使用模式匹配:

let l2 = [ Elephant {Size=1}; Eagle ]

【讨论】:

    【解决方案2】:

    你可以这样做:

    l
    |> List.collect (function Elephant x -> [x] | _ -> [])
    |> List.iter (printfn "%i")
    

    打印

    1000
    1200
    

    它仍然使用模式匹配,但它相当少。

    【讨论】:

    • List.choose (function Elephant x -> Some x | _ -> None) 在这里看起来更好。
    • 谢谢,这就是我现在正在做的事情。
    • 也许我没有很好地使用 DU。我使用它们在内部表示用户输入的命令行选项,其中一种情况是“InvalidCommand as string”,表示系统无法识别的命令。稍后在代码中,我从这个列表中提取出所有 InvalidCommands,以便我可以将它们作为单独的用例处理。处理这些无效命令的代码需要进行模式匹配以获取字符串数据(用户在命令行输入的不正确文本),并且它不能只将列表视为只有 InvalidCommands,它必须考虑所有DU案例
    • @MichaelRayLovett FWIW,这是我们在 Zero29 中使用 DU 解析命令行参数的方法:github.com/ploeh/ZeroToNine/blob/master/Src/Zero29/…
    • 非常在这种情况下使用collect的好主意。
    【解决方案3】:

    您当然可以选择完整象牙塔 (® Scott Wlaschin)

    关于:

    type Thing = 
     | Eagle
     | Elephant of int
    
    type MaybeElephantBuilder() =      
     member this.Bind(x, f) = 
      match x with
      | Eagle -> 0
      | Elephant a -> f a
    
     member this.Return(x) = x
    
    let maybeElephant = new MaybeElephantBuilder()
    
    let l = 
     [ Elephant(1000)
       Elephant(1200)
        ]
    
    let printIt v = 
     let i = 
      maybeElephant { 
       let! elephantValue = v  
       return elephantValue
      }
     printfn "%d" i   
    
    l |> Seq.iter printIt
    

    它甚至可以处理扔在那里的老鹰队的东西!

    嗯……

    去掉非鹰,代码会飞……

    let l = 
     [ Eagle
       Leadon
       Elephant(1000)
       Eagle
       Meisner
       Elephant(1200)
       Eagle
       Felder
        ]
    
    l |> Seq.iter printIt
    

    但是没有。它不好。它不短。它比其他任何东西都更有趣(如果那样的话!)。这也可能是对 F# 计算表达式的最严重滥用!

    而您将需要在某处进行模式匹配。

    谢谢斯科特!还有 Petricek。

    真正的计算表达式动物园! ;-)

    【讨论】:

    • 不错的第一篇文章。期待未来几年类似的东西!
    • @RubenBartelink 不,不是真的。反正不是第一个;-)
    • @user1758475 啊,但是你能证明你是user4946443吗:P Cmon,那里一定有互联网句柄生成器应用程序!
    • @RubenBartelink 简单的解释是我昨天晚上的电脑无法登录到这个帐户,这台电脑就是这样。我想把这个贴出来。是的,我已经研究了如何合并这些“帐户”......虽然还没有。所以现在将两个不同的帐户视为“daytime-me”和“evening-me”;-)
    • @RubenBartelink 现在证明它是同一个用户 ;-) 终于在 SO 的好人的帮助下将其合并...
    【解决方案4】:

    您可以使用来自 Microsoft.FSharp.Reflection 命名空间的反射,但它更加麻烦和缓慢。

    模式匹配可能是从可区分联合中获取数据的最简单方法。

    (另外你有一个Things 的列表,它的所有成员恰好属于Elephant union case)。

    【讨论】:

      【解决方案5】:

      有一种方法可以将模式匹配放入函数的头部(或let 绑定)。不过,它仍然是模式匹配。

      // This function takes a tuple:
      // the first argument is a Thing,
      // the second is "default" weight to be processed if the first one is NOT an Elephant
      let processElephant (Elephant weight, _ | _, weight) =
          weight
      
      let [<Literal>] NON_ELEPHANT_WEIGHT = -1
      
      // usage:
      let totalWeight =
          [Elephant (1000); Elephant (1200)]
          |> List.sumBy (fun el -> processElephant(el, NON_ELEPHANT_WEIGHT))
      

      This question 及其answers 提供更多详细信息。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-05-19
        • 1970-01-01
        • 2020-01-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-08-18
        相关资源
        最近更新 更多