Для вопроса №2 легко создать «ограниченный» гетерогенный тип без явных тегов, используя GADT и класс типов:
{-# LANGUAGE GADTs #-}
data Thing where
T :: THING a => a -> Thing
class THING a
Теперь объявите THING
экземпляров для вещей, которые вы хотите разрешить:
instance THING Int
instance THING Bool
instance THING Char
и вы можете создать Things
и списки (или деревья) из Things
:
> t1 = T 'a' -- Char is okay
> t2 = T "hello" -- but String is not
... type error ...
> tl = [T (42 :: Int), T True, T 'x']
> tt = Branch (Leaf (T 'x')) (Leaf (T False))
>
С точки зрения имен типов в вашем вопросе, у вас есть:
type HeteroValues = Thing
type TreeH = Tree Thing
Вы можете использовать тот же класс типов с новым GADT для вопроса № 1:
data ThingTree where
TT :: THING a => Tree a -> ThingTree
и у вас есть:
type TreeIWant = ThingTree
и вы можете сделать:
> tt1 = TT $ Branch (Leaf 'x') (Leaf 'y')
> tt2 = TT $ Branch (Leaf 'x') (Leaf False)
... type error ...
>
Это все хорошо, пока вы не попытаетесь использовать любое из построенных вами значений. Например, если вы хотите написать функцию для извлечения Bool
из, возможно, глупого Thing
:
maybeBool :: Thing -> Maybe Bool
maybeBool (T x) = ...
Вы бы застряли здесь. Без какого-либо «тега» невозможно определить, является ли x
Bool
, Int
или Char
.
На самом деле у вас do есть неявный доступный тег, а именно словарь классов типа THING
для x
. Итак, вы можете написать:
maybeBool :: Thing -> Maybe Bool
maybeBool (T x) = maybeBool' x
и затем реализовать maybeBool'
в вашем классе типов:
class THING a where
maybeBool' :: a -> Maybe Bool
instance THING Int where
maybeBool' _ = Nothing
instance THING Bool where
maybeBool' = Just
instance THING Char where
maybeBool' _ = Nothing
и ты золотой!
Конечно, если вы использовали явные теги:
data Thing = T_Int Int | T_Bool Bool | T_Char Char
тогда вы можете пропустить класс типов и написать:
maybeBool :: Thing -> Maybe Bool
maybeBool (T_Bool x) = Just x
maybeBool _ = Nothing
В итоге получается, что лучшее представление на Хаскелле алгебраической суммы трех типов - это просто алгебраическая сумма трех типов:
data Thing = T_Int Int | T_Bool Bool | T_Char Char
Попытка избежать необходимости в явных тегах, вероятно, приведет к большому количеству неэлегического шаблона в других местах.
Обновление: Как отметил @DanielWagner в комментарии, вы можете использовать Data.Typeable
вместо этого шаблона (фактически, GHC генерирует для вас много шаблона), поэтому вы можете написать:
import Data.Typeable
data Thing where
T :: THING a => a -> Thing
class Typeable a => THING a
instance THING Int
instance THING Bool
instance THING Char
maybeBool :: Thing -> Maybe Bool
maybeBool = cast
Поначалу это может показаться "элегантным", но если вы попробуете этот подход в реальном коде, я думаю, вы пожалеете о том, что потеряете возможность сопоставления с шаблоном на конструкторах Thing
на сайтах использования (и, следовательно, будете вынуждены заменять цепочки cast
с и / или сравнения TypeRep
с).