为什么差异列表不是可折叠的实例?

Mik*_*cki 9 haskell church-encoding difference-lists

DLIST包包含DList数据类型,它有大量的实例,但不是FoldableTraversable.在我看来,这是两个最"类似列表"的类型.是否存在DList不是这些类的实例的性能原因?

而且,封装确实实现foldrunfoldr,但是没有其他的折叠功能.

Lui*_*las 22

您应该考虑的另一种方法DList是使用Church编码列表.我们的想法是将列表表示为一个不透明的值,它知道如何foldr在列表上执行.这需要使用RankNTypes扩展名:

{-# LANGUAGE RankNTypes #-}

import Prelude 
import Control.Applicative
import Data.Foldable (Foldable)
import qualified Data.Foldable as F
import Data.Traversable (Traversable)
import qualified Data.Traversable as T

-- | Laws:
--
-- > runList xs cons nil == xs
-- > runList (fromList xs) f z == foldr f z xs
-- > foldr f z (toList xs) == runList xs f z
newtype ChurchList a = 
    ChurchList { runList :: forall r. (a -> r -> r) -> r -> r }

-- | Make a 'ChurchList' out of a regular list.
fromList :: [a] -> ChurchList a
fromList xs = ChurchList $ \k z -> foldr k z xs

-- | Turn a 'ChurchList' into a regular list.
toList :: ChurchList a -> [a]
toList xs = runList xs (:) []

-- | We can construct an empty 'ChurchList' without using a @[]@.
nil :: ChurchList a 
nil = ChurchList $ \_ z -> z

-- | The 'ChurchList' counterpart to '(:)'.  Unlike 'DList', whose
-- implementation uses the regular list type, 'ChurchList' doesn't
-- rely on it at all.
cons :: a -> ChurchList a -> ChurchList a
cons x xs = ChurchList $ \k z -> k x (runList xs k z)

-- | Append two 'ChurchList's.  This runs in O(1) time.  Note that
-- there is no need to materialize the lists as @[a]@.
append :: ChurchList a -> ChurchList a -> ChurchList a
append xs ys = ChurchList $ \k z -> runList xs k (runList ys k z)

-- | Map over a 'ChurchList'.  No need to materialize the list.
instance Functor ChurchList where
    fmap f xs = ChurchList $ \k z -> runList xs (\x xs' -> k (f x) xs') z

-- | The 'Foldable' instance is trivial, given the 'ChurchList' law.
instance Foldable ChurchList where
    foldr f z xs = runList xs f z

instance Traversable ChurchList where
    traverse f xs = runList xs step (pure nil)
        where step x rest = cons <$> f x <*> rest
Run Code Online (Sandbox Code Playgroud)

这方面的缺点是没有有效的tail操作来ChurchList折叠ChurchList便宜,但重复尾巴是昂贵的......


Sjo*_*her 12

DList a是一个NEWTYPE包装周围[a] -> [a],其具有a在一个逆变位置,因此它不能执行Foldable或者Traversable,或甚至Functor直接.实现它们的唯一方法是转换为常规列表(参见foldr实现),这会破坏差异列表的性能优势.

  • 那么为什么我们不简单地定义`fold(DL f)= fold(f [])`?我们可以忘记如何实现`DList`s并简单地将它们视为元素序列的一些表示,然后实现`Foldable`是有意义的.以这种方式实现`Functor`和`Traversable`可能会有一些陷阱,但`可折叠'似乎很合理. (4认同)
  • 继Sjoerd的回答之后,DList只对**构建**有效 - 如果你已经建立了你的列表并想要处理它,你应该用`toList`来转换它然后处理常规列表. (2认同)
  • 是的,"可折叠"可能不会太糟糕,包装已经"折叠"了,毕竟这已经足够了.我猜它没有实现的原因是因为上次更新是在2009年,当时`Foldable`还不是一个众所周知的类型类. (2认同)