关联“数据项”的优雅方式

Gus*_*ust 3 haskell algebraic-data-types

我试图将 of 的“数据项”ContentType与其内容相关联:

data ContentType = MyInt | MyBool deriving ( Show )

data Encoding'
  = EncodingInt  [Int]
  | EncodingBool [Bool]

chooseContentType :: IO ContentType
chooseContentType = undefined
Run Code Online (Sandbox Code Playgroud)

我如何制作这样的东西,但经过类型检查?

data Encoding a =
  Encoding { contentType :: ContentType
           , content :: [a]
           }
Run Code Online (Sandbox Code Playgroud)

Sil*_*olo 5

您正在寻找的功能称为广义代数数据类型(或简称 GADT)。它是一个 GHC 扩展,因此您必须在文件顶部放置一个编译指示才能使用它们。(我还包括StandaloneDeriving,因为我们将使用它在一分钟内获取您的Show实例)

{-# LANGUAGE GADTs, StandaloneDeriving #-}
Run Code Online (Sandbox Code Playgroud)

现在您可以ContentType完全定义您的类型。这个想法是ContentType由其参数的(最终)类型参数化,因此ContentType将具有类型* -> *(即它将采用类型参数)。

现在我们要用一些有趣的语法来编写它。

data ContentType a where
  MyInt :: ContentType Int
  MyBool :: ContentType Bool
Run Code Online (Sandbox Code Playgroud)

这表明这MyInt不仅仅是一个ContentType;它是ContentType Int。我们让类型信息ContentType本身保持活跃。

现在Encoding基本上可以按照你的方式编写了。

data Encoding a =
  Encoding { contentType :: ContentType a
           , content :: [a]
           }
Run Code Online (Sandbox Code Playgroud)

Encoding还接受一个类型参数a。其内容必须是 的列表a,并且其内容类型必须是支持该类型的内容类型a。由于我们的示例仅定义了两种内容类型,这意味着它必须是MyInt整数或MyBool布尔值,并且不支持其他编码。

我们可以通过一个子句恢复您的deriving (Show)on (这是我们在上面的编译指示中打开的第二个 GHC 扩展)ContentTypeStandaloneDeriving

deriving instance Show (ContentType a)
Run Code Online (Sandbox Code Playgroud)

这与您的子句等效deriving,只是它位于自己的行上,因为 GADT 语法实际上没有一个好的位置可以将其放在同一行中。