是否可以拥有一个具有不同类型参数的泛型类型列表(来自类层次结构),并通过在 F# 中使用强制转换将其保留在同一列表中?

发布于 2025-01-13 19:09:34 字数 2116 浏览 0 评论 0原文

我有一个由 F# 中的类层次结构描述的效果层次结构。为简单起见,这些类如下所示: 我的

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

and EffectA<'a, 'b>() =
   inherit Effect<'a, 'b>() 

and EffectB<'a, 'b>() =
   inherit Effect<'a, 'b>()

想法是,我将有一个函数来解释此类效果,其中每种效果都会在系统中引起某种副作用。

我想要有许多系统线程,标记为工作线程,每个线程都充当解释器,即每个线程都可以相互独立地解释效果。

我希望有一个如上所示的效果队列,指示已准备好由工作线程解释的效果,以便工作人员可以获取它们并解释它们。

现在,我的问题是,由于这些效果在“a”和“b”上是通用的,因此我无法为所有效果提供一个队列。

例如,这是行不通的:

let add (eff : Effect<obj, obj>) (effs : Effect<obj, obj> list) =
    eff :: effs

let test =
    let effA = EffectA<int, string>()
    let effB = EffectB<string, float>()
    let effs = [effA]
    let newEffs = add effB effs
    ()

回顾一下:我想要一种方法来拥有 Effects的队列(或任何类型的集合)。 (或任何其他类型,只要它们可以一起在集合中),其中可以保留所有效果,然后通过某种方式从列表中检索它们,然后将它们投射回 Effect<'a, 'b>;与他们原来的类型。

我一直在考虑使用灵活的类型,但到目前为止尝试尚未成功。

我希望这是有道理的。谢谢。

编辑: @JL0PD 建议添加一个非通用基类,这是我尝试过的。它确实有效。测试功能按预期工作。

然而,我的问题是,通过这种方法,我如何跟踪每个效果的原始类型,以便我可以正确地投射它们?

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

type Effect<'a, 'b>(a : 'a, b : 'b) = 
    inherit Effect()
    member _.A = a
    member _.B = b

and EffectA<'a, 'b>(a : 'a, b : 'b) =
   inherit Effect<'a, 'b>(a, b) 

and EffectB<'a, 'b>(a : 'a, b : 'b) =
   inherit Effect<'a, 'b>(a, b)

let add (eff : Effect) (effs : Effect list) =
    eff :: effs

let test =
    let effA = EffectA<int, string>(10, "abc")
    let effB = EffectB<string, float>("xyz", 1.0)
    let effs : Effect list = [effA]
    let newEffs = add effB effs
    let head = List.head newEffs :?> Effect<string, float>
    let otherEff = List.head (List.rev newEffs) :?> Effect<int, string>
    printfn $"head -> A: %A{head.A}, B: %A{head.B}"
    printfn $"otherEff -> A: %A{otherEff.A}, B: %A{otherEff.B}"
    ()

I have an Effect hierarchy described by a class hierarchy in F#. For simplicity, the classes look as follows:

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

and EffectA<'a, 'b>() =
   inherit Effect<'a, 'b>() 

and EffectB<'a, 'b>() =
   inherit Effect<'a, 'b>()

The idea is that I will have a function to interpret such effects, where each effect will cause some kind of side effect in the system.

I would like to have a number of system threads, labeled worker threads, that each serve as an interpreter, i.e. each thread can interpret an effect indepedently of one another.

I would like to have a queue of such effects as shown above, indicating effects that are ready to be interpreted by the worker threads, such that the workers can fetch them and interpret them.

Now, my problem is, since these effects are generic on 'a and 'b, I am unable to have a single queue for all effects.

For example, this wouldn't work:

let add (eff : Effect<obj, obj>) (effs : Effect<obj, obj> list) =
    eff :: effs

let test =
    let effA = EffectA<int, string>()
    let effB = EffectB<string, float>()
    let effs = [effA]
    let newEffs = add effB effs
    ()

To recap: I would like a way to have a queue (or any kind of collection) of Effects<obj, obj> (or any other types, as long as they can be in the collection together) where ALL effects could be kept, and then some way to retrieve them from the list, and then cast them back to Effect<'a, 'b> with their original types.

I have been looking into using flexible types, but have so far been unsuccessful in trying.

I hope it makes sense. Thanks.

