列表在Haskell中定义为Maybe?为什么不?

时间:2012-07-06 15:40:33

标签: list haskell maybe

除了错误处理之外,您不会看到Maybe List,因为列表本身有点Maybe:他们有自己的“Nothing”:{{1} }和他们自己的“[]”:Just。 我使用Maybe和函数编写了一个列表类型,将标准转换为“实验”列表。 (:)

toStd . toExp == id

你怎么看待它,作为减少重复的尝试,概括一下?

树也可以使用这些列表定义:

data List a = List a (Maybe (List a))
    deriving (Eq, Show, Read)

toExp [] = Nothing
toExp (x:xs) = Just (List x (toExp xs))

toStd Nothing = []
toStd (Just (List x xs)) = x : (toStd xs)

但我还没有测试过最后一段代码。

5 个答案:

答案 0 :(得分:9)

您可以在Haskell中以多种方式定义列表。例如,作为函数:

{-# LANGUAGE RankNTypes #-}

newtype List a = List { runList :: forall b. (a -> b -> b) -> b -> b }

nil :: List a
nil = List (\_ z -> z )

cons :: a -> List a -> List a
cons x xs = List (\f z -> f x (runList xs f z))

isNil :: List a -> Bool
isNil xs = runList xs (\x xs -> False) True

head :: List a -> a
head xs = runList xs (\x xs -> x) (error "empty list")

tail :: List a -> List a
tail xs | isNil xs = error "empty list"
tail xs = fst (runList xs go (nil, nil))
    where go x (xs, xs') = (xs', cons x xs)

foldr :: (a -> b -> b) -> b -> List a -> b
foldr f z xs = runList xs f z

这个实现的技巧是列表被表示为执行列表元素折叠的函数:

fromNative :: [a] -> List a
fromNative xs = List (\f z -> foldr f z xs)

toNative :: List a -> [a]
toNative xs = runList xs (:) []

在任何情况下,真正重要的是类型及其操作遵循的合同(或法律)以及性能实现。基本上,任何履行合同的实现都会为您提供正确的程序,更快的实现将为您提供更快的程序。

什么是名单合同?好吧,我不打算详细地表达它,但列出服从这样的陈述:

  1. head (x:xs) == x
  2. tail (x:xs) == xs
  3. [] == []
  4. [] /= x:xs
  5. 如果xs == ysx == y,则x:xs == y:ys
  6. foldr f z [] == z
  7. foldr f z (x:xs) == f x (foldr f z xs)
  8. 编辑:并将其与奥古斯丁的回答联系起来:

    newtype ExpList a = ExpList (Maybe (a, ExpList a))
    
    toExpList :: List a -> ExpList a
    toExpList xs = runList xs (\x xs -> ExpList (Just (x, xs))) (ExpList Nothing)
    
    foldExpList f z (ExpList Nothing) = z
    foldExpList f z (ExpList (Just (head, taill))) = f head (foldExpList f z tail)
    
    fromExpList :: ExpList a -> List a
    fromExpList xs = List (\f z -> foldExpList f z xs)
    

答案 1 :(得分:9)

所有ADT都是(,)Either()(->)Void和{{的某种组合的同构(几乎 - 见尾) 1}}其中

Mu

data Void --using empty data decls or newtype Void = Void Void 计算仿函数的固定点

Mu

所以例如

newtype Mu f = Mu (f (Mu f))

相同
data [a] = [] | (a:[a])

本身与

同构
data [a] = Mu (ListF a)
data ListF a f = End | Pair a f

因为

newtype ListF a f = ListF (Either () (a,f))

同构
data Maybe a = Nothing | Just a

你有

newtype Maybe a = Maybe (Either () a)

可以在mu中内联

newtype ListF a f = ListF (Maybe (a,f))

和你的定义

data List a = List (Maybe (a,List a))

只是Mu的展开和外部Maybe的消除(对应于非空列表)

你完成了......

一些事情

  1. 使用自定义ADT可提高清晰度和类型安全性

  2. 这种普遍性很有用:参见GHC.Generic


  3. 好的,我说的几乎是同构的。这不完全是,即

    data List a = List a (Maybe (List a))
    

    在列表的hmm = List (Just undefined) 定义中没有等效值。这是因为Haskell数据类型是coinductive,并且已经a point of criticism of the lazy evaluation model。您可以通过仅使用严格的总和和产品(以及通过值函数调用)以及添加特殊的“Lazy”数据构造函数来解决这些问题

    [a] = [] | (a:[a])

    然后可以忠实地编码所有同构。

答案 2 :(得分:7)

您可以按Maybe来定义列表,但不是这样。您的List类型不能为空。或者您是否打算将Maybe (List a)替换为[a]。这看起来很糟糕,因为它不区分列表和类型。

这样可行

newtype List a = List (Maybe (a, List a))

这有一些问题。首先使用它会比通常的列表更冗长,其次,域名与列表不同构,因为我们在那里有一对(可以是未定义的;在域中添加额外的级别)。

答案 3 :(得分:1)

如果是列表,它应该是Functor的实例,对吧?

instance Functor List
  where fmap f (List a as) = List (f a) (mapMaybeList f as)

mapMaybeList :: (a -> b) -> Maybe (List a) -> Maybe (List b)
mapMaybeList f as = fmap (fmap f) as

以下是一个问题:您可以List Functor的实例,但您的可能列表不是:即使Maybe不是Functor的实例它本身的权利,你不能直接将Maybe . List这样的结构构建成任何实例(你需要一个包装类型)。

与其他类型类似。


话虽如此,您可以使用您的配方执行此操作,而标准Haskell列表则无法执行此操作:

instance Comonad List
  where extract (List a _) = a
        duplicate x @ (List _ y) = List x (duplicate y)

可能列表仍然不会是comonadic。

答案 4 :(得分:1)

当我第一次开始使用Haskell时,我也试图尽可能多地用现有类型来表示事物,理由是避免冗余是很好的。我目前的理解(移动目标!)往往涉及更多的多维度权衡网络的想法。我不会在这里给出任何“回答”,就像粘贴例子并问“你明白我的意思吗?”我希望无论如何它都会有所帮助。

让我们看一下Darcs代码:

data UseCache = YesUseCache | NoUseCache
    deriving ( Eq )

data DryRun = YesDryRun | NoDryRun
    deriving ( Eq )

data Compression = NoCompression
                 | GzipCompression
    deriving ( Eq )

你注意到这三种类型都可以Bool吗?为什么你认为Darcs黑客决定他们应该在他们的代码中引入这种冗余?另一个例子,这是我们几年前改变的一段代码:

type Slot = Maybe Bool                  -- OLD code
data Slot = InFirst | InMiddle | InLast -- newer code

为什么你认为我们决定第二个代码比第一个代码有所改进?

最后,这里有一些我日常工作中的代码。它使用了提到的newtype语法,

newtype Role = Role { fromRole :: Text }
  deriving (Eq, Ord)

newtype KmClass = KmClass { fromKmClass :: Text }
  deriving (Eq, Ord)

newtype Lemma = Lemma { fromLemma :: Text }
  deriving (Eq, Ord)

在这里你会注意到我已经完成了一个好奇的事情,即采用一种非常好的Text类型然后将它包装成三种不同的东西。与普通的Text相比,这三件事没有任何新功能。他们只是在那里与众不同。说实话,我不完全确定这对我来说是不是一个好主意。我暂时认为这是因为我出于很多原因操纵了许多不同的文字,但是时间会证明。

你能看到我想要的东西吗?