对于问题 #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
对于问题 #1,您可以使用具有新 GADT 的相同类型类:
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
从可能的 boolish中提取 a Thing
:
maybeBool :: Thing -> Maybe Bool
maybeBool (T x) = ...
你会发现自己被困在这里。如果没有某种“标签”,就无法确定x
是 a Bool
、Int
还是Char
。
但实际上,您确实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
最后,事实证明,三种类型的代数和的最佳 Haskell 表示只是三种类型的代数和:
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
s 链和/或比较TypeRep
s)。