EDIT: @JL0PD suggested to add a non-generic base class, which is something I've attempted. It does actually work. The test function works as expected.

My question, however, with this approach is that how do I keep track of the original types of each effect, such that I can cast them correctly?

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

type Effect<'a, 'b>(a : 'a, b : 'b) = 
    inherit Effect()
    member _.A = a
    member _.B = b

and EffectA<'a, 'b>(a : 'a, b : 'b) =
   inherit Effect<'a, 'b>(a, b) 

and EffectB<'a, 'b>(a : 'a, b : 'b) =
   inherit Effect<'a, 'b>(a, b)

let add (eff : Effect) (effs : Effect list) =
    eff :: effs

let test =
    let effA = EffectA<int, string>(10, "abc")
    let effB = EffectB<string, float>("xyz", 1.0)
    let effs : Effect list = [effA]
    let newEffs = add effB effs
    let head = List.head newEffs :?> Effect<string, float>
    let otherEff = List.head (List.rev newEffs) :?> Effect<int, string>
    printfn 
quot;head -> A: %A{head.A}, B: %A{head.B}"
    printfn 
quot;otherEff -> A: %A{otherEff.A}, B: %A{otherEff.B}"
    ()

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

忘年祭陌 2025-01-20 19:09:34

因此,在函数式编程世界中,您需要一种称为存在类型的东西。它可以在不诉诸不安全强制转换的情况下实现(真正模拟),但代价是一定量的样板代码。

在 F# 中实现此功能的最简单机制是使用访问者设计模式,在某些方面,此机制与 DU 非常相似,但允许您将值类型参数“隐藏”在被访问接口的外观后面。
(还有其他变体,但让我们坚持 OO 模式),

首先定义将“隐藏”具体类型的接口,即 IEffect。
然后定义允许您“匹配”IEffect 的访问者接口。
您想要用来模拟效果的每种类型。

type IEffect = 
    abstract member AcceptVistor<'c> : IEffectVisitor<'c> -> 'c
and IEffectVisitor<'c> =
    abstract member VisitEffectA<'a,'b> : EffectA<'a,'b> -> 'c
    abstract member VisitEffectB<'a,'b> : EffectB<'a,'b> -> 'c
    abstract member VisitEffectC<'a,'b> : EffectC<'a,'b> -> 'c
and EffectA<'a,'b>() =
    member val Name = "EffectA"
    interface IEffect with
        member this.AcceptVistor v = 
            v.VisitEffectA this
and EffectB<'a,'b>() =
    member val Title = "EffectB"
    interface IEffect with
        member this.AcceptVistor v = 
            v.VisitEffectB this
and EffectC<'a,'b>() =
    member val Caption = "EffectC"
    interface IEffect with
        member this.AcceptVistor v = 
            v.VisitEffectC this

定义“IEffect”注释的数组

let effects : IEffect[] = [| 
    EffectA<string,int>() :> IEffect
    EffectB<int,string>()
    EffectC<float,double>() |]

现在,您可以使用“隐藏”的不同类型参数 。该数组属于“基本”接口 IEffect。

现在您可以处理这些效果

effects
|> Array.iter (fun effect -> 
    let message = 
        effect.AcceptVistor 
                { new IEffectVisitor<string> with 
                    member __.VisitEffectA eff = eff.Name
                    member __.VisitEffectB eff = eff.Title
                    member __.VisitEffectC eff = eff.Caption }
    printfn "%s" message)

请注意,无需强制转换,访问者“知道”每个效果的类型,包括类型参数。

这为您提供了比标准 DU 稍微强大的机制,但需要付出所有样板的成本(即,除非必须,否则我不建议使用此机制)。

So in the functional programming world you want something called existential types. It can be achieved (simulated really) without resorting to unsafe casts, but at the cost of a certain amount of boilerplate code.

The simplest mechanism to implement this in F# is to use the visitor design pattern, in some ways this mechanism is very similar to DUs but allows you to 'hide' your values type parameters behind the facade of the visited interface.
(there are other variations of this, but lets stick to the OO pattern),

First you define the interface behind which your concrete types will 'hide', i.e. IEffect.
Then define the vistor interface which will allow you to 'match' an IEffect.
The each of the types you want to use to model your effects.

type IEffect = 
    abstract member AcceptVistor<'c> : IEffectVisitor<'c> -> 'c
