Question

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?

Was it helpful?

Solution

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.

OTHER TIPS

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top