我有这个通用的值容器:
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
,即使已经存在内置模式。< / LI>
有更好的方法吗?
答案 0 :(得分:3)
这会做你想要的吗?
open System
type Envelope<'a> =
{ Id : Guid
ConversationId : Guid
Created : DateTimeOffset
Item : 'a }
type RecA = { Text : string; Number : int }
type RecB = { Text : string; Version : Version }
type MyDU = | CaseA of RecA | CaseB of RecB
let e =
{ Id = Guid.NewGuid();
ConversationId = Guid.NewGuid();
Created = DateTimeOffset.MinValue;
Item = CaseA {Text = ""; Number = 1 } }
match e with
| { Item = CaseA item } as x -> sprintf "%O: %s: %O" x.Id item.Text item.Number
| { Item = CaseB item } as x -> sprintf "%O: %s: %O" x.Id item.Text item.Version
x是原始值,'item'是RecA或RecB。
答案 1 :(得分:3)
这似乎有效:
let format x =
match x.Item with
| CaseA r ->
let v = mapEnvelope (fun _ -> r) x
sprintf "%O: %s: %O" v.Id v.Item.Text v.Item.Number
| CaseB r ->
let v = mapEnvelope (fun _ -> r) x
sprintf "%O: %s: %O" v.Id v.Item.Text v.Item.Version
可能我没有完全理解你的问题,但是如果你最后需要用Envelope< RecA>
来调用函数,那么你可以v
包含这个函数。
<强>更新强>
在理解这也是你的第一次尝试后,有些想法。
理想情况下,您可以使用这样的记录语法:
let v = {x with Item = r}
遗憾的是它不会编译,因为泛型参数的类型不同。
但是你可以用命名参数模仿这个表达式,并且使用重载你可以让编译器决定最终的类型:
#nowarn "0049"
open System
type Envelope<'a> =
{Id :Guid; ConversationId :Guid; Created :DateTimeOffset; Item :'a}
with
member this.CloneWith(?Id, ?ConversationId, ?Created, ?Item) = {
Id = defaultArg Id this.Id
ConversationId = defaultArg ConversationId this.ConversationId
Created = defaultArg Created this.Created
Item = defaultArg Item this.Item}
member this.CloneWith(Item, ?Id, ?ConversationId, ?Created) = {
Id = defaultArg Id this.Id
ConversationId = defaultArg ConversationId this.ConversationId
Created = defaultArg Created this.Created
Item = Item}
type RecA = { Text : string; Number : int }
type RecB = { Text : string; Version : Version }
type MyDU = | CaseA of RecA | CaseB of RecB
现在您可以使用类似的语法进行克隆,并最终更改泛型类型
let x = {
Id = Guid.NewGuid()
ConversationId = Guid.NewGuid()
Created = DateTimeOffset.Now
Item = CaseA { Text = ""; Number = 0 }}
let a = x.CloneWith(Id = Guid.NewGuid())
let b = x.CloneWith(Id = Guid.NewGuid(), Item = CaseB {Text = ""; Version = null })
let c = x.CloneWith(Id = Guid.NewGuid(), Item = {Text = ""; Version = null })
然后你的比赛可以这样写:
let format x =
match x.Item with
| CaseA r ->
let v = x.CloneWith(Item = r)
sprintf "%O: %s: %O" v.Id v.Item.Text v.Item.Number
| CaseB r ->
let v = x.CloneWith(Item = r)
sprintf "%O: %s: %O" v.Id v.Item.Text v.Item.Version
当然,您必须在CloneWith
方法中提及每个字段(在本例中为两次)。但是在调用站点,语法更好。
可能有解决方案没有提及涉及反射的所有领域。
答案 2 :(得分:3)
要重新解释您的问题,如果我理解正确,您想要打开信封的内容,同时仍然可以访问信封的标题吗? / p>
在这种情况下,为什么不提取内容,然后将内容和标题作为一对传递?
创建对的辅助函数可能如下所示:
let extractContents envelope =
envelope.Item, envelope
然后您的格式代码将更改为处理标题和内容:
let formatA header (contents:RecA) =
sprintf "%O: %s: %O" header.Id contents.Text contents.Number
let formatB header (contents:RecB) =
sprintf "%O: %s: %O" header.Id contents.Text contents.Version
有了这个,你可以正常使用模式匹配:
let format envelope =
match (extractContents envelope) with
| CaseA recA, envA -> formatA envA recA
| CaseB recB, envB -> formatB envB recB
这里是完整的代码:
open System
type Envelope<'a> = {
Id : Guid
ConversationId : Guid
Created : DateTimeOffset
Item : 'a }
type RecA = { Text : string; Number : int }
type RecB = { Text : string; Version : Version }
type MyDU = | CaseA of RecA | CaseB of RecB
let extractContents envelope =
envelope.Item, envelope
let formatA header (contents:RecA) =
sprintf "%O: %s: %O" header.Id contents.Text contents.Number
let formatB header (contents:RecB) =
sprintf "%O: %s: %O" header.Id contents.Text contents.Version
let format envelope =
match (extractContents envelope) with
| CaseA recA, envA -> formatA envA recA
| CaseB recB, envB -> formatB envB recB
如果我这么做了,我可能会为标题创建一个单独的记录类型,这会使这更简单。
let extractContents envelope =
envelope.Item, envelope.Header
BTW我会稍微简单地写一下mapEnvelope
:)
let mapEnvelope f envelope =
{envelope with Item = f envelope.Item}
答案 3 :(得分:2)
首先这有点令人困惑,但这里有一个更简单的版本(由于部分匹配,这显然不完美,但我正在努力改进它):
open System
type Envelope<'a> = {
Item : 'a }
type RecA = { Text : string; Number : int }
type RecB = { Text : string; Version : Version }
type MyDu = |A of RecA |B of RecB
let (|UnionA|_|) x =
match x.Item with
|A(a) -> Some{Item=a}
|B(b) -> None
let (|UnionB|_|) x =
match x.Item with
|A(_) -> None
|B(b) -> Some{Item=b}
let test (t:Envelope<MyDu>) =
match t with
|UnionA(t) -> () //A case - T is a recA
|UnionB(t) -> () //B case - T is a recB
一般问题是我们想要一个同时返回Envelope<RecA>
和Envelope<RecB>
的函数(这不是很简单)。
修改的
事实证明这很容易:
let (|UnionC|UnionD|) x =
match x.Item with
|A(a) -> UnionC({Item=a})
|B(b) -> UnionD{Item=b}
let test (t:Envelope<MyDu>) =
match t with
|UnionC(t) -> () //A case - T is a recA
|UnionD(t) -> () //B case - T is a recB;;