and IEffectVisitor<'c> =
    abstract member VisitEffectA<'a,'b> : EffectA<'a,'b> -> 'c
    abstract member VisitEffectB<'a,'b> : EffectB<'a,'b> -> 'c
    abstract member VisitEffectC<'a,'b> : EffectC<'a,'b> -> 'c
and EffectA<'a,'b>() =
    member val Name = "EffectA"
    interface IEffect with
        member this.AcceptVistor v = 
            v.VisitEffectA this
and EffectB<'a,'b>() =
    member val Title = "EffectB"
    interface IEffect with
        member this.AcceptVistor v = 
            v.VisitEffectB this
and EffectC<'a,'b>() =
    member val Caption = "EffectC"
    interface IEffect with
        member this.AcceptVistor v = 
            v.VisitEffectC this

now you can define an array of 'IEffect's

let effects : IEffect[] = [| 
    EffectA<string,int>() :> IEffect
    EffectB<int,string>()
    EffectC<float,double>() |]

note all with different type parameters that become 'hidden'. The array is of the 'base' interface IEffect.

now you can process these effects

effects
|> Array.iter (fun effect -> 
    let message = 
        effect.AcceptVistor 
                { new IEffectVisitor<string> with 
                    member __.VisitEffectA eff = eff.Name
                    member __.VisitEffectB eff = eff.Title
                    member __.VisitEffectC eff = eff.Caption }
    printfn "%s" message)

Note there is no need to cast, the visitor 'knows' the type of each effect, including the type parameters.

this gives you a slightly more powerful mechanism than standard DUs, but with the costs of all the boilerplate (i.e. I wouldnt recommend using this mechanism unless you have to).

苍风燃霜 2025-01-20 19:09:34

我认为这可能有效。我不确定这是否是最优雅的解决方案,但它似乎完成了它的工作。如果您知道从 Effect 派生的类的泛型参数类型,则可以创建一个 DU 泛型类型,其中包含跨子类型使用的所有类型的联合(集)。


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

and EffectA<'a, 'b>() =
   inherit Effect<'a, 'b>() 

and EffectB<'a, 'b>() =
   inherit Effect<'a, 'b>()

//create a DU type with all types that might appear in the DU elements
type EffectDU<'a, 'b, 'c, 'd> = 
    | EffectA of EffectA<'a, 'b> 
    | EffectB of EffectB<'c, 'd>


let add (eff : EffectDU<'a, 'b, 'c, 'd>) (effs : EffectDU<'a, 'b, 'c, 'd> list) =
    eff :: effs
   
let createTestList =
    let effA = EffectA (new EffectA<int, string>())
    let effB = EffectB (new EffectB<string, float>())
    let effC = EffectA (new EffectA<int, string>())
    let effs = [effA]
    let newEffs = [] |> add effA |> add effB |> add effC
    newEffs

let lst = createTestList

let processing = lst |> List.map (fun el -> match el with
                                            | EffectA a -> "Processing Effect A"
                                            | EffectB b -> "Processing Effect B"
                                            )

printfn "%A" processing 

这样您就可以避免匹配表达式中的反射和笨拙的类型检查。

I think that this might work. I am not sure if it is the most elegant solution, but it appears to do its job. If you know the types of generic parameters of the classes deriving from Effect, you can create a DU generic type that contains a union (set) of all types used across subtypes.


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

and EffectA<'a, 'b>() =
   inherit Effect<'a, 'b>() 

and EffectB<'a, 'b>() =
   inherit Effect<'a, 'b>()

//create a DU type with all types that might appear in the DU elements
type EffectDU<'a, 'b, 'c, 'd> = 
    | EffectA of EffectA<'a, 'b> 
    | EffectB of EffectB<'c, 'd>


let add (eff : EffectDU<'a, 'b, 'c, 'd>) (effs : EffectDU<'a, 'b, 'c, 'd> list) =
    eff :: effs
   
let createTestList =
    let effA = EffectA (new EffectA<int, string>())
    let effB = EffectB (new EffectB<string, float>())
    let effC = EffectA (new EffectA<int, string>())
    let effs = [effA]
    let newEffs = [] |> add effA |> add effB |> add effC
    newEffs

let lst = createTestList

let processing = lst |> List.map (fun el -> match el with
                                            | EffectA a -> "Processing Effect A"
                                            | EffectB b -> "Processing Effect B"
                                            )

printfn "%A" processing 

This way you can avoid reflection and unwieldy type checking in your match expressions.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文