各种优化问题,例如这个问题,导致 Church 编码列表作为一种启用流融合的方式,即编译器消除中间结果(例如列表)。这是在优化问题中成功使用的定义:
{-# LANGUAGE RankNTypes #-}
-- A list encoded as a strict left fold.
newtype ListL a = ListL {build :: forall b. (b -> a -> b) -> b -> b}
以下是我对 Church-somethings 的看法:与其问“某事”是什么,不如问它能为你做什么。对于列表,答案是:列表可以折叠。为了折叠,我需要一个 type 的“更新”函数b->a->b
和一个 type 的起始值b
。然后我会把折叠的结果返回给你,它的类型是b
. 因此定义ListL
。以下是对 的一些基本操作ListL
:
mapL :: (a -> a') -> ListL a -> ListL a'
mapL f l = ListL (\f' b' -> build l (\b a -> f' b (f a)) b')
instance Functor ListL where fmap = mapL
fromList :: [a] -> ListL a
fromList l = ListL (\c z -> foldl' c z l)
toList :: ListL a -> [a]
toList l = build l snoc [] where snoc xs x = xs ++ [x]
nullL :: ListL a -> Bool
nullL l = build l (\_ _->False) True
这里还有更多:
filterL :: (a->Bool) -> ListL a -> ListL a
filterL p l = ListL (\f b->build l (\b' a'->if p a' then f b' a' else b') b)
iterUntil :: (a->Bool) -> a -> (a->a) -> ListL a
iterUntil p a f = ListL (\g b-> snd $ until (p.fst) (\(a',b')->(f a', g b' a')) (a,b))
iterUntil
迭代一个函数a->a
,从某个类型的值开始a
,直到a->bool
满足谓词。像 Prelude 这样的函数iterate
是不可能的——至少我不知道如何定义它,它必须是某种递归。
继续举例,length
只是sum
在 a 中选择正确的“更新”函数和起始值的练习foldl
:
lengthL :: ListL a -> Int
lengthL l = build l (\b _ -> b+1) 0
sumL :: Num a => ListL a -> a
sumL l = build l (+) 0
现在,让我们试试headL
:
headL :: ListL a -> a
headL l = build l (\_ a->a) _ -- this does not compile!
无论提供什么开始,都应该返回b
第一个。需要 a ,但我们没有。这是一个奇怪的问题:基本上我们想告诉编译器:你不需要,相信我...另一方面, A 很容易构造。一个in place of the hole不起作用,因为它总是被调用 - 懒惰似乎并没有解决这个问题。所以,我被困住了。因此这里是a
build l
b
b
headL' :: ListL a -> ListL a
error "empty list!"
_
headL
问题一:如何headL
实施?
第二个问题出现在尝试实现repeatM :: Monad m => m a -> m [a]
. 与 一样iterUntil
,需要谓词a->Bool
来停止迭代:
iterUntilM :: Monad m => (a->Bool) -> m a -> m (ListL a)
目的很明确:重复一个单子动作m a
,直到a->Bool
满足为止。想法当然是ListL a
马上把它折叠起来,实现流融合(list fusion)。例如:
import System.Random (randomIO)
main :: IO ()
main = do
rs <- iterUntilM (>42::Int) randomIO
print $ lengthL rs
这个例子是相当做作的,它打印出在找到第一个数字 >42 之前所花费的抽奖次数。m
例如,在更现实的环境中,monad是ST s
包装了一些 FFI 的 monad。关键是:这应该有效地运行。我完全被这个卡住了。我如何纠缠(>>=) :: m a -> (a->m b) -> m b
与build
得到一个m (ListL a)
?即这是
问题2:如何iterUntilM
实施?
除了作为一个很好的学习练习之外,这真的是一个好主意吗?