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?
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.
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
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.
来源:https://stackoverflow.com/questions/8130433/f-combining-together-discriminated-unions-and-class-hierarchies