Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/fsharp/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Generics 判别并集的一般容器上的模式匹配_Generics_F#_Pattern Matching - Fatal编程技术网

Generics 判别并集的一般容器上的模式匹配

Generics 判别并集的一般容器上的模式匹配,generics,f#,pattern-matching,Generics,F#,Pattern Matching,我有一个通用的值容器: open System type Envelope<'a> = { Id : Guid ConversationId : Guid Created : DateTimeOffset Item : 'a } 然而,这不起作用,所以我想知道是否有一种方法可以做到这一点 更多详细信息 假设我有以下类型: type RecA = { Text : string; Number : int } type RecB = { Text :

我有一个通用的值容器:

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 mapEnvelope f x =
    let y = f x.Item
    { Id = x.Id; ConversationId = x.ConversationId; Created = x.Created; Item = y }
此函数具有签名
('a->'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
    | _ -> ""
let formatA(x:Envelope)=sprintf“%O:%s:%O”x.Id x.Item.Text x.Item.Number
让formatB(x:Envelope)=sprintf“%O:%s:%O”x.Id x.Item.Text x.Item.Version
让我们看看格式x=
将x与
|信封(| CaseA | | |)y->y |>格式
|信封(| CaseB | | |)y->y |>formatB
| _ -> ""
请注意,在第一种情况下,
x
是一个
信封
,您可以看到它,因为可以从
x.Item.Number
读取值。类似地,在第二种情况下,
x
Envelope

还请注意,每个案例都需要从信封访问
x.Id
,这就是为什么我不能首先匹配
x.Item

这是可行的,但有以下缺点:

  • 我需要定义一个部分活动模式,如
    (|CaseA | | | | | |)
    ,以便将
    MyDU
    分解为
    CaseA
    ,即使已经有一个内置模式
  • 即使我有一个有区别的并集,编译器也不能告诉我是否忘记了一个案例,因为每个模式都是部分活动模式

有更好的方法吗?

所以一开始这有点让人困惑,但这里有一个更简单的版本(这显然不是完美的,因为部分匹配,但我正在尝试改进它):

开放系统
键入{Item=a}
|B(B)->无
设(| UnionB | | |)x=
将x.项与
|A()->无
|B(B)->某个{Item=B}
let测试(t:包络线)=
匹配
|UnionA(t)->()//一个case-t是recA
|UnionB(t)->()//B case-t是一个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;;
let(| UnionC | UnionD |)x=
将x.项与
|A(A)->UnionC({Item=A})
|B(B)->union{Item=B}
let测试(t:包络线)=
匹配
|UnionC(t)->()//一个case-t是recA
|union d(t)->()//B case-t是一个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 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
开放系统
键入“%O:%s:%O”x.Id项。文本项。编号
|{Item=CaseB Item}as x->sprintf“%O:%s:%O”x.Id Item.Text Item.Version

x为原始值,“项目”为RecA或RecB

这似乎奏效了:

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
也许我没有完全理解你的问题,但是如果你最终需要调用一个带有
信封
的函数,你可以,因为
v
包含了这个函数

更新

在理解了这也是你第一次尝试之后,这里有一些想法

理想情况下,您可以使用如下记录语法:

let format x =
    match x with
    | Envelope (CaseA x) -> // x would be Envelope<RecA>
    | Envelope (CaseB x) -> // x would be Envelope<RecB>
let v = {x with Item = r}
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
let extractContents envelope = 
    envelope.Item, envelope 
不幸的是,它无法编译,因为泛型参数的类型不同

但是,您可以使用命名参数模拟此表达式,并使用重载使编译器决定最终类型:

#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 format x =
    match x with
    | Envelope (CaseA x) -> // x would be Envelope<RecA>
    | Envelope (CaseB x) -> // x would be Envelope<RecB>
let v = {x with Item = r}
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
let extractContents envelope = 
    envelope.Item, envelope 
当然,您必须提到
CloneWith
方法中的每个字段(在本例中是两次)。但是在调用站点上,语法更好。
可能有一些解决方案没有提到所有涉及反射的字段。

要重新表述您的问题,如果我理解正确,您希望在仍然可以访问信封标题的情况下打开信封的内容吗

在这种情况下,为什么不提取内容,然后成对地传递内容和标题呢

用于创建配对的帮助器函数可能如下所示:

let format x =
    match x with
    | Envelope (CaseA x) -> // x would be Envelope<RecA>
    | Envelope (CaseB x) -> // x would be Envelope<RecB>
let v = {x with Item = r}
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
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
顺便说一句,我会写
mapevelope
稍微简单一点:)


这有什么问题:让格式x=将x.Item与| CaseA r->sprint f“%O:%s:%O”x.Id r.Text r.Number | CaseB r->sprint f“%O:%s:%O”x.Id r.Text r.version匹配,这相当于在
x.Item
上匹配,因为它使
r
成为
RecA
值,而不是
信封
值。在上面的示例中,这并不重要,因为正如您所建议的,我仍然可以访问
x
。然而,我真正需要做的是调用一个需要
信封
值的函数。我更新了我的问题,以便更好地说明为什么我需要映射信封,而不是分解它。+1提供了一些好的建议,但是
(| union c | union d |)中的匹配表达式
给了我这个编译器警告,我实际上无法解释:“警告FS0025:此表达式上的模式匹配不完整。”另一个问题是,它迫使我为我拥有的歧视联合的每个信封定义一个活动模式……虽然我没有那么多,但我希望有一个通用的解决方案。@MarkSeemann-我在VS2012上没有收到任何编译器警告(也许你有一些旧的定义?)我找不到一个方法来解释