【问题标题】:The limit set of types with new data like `Tree a`具有新数据的限制类型集,例如“树 a”
【发布时间】:2019-06-28 11:10:25
【问题描述】:

在 Haskell 中探索和研究类型系统我发现了一些问题。

1) 让我们将多态类型视为二叉树:

 data Tree a = Leaf a | Branch (Tree a) (Tree a) deriving Show

而且,例如,我只想将我的考虑限制在Tree IntTree BoolTree Char。当然,我可以做出这样的新类型:

data TreeIWant = T1 (Tree Int) | T2 (Tree Bool) | T3 (Tree Char) deriving Show

但是是否有可能以更优雅的方式(并且没有像 T1T2T3 这样的新标签)方式(也许使用一些高级类型扩展)来制作新的受限类型(用于同质树)?

2) 第二个问题是关于具有异质值的树。我可以用通常的 Haskell 来做,即我可以做新的帮助类型,包含标记的异构值:

data HeteroValues = H1 Int | H2 Bool | H3 Char deriving Show

然后用这种类型的值创建树:

type TreeH = Tree HeteroValues

但是是否有可能以更优雅的方式(并且没有像H1H2H3 这样的新标签)方式(也许使用一些高级类型扩展)来制作新类型(用于异构树)? 我知道heterogeneous list,也许是同一个问题?

【问题讨论】:

  • 关于hetrogenous lists的答案可以应用于树(无需太多)修改,除了使用fmap over map等。原理在一个列表,但你可以在各种“仿函子”的东西上使用它。
  • 有几种方法可以大致完成您的要求。我不会说它们中的任何一个确实比显式标记的替代类型更好,但也许如果您解释了您需要的应​​用程序,我们可以提供一些提示。

标签: haskell type-systems gadt data-kinds phantom-types


【解决方案1】:

对于问题 #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

您可以创建ThingsThings 的列表(或树):

> 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 ...
>

这一切都很好,直到您尝试使用您构建的任何值。例如,如果你想编写一个函数来从可能是布尔值的 Thing 中提取 Bool

maybeBool :: Thing -> Maybe Bool
maybeBool (T x) = ...

你会发现自己被困在这里。如果没有某种“标签”,就无法确定xBoolInt 还是Char

实际上,您确实有一个可用的隐式标记,即xTHING 类型类字典。所以,你可以写:

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 构造函数进行模式匹配的能力(因此不得不替换casts 和/或TypeReps 的比较)。

【讨论】:

  • 如果你class Typeable a => THING a,那么你不需要返回并为每个实例添加一个强制转换方法到类中。
  • 是的。我在最后添加了一条注释。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-06
  • 1970-01-01
  • 2021-04-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多