简体   繁体   English

F# 模式与泛型类型匹配。 可能的?

[英]F# pattern matching with generic types. Possible?

I have the following code:我有以下代码:

[<AbstractClass>]
type Effect<'a>() =
    class end

type Input<'a>(chan : Channel<'a>, cont : 'a -> Effect<'a>) = 
    inherit Effect<'a>()
    member this.Chan = chan
    member this.Cont = cont

type Output<'a>(value : 'a, chan : Channel<'a>, cont : unit -> Effect<'a>) =
    inherit Effect<'a>()
    member this.Value = value
    member this.Chan = chan
    member this.Cont = cont

type Parallel<'a, 'b>(eff1 : Effect<'a>, eff2 : Effect<'b>) =
    inherit Effect<'a * 'b>()
    member this.Eff1 = eff1
    member this.Eff2 = eff2

type Return<'a>(value : 'a) =
    inherit Effect<'a>()
    member this.Value = value

let Send(value, chan, cont) = Output(value, chan, cont)
let Receive(chan, cont) = Input(chan, cont)

let rec NaiveEval (eff : Effect<'a>) =
    match eff with 
    | :? Input<'a> as input           -> ()
    | :? Output<'a> as output         -> ()
    | :? Parallel<'a, 'b> as par      -> () // ERROR: FS0193: Type constraint mismatch. The type 'Parallel<'a,'b> is not compatible with type 'Effect<'a>'.
    | :? Return<'a> as ret            -> ()
    | _                               -> failwith "Unsupported effect!"

I want to be able to pattern match in some way on the different subclasses of Effect as seen in the NaiveEval function.我希望能够以某种方式对 Effect 的不同子类进行模式匹配,如 NaiveEval function 中所示。 The pattern match on Parallel<'a, 'b> gives the following error: Parallel<'a, 'b>上的模式匹配给出以下错误:

FS0193: Type constraint mismatch. FS0193:类型约束不匹配。 The type 'Parallel<'a,'b> is not compatible with type 'Effect<'a>'. 'Parallel<'a,'b> 类型与'Effect<'a>' 类型不兼容。

I would assume that in this case 'a would be inferred to be 'a * 'b but I guess that is not what's happening.我会假设在这种情况下'a将被推断为'a * 'b但我想这不是正在发生的事情。

Any help is appreciated.任何帮助表示赞赏。 Thanks.谢谢。

EDIT to Mark's answer:编辑马克的回答:

So as far as I understand, what I am trying to do is not possible.据我了解,我想做的事情是不可能的。 I started out with a discriminated union, but I thought the type hierarchy would fix my issue.我从一个有区别的联合开始,但我认为类型层次结构可以解决我的问题。 I guess not.我猜不是。

I used this union before:我以前使用过这个联合:

type Effect<'a> =
     | Input of Channel<'a> * ('a -> Effect<'a>)
     | Output of 'a * Channel<'a> * (unit -> Effect<'a>)
     | Parallel of Effect<'a> * Effect<'a>
     | Return of 'a

I used to use this type, but I wanted to parallel to be: Parallel of Effect<'a> * Effect<'b> and not just both 'a.我曾经使用这种类型,但我想并行为: Parallel of Effect<'a> * Effect<'b> 而不仅仅是两个 'a。 I assume this is not possible either?我认为这也不可能吗?

This is because the type parameter in Parallel is defined as corresponding to 'a * 'b .这是因为Parallel中的类型参数被定义为对应于'a * 'b In other words, the single type parameter of Effect<'a> is here 'a * 'b .换句话说, Effect<'a>的单一类型参数在这里是'a * 'b

Perhaps it's easier to understand how these correspond if you rename the type variables to 'b and 'c :如果将类型变量重命名为'b'c ,可能更容易理解它们之间的对应关系:

type Parallel<'b, 'c>(eff1 : Effect<'b>, eff2 : Effect<'c>) =
    inherit Effect<'b * 'c>()
    member this.Eff1 = eff1
    member this.Eff2 = eff2

Now you have that in the Parallel case, 'a = 'b * 'c .现在您在Parallel情况下有了'a = 'b * 'c

The compiler error is exactly because you're trying to redefine what 'a is.编译器错误正是因为您试图重新定义'a是什么。 Essentially, you're trying to insist that 'a = 'a * 'b .本质上,您试图坚持'a = 'a * 'b This doesn't work because you have 'a on both sides of the equals sign.这不起作用,因为等号两边都有'a

(We could imagine a more sophisticated type system that would allow this. This would essentially be like saying that x = x + y , which is only possible if y = 0 . In the world of algebraic data types, this would imply that 'b would be () ( unit )... but the F# type system can't infer that, and in any case it's probably not what you want.) (我们可以想象一个更复杂的类型系统允许这样做。这基本上就像说x = x + y ,这只有在y = 0时才有可能。在代数数据类型的世界中,这意味着'b将是() ( unit )... 但是 F# 类型的系统无法推断出这一点,而且无论如何它可能不是你想要的。)

You can exchange the error for a compiler warning by explicitly using the renamed type arguments:您可以通过显式使用重命名的类型 arguments 将错误换成编译器警告:

let rec NaiveEval (eff : Effect<'a>) =
    match eff with 
    | :? Input<'a> as input           -> ()
    | :? Output<'a> as output         -> ()
    | :? Parallel<'b, 'c> as par      -> () // Warning FS0064
    | :? Return<'a> as ret            -> ()
    | _                               -> failwith "Unsupported effect!"

The warning, however, is:然而,警告是:

This construct causes code to be less generic than indicated by the type annotations.这种构造导致代码不像类型注释所指示的那样通用。 The type variable 'a has been constrained to be type ''b * 'c'.类型变量 'a 已被限制为类型 ''b * 'c'。

In other words, the NaiveEval function can only evaluate Effect<'b * 'c> - probably still not what you want..?换句话说, NaiveEval function 只能评估Effect<'b * 'c> - 可能仍然不是你想要的......?

This may still be a little difficult to accept, but imagine that you'd want to do something with par in the Parallel case.这可能仍然有点难以接受,但想象一下您想在Parallel案例中使用par一些事情。 This, for example, doesn't compile because, once again, the types don't line up:例如,这不会编译,因为再一次,类型不对齐:

| :? Parallel<'b, 'c> as par      ->
    NaiveEval (par.Eff2)

Here, par.Eff2 has the type 'c , but you're currently 'inside' a function of the type Effect<'b * 'c> -> unit .在这里, par.Eff2的类型为'c ,但您当前“在”一个 function 类型为Effect<'b * 'c> -> unit You can't recurse into it, and compiler tells you so:你不能递归到它,编译器会告诉你:

Error FS0001 The types ''c' and ''b * 'c' cannot be unified.错误 FS0001 ''c' 和 ''b * 'c' 类型不能统一。


BTW, have you considered defining a discriminated union instead of a type hierarchy?顺便说一句,您是否考虑过定义有区别的联合而不是类型层次结构?

I have many moons ago dug into tagless encodings, but I can't seem to get this to work in this context (actually i do further down).我有很多个月前研究过无标签编码,但我似乎无法让它在这种情况下工作(实际上我做得更进一步)。

I got something to work using good old fashioned and underated visitor pattern (which is closely related, excuse my idiosyncratic F# its been a while, I think my method signatures should be tupled really, but i dont do that until the final version).我使用良好的老式和低调的访问者模式(这是密切相关的,请原谅我的特殊 F# 已经有一段时间了,我认为我的方法签名应该是元组的,但我直到最终版本才这样做)。

foo outputs 6, and recursively evaluates a parallel effect. foo 输出 6,并递归评估并行效果。

type Channel<'a>() = class end

type IEffectVisitor<'ret> =
    abstract member VisitInput<'a> : Input<'a> -> 'ret
    abstract member VisitOutput<'a> : Output<'a> -> 'ret
    abstract member VisitParallel<'a,'b> : Parallel<'a,'b> -> 'ret 
    abstract member VisitReturn<'a> : Return<'a> -> 'ret
and IEffect<'a> = 
    abstract member Accept<'ret> : IEffectVisitor<'ret> -> 'ret
and Input<'a>(chan : Channel<'a>, cont : 'a -> IEffect<'a>) = 
    interface IEffect<'a> with
        member this.Accept(v) = v.VisitInput(this)
    member this.Chan = chan
    member this.Cont = cont
and Output<'a>(value : 'a, chan : Channel<'a>, cont : unit -> IEffect<'a>) =
    interface IEffect<'a> with
        member this.Accept(v) = v.VisitOutput(this)
    member this.Value = value
    member this.Chan = chan
    member this.Cont = cont
and Parallel<'a, 'b>(eff1 : IEffect<'a>, eff2 : IEffect<'b>) =
    interface IEffect<'a> with
        member this.Accept(v) = v.VisitParallel(this)
    member this.Eff1 = eff1
    member this.Eff2 = eff2
and Return<'a>(value : 'a) =
    interface IEffect<'a> with
        member this.Accept(v) = v.VisitReturn(this)
    member this.Value = value

let rec NaiveEval (eff : IEffect<'a>) : int =
    eff.Accept(
        { new IEffectVisitor<int> with
            member this.VisitOutput x = 
                1
            member this.VisitParallel x =                     
                x.Eff1.Accept(this) + x.Eff2.Accept(this)
            member this.VisitReturn x = 
                3
            member __.VisitInput x = 
                4 })

let foo() = 
    let x = Parallel(Return 1, Return "a")
    printfn "%A" <| NaiveEval(x)
    ()

actually thinking about this, I went back to it, you can lose the type parameter on the effect all together without (in the example) any loss of type safety, and this paves the way for a more tagless like implementation.实际上考虑到这一点,我又回到了它,您可以一起丢失效果上的类型参数,而不会(在示例中)任何类型安全性的损失,这为更无标记的实现铺平了道路。

type Channel<'a>() = class end

type IEffectVisitor<'ret> =
    abstract member VisitInput<'a> : Input<'a> -> 'ret
    abstract member VisitOutput<'a> : Output<'a> -> 'ret
    abstract member VisitParallel<'a,'b> : Parallel -> 'ret 
    abstract member VisitReturn<'a> : Return<'a> -> 'ret
and IEffect = 
    abstract member Accept<'ret> : IEffectVisitor<'ret> -> 'ret
and Input<'a>(chan : Channel<'a>, cont : 'a -> IEffect) = 
    interface IEffect with
        member this.Accept(v) = v.VisitInput(this)
    member this.Chan = chan
    member this.Cont = cont
and Output<'a>(value : 'a, chan : Channel<'a>, cont : unit -> IEffect) =
    interface IEffect with
        member this.Accept(v) = v.VisitOutput(this)
    member this.Value = value
    member this.Chan = chan
    member this.Cont = cont
and Parallel(eff1 : IEffect, eff2 : IEffect) =
    interface IEffect with
        member this.Accept(v) = v.VisitParallel(this)
    member this.Eff1 = eff1
    member this.Eff2 = eff2
and Return<'a>(value : 'a) =
    interface IEffect with
        member this.Accept(v) = v.VisitReturn(this)
    member this.Value = value


module Foo = 
    let Send(value, chan, cont) = Output(value, chan, cont)
    let Receive(chan, cont) = Input(chan, cont)

    let rec NaiveEval (eff : IEffect) : int =
        eff.Accept(
            { new IEffectVisitor<int> with
                member this.VisitOutput x = 
                    1
                member this.VisitParallel x =                     
                    x.Eff1.Accept(this) + x.Eff2.Accept(this)
                member this.VisitReturn x = 
                    3
                member __.VisitInput x = 
                    4 })

    let foo() = 
        let x = Parallel(Return 1, Return "a")
        printfn "%A" <| NaiveEval(x)
        ()

which leads you onto something a bit tagless....(I always love this bit, becuase of the realisation that a factory pattern is actually a special case of a visitor - in a way, and all your classes and DUs evaporate into functions/methods).这会引导你进入一些无标签的东西......(我总是喜欢这一点,因为意识到工厂模式实际上是访问者的特殊情况 - 在某种程度上,你所有的类和 DU 都蒸发为函数/方法)。

type Channel<'a>() = class end

type IEffectAlgebra<'retInput,'retOutput,'retParallel,'retReturn> =
    abstract member Input<'a> : Channel<'a> * ('a -> IEffect) -> 'retInput
    abstract member Output<'a> : 'a * Channel<'a> * (unit -> IEffect) -> 'retOutput
    abstract member Parallel<'a,'b> : IEffect * IEffect -> 'retParallel
    abstract member Return<'a> : 'a -> 'retReturn
and IEffect = 
    abstract member Accept<'ret> : IEffectAlgebra<'ret,'ret,'ret,'ret> -> 'ret

type IEffectVisitor<'ret> = IEffectAlgebra<'ret,'ret,'ret,'ret>
type IEffectFactory = IEffectVisitor<IEffect>

let effectFactory = 
    { new IEffectFactory with
        member __.Input(arg1, arg2) = 
            { new IEffect with 
                member __.Accept(v) = v.Input(arg1, arg2) }
        member __.Output(arg1, arg2, arg3) = 
            { new IEffect with 
                member __.Accept(v) = v.Output(arg1, arg2, arg3) }
        member __.Parallel(arg1, arg2) = 
            { new IEffect with 
                member __.Accept(v) = v.Parallel(arg1, arg2) }
        member __.Return(arg1) = 
            { new IEffect with 
                member __.Accept(v) = v.Return arg1 }
    }

module Foo = 
    let Send(value, chan, cont) = effectFactory.Output(value, chan, cont)
    let Receive(chan, cont) = effectFactory.Input(chan, cont)

    let rec NaiveEval (eff : IEffect) : int =
        eff.Accept(
            { new IEffectVisitor<int> with
                  member __.Input(_, _) = 1
                  member __.Output(_, _, _) = 2
                  member this.Parallel(arg1, arg2) = 
                    arg1.Accept this + arg2.Accept this
                  member __.Return(_) = 4 })

    let foo() = 
        let x = effectFactory.Parallel(effectFactory.Return 1, effectFactory.Return "a")
        printfn "%A" <| NaiveEval(x)
        ()

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 通用参数的F#模式匹配 - F# Pattern Matching on Generic Parameter 泛型类型的F#问题 - F# Problems With Generic Types F#中泛型类型的通用人工操作 - Generic artihmetical operations on generic types in F# 为什么某些表达式中的泛型类型在F#模式匹配中匹配为obj? - Why is generic type in some expressions is matched as obj in F# pattern matching? f# 模式匹配序列:? seq&lt;_&gt; (IEnumerable) - f# pattern matching sequence :? seq<_> (IEnumerable) F#重载泛型类型的运算符 - F# overloading operators for generic types 是否可以在 F# 中为参数化泛型类型定义扩展方法(就像在 C# 中一样) - Is it possible to define extension methods for parameterized generic types in F# (like in C#) f# 为多种类型和重载运算符定义通用 function - f# define generic function for multiple types and overloaded operators 是否有可能有一个具有不同类型参数的泛型类型列表(来自 class 层次结构),通过在 F# 中使用强制转换保存在同一个列表中? - Is it possible to have a list of generic types (from class hierarchy) with different type parameters, kept in the same list by use of casting in F#? F#嵌套泛型类型与实现类型不兼容 - F# nested generic types not compatible with implemented type
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM