8

我正在尝试阅读 Haskell 包 Data.List.Class 的源代码。(列表-0.4.2)。但我坚持使用一些语法。

开头是这样写的:

data ListItem l a =
    Nil |
    Cons { headL :: a, tailL :: l a }

我不熟悉第 3 行的语法。我猜这最后一行相当于Cons a (l a)???。但我不太确定。我注意到文件的标题说:{-# LANGUAGE FlexibleContexts, TypeFamilies #-}.

然后,随着我的继续,该type语句有一个奇怪的用法: type ItemM l :: * -> *,我无法理解。

Data.List.Class
-- | A class for list types. Every list has an underlying monad.
class (MonadPlus l, Monad (ItemM l)) => List l where
    type ItemM l :: * -> *
    runList :: l a -> ItemM l (ListItem l a)
    joinL :: ItemM l (l a) -> l a
    cons :: a -> l a -> l a
    cons = mplus . return

谁能帮忙解释一下这些是什么意思?我对 Data.List 有一个完美的理解,但是这个类型类的东西对我来说并不是很清楚。我还搜索了有关使用 Data.List.{Class,Tree} 的 wiki、示例和/或教程,但似乎没有任何内容,除了代码附带的注释。这里有任何指示吗?

谢谢。

-- 更新 -- 第一个答案 (@Chris) 帮助我理解了 Kind 签名和 Record 语法,这真的很有帮助。但是,就它如何捕获/定义 List 的行为以及它为熟悉的 Data.List 定义增加什么价值而言,我仍然无法从整体上理解那段代码。这里有一些进一步的细节,其中只有两个实例语句。该Identity术语也来自import Data.Functor.Identity (Identity(..)). 你能帮忙解释一下这是什么类型类来捕捉我们通常知道的列表的特征吗?同样,我在网上搜索了它,但除了代码本身之外,实际上没有 Data.List.Class 的文档。有谁知道?

此外,在 typeclass 约束中是否有另一个示例使用该type语句,类似于本示例中的内容?我搜索了 learnyouahaskell.com/ (@Landei) 但找不到这样的例子。我假设type这里的用法类似于typedef在 C++ 模板中使用 's 来定义“类型上的函数”,对吧?

再次感谢。

instance List [] where
    type ItemM [] = Identity
    runList [] = Identity Nil
    runList (x:xs) = Identity $ Cons x xs
    joinL = runIdentity
    cons = (:)

instance Functor m => Functor (ListItem m) where
    fmap _ Nil = Nil
    fmap func (Cons x xs) = Cons (func x) (fmap func xs)
4

1 回答 1

9

记录语法

data ListItem l a = Nil | Cons { headL :: a, tailL :: l a }

称为记录语法。当您猜测结构与您输入的相同时,您是正确的

data ListItem l a = Nil | Cons a (l a)

但是,您还可以获得两个访问器函数:

headL :: ListItem l a -> a
headL (Cons a _) = a

tailL :: ListItem l a -> l a
tailL (Cons _ as) = as

记录语法是语法糖——在这里它可以为您节省大约 4 行代码。您可以以正常方式进行模式匹配,如本段正上方的代码,或者您可以在模式匹配中使用记录语法:

safeHeadL :: ListItem l a -> Maybe a
safeHeadL Nil                = Nothing
safeHeadL (Cons {headL = a}) = Just a

同样,这被取消为标准模式匹配。

亲切的签名

class (MonadPlus l, Monad (ItemM l)) => List l where
  type ItemM l :: * -> *
  runList :: l a -> ItemM l (ListItem l a)
  joinL   :: ItemM l (l a) -> l a

  cons :: a -> l a -> l a
  cons = mplus . return

是一个类型族声明。线

  type ItemM l :: * -> *

是一种亲切的签名。当我们说某物有 kind*时,我们的意思是它是一个基本类型,比如Intor Float。说某物有种类* -> *意味着它是一个类型构造函数,即它接受一个类型并返回另一个类型。

Maybe类型构造函数有这样的签名:

Maybe :: * -> *

请记住,Maybe它本身不是一种类型。它必须被赋予一个类型,然后它返回一个类型。给它Int,你得到Maybe Int。给它Double,你得到Maybe Double

类型构造函数ItemM l接受一个类型参数(类型*)并返回一些类型*。请注意,既然l是善良的*,你有

ItemM :: * -> * -> *

ieItemM接受两种类型,并返回一个类型(等效地,它接受一种类型并返回一个一元类型构造函数)。

通过在类中包含类型声明,您施加了一个约束,即在类的所有实例中,linItemM l必须与lin匹配List l。不可能创建这些不匹配的类的实例。

于 2012-08-02T04:30:58.463 回答