Mar*_*ann 13 generics f# pattern-matching
我有这个通用的值容器:
open System
type Envelope<'a> = {
Id : Guid
ConversationId : Guid
Created : DateTimeOffset
Item : 'a }
Run Code Online (Sandbox Code Playgroud)
我想能够使用模式匹配上Item,同时仍保留包络值.
理想情况下,我希望能够做到这样的事情:
let format x =
match x with
| Envelope (CaseA x) -> // x would be Envelope<RecA>
| Envelope (CaseB x) -> // x would be Envelope<RecB>
Run Code Online (Sandbox Code Playgroud)
但是,这不起作用,所以我想知道是否有办法做这样的事情?
更多详情
假设我有这些类型:
type RecA = { Text : string; Number : int }
type RecB = { Text : string; Version : Version }
type MyDU = | CaseA of RecA | CaseB of RecB
Run Code Online (Sandbox Code Playgroud)
我希望能够声明类型的值,Envelope<MyDU>并仍然能够匹配包含Item.
也许这是在错误的切线上,但我首先尝试使用信封的映射函数:
let mapEnvelope f x =
let y = f x.Item
{ Id = x.Id; ConversationId = x.ConversationId; Created = x.Created; Item = y }
Run Code Online (Sandbox Code Playgroud)
这个函数有签名('a -> 'b) -> Envelope<'a> -> Envelope<'b>,所以看起来像我们以前见过的东西.
这使我能够定义此部分活动模式:
let (|Envelope|_|) (|ItemPattern|_|) x =
match x.Item with
| ItemPattern y -> x |> mapEnvelope (fun _ -> y) |> Some
| _ -> None
Run Code Online (Sandbox Code Playgroud)
和这些辅助部分活动模式:
let (|CaseA|_|) = function | CaseA x -> x |> Some | _ -> None
let (|CaseB|_|) = function | CaseB x -> x |> Some | _ -> None
Run Code Online (Sandbox Code Playgroud)
使用这些构建块,我可以编写如下函数:
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
| _ -> ""
Run Code Online (Sandbox Code Playgroud)
请注意,在第一种情况下,x是一个Envelope<RecA>,您可以看到,因为它可以读取值x.Item.Number.同样,在第二种情况下,x是Envelope<RecB>.
另请注意,每个案例都需要x.Id从信封中访问,这就是为什么我不能仅仅匹配x.Item开始的原因.
这有效,但有以下缺点:
(|CaseA|_|),以便分解MyDU为CaseA,即使已经有一个内置的模式.有没有更好的办法?
这似乎有效:
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
Run Code Online (Sandbox Code Playgroud)
可能我没有完全理解你的问题,但如果你最终需要用 an 调用一个函数,Envelope< RecA> 你可以,因为这就是v包含的内容。
更新
以下是了解这也是您的第一次尝试后的一些想法。
理想情况下,您可以使用如下所示的记录语法:
let v = {x with Item = r}
Run Code Online (Sandbox Code Playgroud)
不幸的是它无法编译,因为通用参数是不同的类型。
但是,您可以使用命名参数来模拟此表达式,并且使用重载可以使编译器决定最终类型:
#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
Run Code Online (Sandbox Code Playgroud)
现在您可以使用类似的语法进行克隆并最终更改泛型类型
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 })
Run Code Online (Sandbox Code Playgroud)
那么你的匹配可以这样写:
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
Run Code Online (Sandbox Code Playgroud)
当然,您必须提及CloneWith方法中的每个字段(在本例中为两次)。但在调用站点,语法更好。可能有一些解决方案没有提及涉及反射的所有领域。
| 归档时间: |
|
| 查看次数: |
557 次 |
| 最近记录: |