【发布时间】:2014-05-07 14:52:21
【问题描述】:
我有这个通用的值容器:
open System
type Envelope<'a> = {
Id : Guid
ConversationId : Guid
Created : DateTimeOffset
Item : 'a }
我希望能够在Item 上使用模式匹配,同时仍然保留信封值。
理想情况下,我希望能够做这样的事情:
let format x =
match x with
| Envelope (CaseA x) -> // x would be Envelope<RecA>
| Envelope (CaseB x) -> // x would be Envelope<RecB>
但是,这不起作用,所以我想知道是否有办法做这样的事情?
更多详情
假设我有这些类型:
type RecA = { Text : string; Number : int }
type RecB = { Text : string; Version : Version }
type MyDU = | CaseA of RecA | CaseB of RecB
我希望能够声明 Envelope<MyDU> 类型的值,并且仍然能够匹配包含的 Item。
也许这是错误的切线,但我首先尝试使用信封的映射函数:
let mapEnvelope f x =
let y = f x.Item
{ Id = x.Id; ConversationId = x.ConversationId; Created = x.Created; Item = y }
这个函数有签名('a -> 'b) -> Envelope<'a> -> Envelope<'b>,所以它看起来像我们以前见过的东西。
这使我能够定义这个部分活动模式:
let (|Envelope|_|) (|ItemPattern|_|) x =
match x.Item with
| ItemPattern y -> x |> mapEnvelope (fun _ -> y) |> Some
| _ -> None
以及这些辅助的部分活动模式:
let (|CaseA|_|) = function | CaseA x -> x |> Some | _ -> None
let (|CaseB|_|) = function | CaseB x -> x |> Some | _ -> None
有了这些积木,我就可以写出这样的函数了:
let formatA (x : Envelope<RecA>) = sprintf "%O: %s: %O" x.Id x.Item.Text x.Item.Number
let formatB (x : Envelope<RecB>) = sprintf "%O: %s: %O" x.Id x.Item.Text x.Item.Version
let format x =
match x with
| Envelope (|CaseA|_|) y -> y |> formatA
| Envelope (|CaseB|_|) y -> y |> formatB
| _ -> ""
请注意,在第一种情况下,x 是 Envelope<RecA>,您可以看到它,因为可以从 x.Item.Number 中读取值。同样,在第二种情况下,x 是Envelope<RecB>。
另请注意,每个案例都需要从信封访问x.Id,这就是为什么我不能一开始就匹配x.Item。
这可行,但有以下缺点:
- 我需要定义一个像
(|CaseA|_|)这样的部分活动模式,以便将MyDU分解为CaseA,即使已经有一个内置模式。 - 即使我有一个有区别的联合,编译器也无法告诉我是否忘记了一个案例,因为每个模式都是部分活动模式。
有没有更好的办法?
【问题讨论】:
-
这有什么问题:让格式 x = 匹配 x.Item 与 | CaseA r -> sprintf "%O: %s: %O" x.Id r.Text r.Number | CaseB r -> sprintf "%O: %s: %O" x.Id r.Text r.Version
-
这相当于匹配
x.Item,因为它导致r成为RecA值,而不是Envelope<RecA>值。在上面的示例中,这并不重要,因为正如您所建议的,我仍然可以访问x。但是,我真正需要做的是调用一个需要Envelope<RecA>值的函数。 -
我更新了我的问题,以更好地说明为什么我需要映射信封,而不是分解它。
标签: generics f# pattern-matching