F #: объединение различаемых объединений и иерархий классов?

Скажем, у меня есть значительная иерархия классов:

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]

Очевидно, что Tag и String, описанные в Node, ортогональны и отделены от существующих классов Tag / String. Наведение указателя мыши дает мне типы Node.Tag и Node.String, чего я не хочу.

Теперь у меня есть функция t, которая создает StringTag, который наследуется от Tag, давая мне

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

что довольно приятно, но лишнее t добавляет визуального шума. На самом деле мне нужен строго типизированный «список двух разных типов», с которым я мог бы работать, используя операторы match. Я думал, что это и есть суть Discriminated Unions, но их неспособность использовать существующие иерархии типов является проблемой, поскольку существующая иерархия (в данном случае Tag) достаточно сложна, я думаю, что подход полного объектно-ориентированного наследования к этому подмножеству типов более ясен чем чистый подход Дискриминационного союза

Один из вариантов - просто составить список из obj и разыграть все до / во время match, но это не очень хорошо. Есть ли другие подходы?


person Li Haoyi    schedule 15.11.2011    source источник


Ответы (3)


Если у вас было два разных DU, скажем

type Node = 
  | Tag of Tag
  | String of String

а также

type Foo = 
  | Bar of Tag
  | Name of String

как компилятор узнает, к какому типу относится следующий список?

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

Как сказал Свик, дискриминатор необходим. Если вы вместо этого используете классы, вам нужно будет выполнить преобразование до базового типа, поэтому я не уверен, что вы экономите на нажатиях клавиш.

Если вы работаете со словарями, здесь - отличный вариант уменьшить синтаксический шум бокса. Может быть, вы можете сделать что-то подобное для списков.

person Daniel    schedule 15.11.2011
comment
Или даже: type Node = Bar of Tag | Foo of Tag - person David Grenier; 01.12.2011

Я не знаю, насколько это полезно, но вы можете использовать Active Patterns для сопоставления иерархии классов в стиле 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
person Brian    schedule 15.11.2011

Дискриминируемые союзы просто дискриминируются (в отличие, например, от союзов C). Это означает, что вы всегда должны добавлять дискриминатор.

Если бы это был C #, я бы подумал о неявном преобразовании из string в StringTag. Но поскольку F # не поддерживает неявные преобразования, я думаю, что лучше всего подходит второй подход. Хотя я бы сделал имя функции более наглядным, а не просто t. В большинстве случаев лучше писать код, который легко читать, а не код, который легко написать.

person svick    schedule 15.11.2011