[Haskell-cafe] Corecursive folds viewed as coroutines?

Viktor Dukhovni ietf-dane at dukhovni.org
Fri Sep 17 09:42:42 UTC 2021


> On 17 Sep 2021, at 3:19 am, Tom Ellis <tom-lists-haskell-cafe-2017 at jaguarpaw.co.uk> wrote:
> 
> On Fri, Sep 17, 2021 at 01:57:58AM -0400, Viktor Dukhovni wrote:
>> Laziness makes it it possible to use folds as coroutines that lazily
>> yield a sequence of values.  This is not possible in strict languages,
>> where you'd need explicit support for coroutines (generators) via
>> something like a "yield" primitive.
> 
> Wouldn't an explicit thunk datatype (that takes a lambda as a
> "constructor") be sufficient?  I can't see why going all the way to
> coroutines would be required.

Yes, sure, coroutines are but one model.  Indeed explicit thunks
can simulate laziness in a strict language.

But then there's the mind bending recent challenge on r/haskell to
implement (in Haskell) a general `foldr` using nothing from the
underlying Foldable except its `foldl'` (otherwise, any and all
Haskell tools are fine).

The implementation needs to be no less lazy than the real `foldr`,
forcing no more of the structures spine or elements than `foldr`
would.

It turns out that pretty much the only solutions reported all use
coroutines (unsafePerformIO and forkIO) in order to synchronise
demand-driven yields of the structure elements by a strict left
fold.

This tells me that `foldr` as coroutine is one version of the truth,
even if there are alternative valid mental models.

    {-# LANGUAGE LambdaCase #-}
    import Control.Concurrent
    import qualified Data.Foldable as F
    import System.IO.Unsafe
    
    foldr :: Foldable f => (a -> b -> b) -> b -> f a -> b
    foldr f z xs = unsafeDupablePerformIO $ do
        next <- newEmptyMVar
        lock <- newEmptyMVar
        let yield k a = seq (unsafeDupablePerformIO $ putMVar next (Just a) >> takeMVar lock) k
            loop      = takeMVar next >>= \case
                Nothing -> return z
                Just  a -> unsafeInterleaveIO (putMVar lock () >> loop) >>= pure . f a
        forkIO $ F.foldl' yield (pure ()) xs >> putMVar next Nothing
        loop

-- 
	Viktor.



More information about the Haskell-Cafe mailing list