这是一个有文化的haskell帖子。只需将其保存为" ChurchList.lhs"运行它。
> {-# LANGUAGE Rank2Types #-}
Church编码列表是一种通过函数表示列表的方法。它类似于折叠和延续传递风格。
> newtype ChurchList a = CList {runCList :: forall r. (a -> r -> r) -> r -> r}
为了说明这对应于列表,这里是O(n)同构
> fromList :: [a] -> ChurchList a
> fromList xs = CList $ \cons empty -> foldr cons empty xs
> toList :: ChurchList a -> [a]
> toList cl = runCList cl (:) []
> instance Show a => Show (ChurchList a) where
> show cl = "fromList " ++ show (toList cl)
这些东西具有良好的性能特征。
> singleton :: a -> ChurchList a -- O(1)
> singleton a = CList $ \cons empty -> a `cons` empty
> append :: ChurchList a -> ChurchList a -> ChurchList a -- O(1)!!! This also means cons and snoc are O(1)
> append cl1 cl2 = CList $ \cons empty -> runCList cl1 cons (runCList cl2 cons empty)
> concatCl :: ChurchList (ChurchList a) -> ChurchList a -- O(n)
> concatCl clcl = CList $ \cons empty -> runCList clcl (\cl r -> runCList cl cons r) empty
> headCl :: ChurchList a -> Maybe a -- O(1)
> headCl cl = runCList cl (\a _ -> Just a) Nothing
现在,问题出现在tail
。
> tailClbad :: ChurchList a -> Maybe (ChurchList a) --O(n)?!!
> tailClbad cl = (fmap snd) $ runCList cl
>
> (\a r -> Just (a, case r of
> Nothing -> CList $ \cons empty -> empty
> Just (s,t) -> append (singleton s) t)) --Cons
>
> Nothing --Empty
基本上我的实现是将列表拆分为头尾。缺点取代头部,将旧头部贴在尾巴上。这是相当低效的。 教会名单似乎在分裂时效率低下。
我希望自己错了。是否有tailCl
的实现优于O(n)(最好是O(1))。
答案 0 :(得分:6)
Koopman,Plasmeijer和Jansen的论文Church Encoding of Data Types Considered Harmful for Implementations似乎在详细讨论这个问题。特别是引用摘要(我的重点):
[...]
我们在教会编码选择器中显示构造函数 产生递归类型,如列表的 tail ,有一个不合需要的 严格的数据结构的脊柱。斯科特编码 不以任何方式妨碍懒惰的评价。 评价 由教会编码的递归脊椎使得复杂性 这些析构函数 O(n)。 Scott编码中的相同析构函数 只需要一段时间。而且,教会编码有 图减少的严重问题。 Parigot编码结合起来 两个世界中最好的,但在实践中,这并没有提供 优点
但是,虽然Scott encoding提供了性能优势,但似乎problematic在系统F中定义它而不添加递归类型。
答案 1 :(得分:3)
是的,是O(n)。教会编码列表用其foldr函数标识,它必须在任何地方做同样的事情。由于获得尾部需要为第一个项目做一些事情,因此必须对所有剩余项目执行相同的操作。
{-# LANGUAGE RankNTypes #-}
newtype ChurchList a = CList { getFoldr :: forall r. (a -> r -> r) -> r -> r }
fromList :: [a] -> ChurchList a
fromList xs = CList $ \f z -> foldr f z xs
toList :: ChurchList a -> [a]
toList cl = getFoldr cl ((:)) []
您的解决方案尽可能高效。通过在第一个项目上构建列表和匹配,也可以轻松地编写相同的解决方案。
safeTail :: [a] -> Maybe [a]
safeTail [] = Nothing
safeTail (_:xs) = Just xs
tailCtrivial :: ChurchList a -> Maybe (ChurchList a)
tailCtrivial = fmap fromList . safeTail . toList
答案 2 :(得分:0)
不,不是必然 O(n):
前奏>拿5。 snd。 foldr(\ a r->(a:fst r,fst r))([],undefined)$ [1 ..]
[2,3,4,5,6]
它确实为每个元素最终生成的添加了O(1)开销。
尝试 safetail
不起作用:
前奏> fmap(取5)。 snd。 foldr(\ a r->(fmap(a :) $ fst r,fst r))(Just [],Nothing)
$ [1 ..]
中断。
所以,
tailCL cl = CList $ \k z-> snd $ runCList cl (\a r-> (a`k`fst r,fst r)) (z, undefined)
前奏>拿5。 toList。 tailCL。 fromList $ [1 ..]
[2,3,4,5,6]
编辑:按@user3237465跟进评论,结果发现 safetail
是可能的:
前奏> fmap(取5)。 snd。 foldr(\ a~(r,_) - >(Just(a:fromJust r),r))(Just [] ,没什么)$ [1 ..]
只是[2,3,4,5,6]
以前不起作用的原因是Maybe
的{{1}}强迫其第二个参数找出它是哪种情况,但在这里我们知道它通过构造是fmap
值。我不能把它作为你的类型的定义,不管我试过什么都不通过类型检查器。