F#:将受歧视的联合和类层次结构结合在一起?

发布于 2024-12-15 12:53:19 字数 1140 浏览 1 评论 0原文

假设我有一个重要的类层次结构:

Tag
    ControlFlowTag
        IfTag
        ForTag
    JumpTag
    HTMLTag
        DivTag

并且我想制作一个散布这些和字符串的列表。

let MyList = [tagA, tagB, "some text", tagC]

我以为我可以区分联合它,

type Node = 
    | Tag of Tag
    | String of String

let MyList: list<Node> = [tagA, tagB, "some text", tagC]

但可惜的是,如果没有它,它就无法工作

let MyList: list<Node> = [Tag tagA, Tag tagB, String "some text", Tag tagC]

。 显然,Node 中描述的 Tag 和 String 是正交的,并且与现有的 Tag/String 类分开。将鼠标悬停给我的类型为 Node.TagNode.String,这不是我想要的。

我现在拥有的是一个函数t,它创建了一个继承自TagStringTag,这给了我

let MyList : list<Tag> = [tagA, tagB, t"some text", tagC]

相当不错的功能,但是额外的t 增加了视觉噪音。我真正想要的是一个强类型的“两种不同类型的列表”,我可以使用 match 语句来使用它。我认为这就是受歧视联合的要点,但它们无法使用现有的类型层次结构是一个问题,因为现有的层次结构(在本例中为 Tag)足够复杂,我认为完整的 OO 继承方法类型子集比纯粹的可区分联合方法更清晰

一种选择是仅将其设为 obj 列表并在 match 之前/期间强制转换所有内容,但这不是真的非常 好的。还有其他方法吗?

Let's say I have a significant class hierarchy:

Tag
    ControlFlowTag
        IfTag
        ForTag
    JumpTag
    HTMLTag
        DivTag

and I want to make a list interspersed with these and strings.

let MyList = [tagA, tagB, "some text", tagC]

and I thought I could discriminated union it

type Node = 
    | Tag of Tag
    | String of String

let MyList: list<Node> = [tagA, tagB, "some text", tagC]

but alas, it doesn't work without

let MyList: list<Node> = [Tag tagA, Tag tagB, String "some text", Tag tagC]

Obviously the Tag and String described in Node are orthogonal and separate from the existing Tag/String classes. Mousing over gives me the types as Node.Tag and Node.String, which isn't what I want.

What I have now is a function t which creates a StringTag which inherits from Tag, giving me

let MyList : list<Tag> = [tagA, tagB, t"some text", tagC]

which is pretty nice, but the extra t adds to the visual noise. What I actually want is a strongly typed "list of two different types" which I could work with using match statements. I thought that was the point of Discriminated Unions, but their inability to use existing type hierarchies is a problem, since the existing hierarchy (in this case Tag) is complex enough I think a full OO-inheritence approach to that subset of types is clearer than a pure Discriminated Union approach

One option is to just make it a list of obj and cast everything before/during the match, but that's not really very nice. Are there any other approaches?

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

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

发布评论

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

评论(3

顾北清歌寒 2024-12-22 12:53:19

如果您有两个不同的 DU,那么

type Node = 
  | Tag of Tag
  | String of String

编译

type Foo = 
  | Bar of Tag
  | Name of String

器如何知道以下列表属于哪种类型?

[tagA; tagB; "some text"; tagC]

正如斯维克所说,鉴别器是必要的。如果您使用类,则需要向上转换为基本类型,因此我不确定是否可以节省击键次数。

如果您正在使用字典,这里是一个不错的选择减少拳击的语法噪音。也许你可以对列表做类似的事情。

If you had two different DUs, say

type Node = 
  | Tag of Tag
  | String of String

and

type Foo = 
  | Bar of Tag
  | Name of String

how would the compiler know of which type the following list is?

[tagA; tagB; "some text"; tagC]

As svick said, the discriminator is necessary. If you use classes instead you'll need to upcast to the base type, so I'm not sure you save on keystrokes.

If you're working with dictionaries, here is a nice option to reduce the syntactic noise of boxing. Maybe you can do something similar for lists.

花落人断肠 2024-12-22 12:53:19

我不知道这有多大帮助,但如果合适的话,您可以使用活动模式以类似 DU 的方式匹配类层次结构。

[<AbstractClass>]
type Animal() =
    abstract Talk : string

type Cat() =
    inherit Animal()
    override this.Talk = "Meow"

type Dog() =
    inherit Animal()
    override this.Talk = "Woof"

type SuperCat(s) =
    inherit Cat()
    override this.Talk = s

let animals : list<Animal> = 
    [Dog(); Cat(); SuperCat("MEOW")]

let (|SCSaid|_|) (a:Animal) =    // Active Pattern
    match a with
    | :? SuperCat as sc -> Some sc.Talk 
    | _ -> None

for a in animals do
    match a with
    | :? Dog -> printfn "dog"    
    | SCSaid s -> printfn "SuperCat said %s" s // looks like DU
    | _ -> printfn "other"
//dog
//other
//SuperCat said MEOW

I don't know how helpful this is, but you can use Active Patterns to match a class hierarchy in a DU-like fashion if appropriate.

[<AbstractClass>]
type Animal() =
    abstract Talk : string

type Cat() =
    inherit Animal()
    override this.Talk = "Meow"

type Dog() =
    inherit Animal()
    override this.Talk = "Woof"

type SuperCat(s) =
    inherit Cat()
    override this.Talk = s

let animals : list<Animal> = 
    [Dog(); Cat(); SuperCat("MEOW")]

let (|SCSaid|_|) (a:Animal) =    // Active Pattern
    match a with
    | :? SuperCat as sc -> Some sc.Talk 
    | _ -> None

for a in animals do
    match a with
    | :? Dog -> printfn "dog"    
    | SCSaid s -> printfn "SuperCat said %s" s // looks like DU
    | _ -> printfn "other"
//dog
//other
//SuperCat said MEOW
攀登最高峰 2024-12-22 12:53:19

受歧视的工会就是受歧视的(与 C 工会不同)。这意味着您必须始终添加鉴别器。

如果这是 C#,我会考虑从 stringStringTag 的隐式转换。但由于 F# 不支持隐式转换,我认为第二种方法是最好的选择。尽管我会让函数的名称更具描述性,而不仅仅是t。大多数时候,最好编写易于阅读的代码,而不是易于编写的代码。

Discriminated unions are just that – discriminated (unlike e.g. C unions). That means you have to always add the discriminator.

If this were C#, I would think about having an implicit conversion from string to StringTag. But since F# doesn't support implicit conversions, I think the second approach is your best bet. Although I would make the function's name more descriptive, not just t. Most of the time, it's better to write code that's easy to read, not code that's easy to write.

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