如何在消耗懒惰序列的同时观察进展?

时间:2014-07-06 15:08:46

标签: haskell clojure observer-pattern lazy-evaluation

我想知道以下“消耗”顺序时“观察”序列的方法是否正确。我已经阅读了以下SO答案,但我有点惊讶,因为我已经多次阅读过“正确”的方法是使用懒惰(并因此明确地使你的序列变得懒惰,即使它不是),但是“懒惰”这个词在那里甚至没有提到:

How to implement the Observer Design Pattern in a pure functional way?

所以基本上我有一个懒惰的序列,我想保持这个懒惰的序列100%纯,我不希望观察者将任何东西泄漏到那个懒惰的序列中。在我的例子中,我只是使用(range 100000),但任何懒惰序列都可以。

然后我使用一些可变性(在使用原子的Clojure中)做这样的事情(代码是可运行的,你可以像在REPL中那样复制/粘贴):

(let [a (atom (range 100000))]
  (loop [i 0]
    (if (= 0 (mod i 10)) (println "i: " i)) ; the observer
    (if (= 100 i)
      (take 1 @a)
      (do (reset! a (rest @a)) (recur (inc i))))))

关键不在于我在这里使用了一个可变原子,而是懒惰序列的实现完全不知道它被观察到了。观察者显然可以更漂亮:就像实际通知观察者而不是使用副作用来打印 i (再次:打印 i 这里只是一个例子)。

这是一种在消耗懒惰序列时观察的“正确”方式吗?

如果这不正确,你会在Clojure中消耗懒惰序列时如何观察?

或者,你会如何在Haskell中做到这一点?

2 个答案:

答案 0 :(得分:4)

如果你只是想在消费过程中散布副作用,那么是的,在Clojure中要做的明智的事情就是用另一个懒惰的序列来包装。

(defn lazy-report
  "Wrap s in a lazy sequence that will call f, presumably for side effects, 
   on every nth element of s during consumption."
  [s f n] 
  (let [g (cons (comp first (juxt identity f)) (repeat (dec n) identity))]
    (map #(% %2) (rest (cycle g)) s)))

(println "sum:" (reduce + (lazy-report (range 1 1000) #(println "at:" %) 100)))
;=> at: 100
;   at: 200
; ...
;   at: 900
;   sum: 499500

答案 1 :(得分:3)

这是我在Haskell中如何做到的。你要求的是一种懒惰的数据结构,偶尔会产生一些价值。这可以建模为跳过的流。

data Stream a
    = Done 
    | Skip    (Stream a)
    | Yield a (Stream a) 

Stream可以Yield一个值和流的其余部分Skip并返回流的其余部分,或者Done,这意味着流已被完全消耗。

我们可以通过简单地递归产生副作用来评估产生副作用的Stream,在Yield中执行副作用,直到我们到达Stream的末尾。

eval :: Monad m => Stream (m a) -> m ()
eval Done         = return ()
eval (Yield ma s) = ma >> eval s
eval (Skip     s) = eval s

我们可以从懒惰序列(在这种情况下是一个列表)中构建一个Stream给出一个"决定"选择是否为原始序列中的每个元素产生副作用或跳过的函数。

observe :: Monad m => (a -> Maybe (m b)) -> [a] -> m ()
observe f = eval . go
    where go []     = Done
          go (x:xs) = case f x of
              Nothing -> Skip     (go xs)
              Just mb -> Yield mb (go xs)

如果我们想要观察其他类型的序列(包括列表),我们可以使用这个非常简洁的定义来概括observe以适用于任何Foldable实例:

observe :: (Foldable f, Monad m) => (a -> Maybe (m b)) -> f a -> m ()
observe f = eval . foldr (maybe Skip Yield . f) Done

最终产品,

f :: Int -> Maybe (IO ())
f x | x `rem` 10 == 0 = Just (print x)
    | otherwise       = Nothing

main = observe f [0..100]

这适用于无限序列。