我想知道以下“消耗”顺序时“观察”序列的方法是否正确。我已经阅读了以下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中做到这一点?
答案 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]
这适用于无限序列。