packages feed

heftia-effects-0.6.0.0: Example/Stream/Main.hs

{-# OPTIONS_GHC -fconstraint-solver-iterations=16 #-}

-- SPDX-License-Identifier: MPL-2.0

-- This example is based on https://h2.jaguarpaw.co.uk/posts/bluefin-streams-finalize-promptly/
module Main where

import Control.Arrow ((>>>))
import Control.Monad (forever, void, when)
import Control.Monad.Hefty (Eff, Emb, FOEs, liftIO, onlyFOEs, type (:>))
import Control.Monad.Hefty.Concurrent.Parallel (runParallelIO)
import Control.Monad.Hefty.Concurrent.Stream (
    Input,
    Machinery (Unit),
    Output,
    input,
    output,
    runMachinery,
    runMachineryIO_,
 )
import Control.Monad.Hefty.Concurrent.Timer (Timer, runTimerIO, sleep)
import Control.Monad.Hefty.Except (runThrow, throw)
import Control.Monad.Hefty.Unlift (runUnliftIO)
import Data.Foldable (for_)
import UnliftIO (bracket_)

{- | In reality, this 'throw' operates independently of @bracket@...
because 'runThrow' functions under the semantics of pure algebraic effects,
it operates independently without interfering with 'IO'-level exceptions.

This function is equivalent to the following (as a result of reducing 'runThrow').

@
produce = void do
    for_ [1 .. 4] \(i :: Int) -> do
        output i
        sleep 0.5
@
-}
produce :: (Output Int :> es, Timer :> es, FOEs es) => Eff es ()
produce = void . runThrow @() $
    for_ [1 ..] \(i :: Int) -> do
        when (i == 5) $ throw ()
        output i
        sleep 0.5

consume :: (Input Int :> es, Timer :> es, Emb IO :> es) => Eff es ()
consume = forever do
    liftIO . print =<< input @Int
    sleep 0.5

plus100 :: (Input Int :> es, Output Int :> es, Timer :> es, Emb IO :> es) => Eff es ()
plus100 = forever do
    i <- input @Int
    let o = i + 100
    liftIO $ putStrLn $ "Transform " <> show i <> " to " <> show o
    output o
    sleep 0.5

{- |
The difference between `runMachinery` and `runMachineryIO` is that the former
returns a continuation at the point when the stream has paused, allowing the
stream to be resumed later by providing new inputs and handlers. In terms of
algebraic effects, this means that /non-scoped resumption/ is possible.

Conversely, the latter allows the unrestricted use of `bracket`
(`MonadUnliftIO`) internally, but resumption afterwards is not possible.
`runMachineryIO` operates with a mechanism equivalent to the [Bluefin effect library](https://hackage.haskell.org/package/bluefin-0.0.9.0/docs/Bluefin-Stream.html)
and offers the same functionality.
-}
main :: IO ()
main = runUnliftIO . runTimerIO $ do
    liftIO $ putStrLn "[Parallel effect-based (purer & non-IO-fused) machinery interpretation example]"
    _ <-
        runParallelIO . runMachinery $
            Unit @() @Int produce
                >>> Unit @Int @Int plus100
                >>> Unit @Int @() consume

    liftIO $ putStrLn "\n[IO-fused machinery interpretation example]"

    let produceWithBracket =
            bracket_
                (liftIO $ putStrLn "Acquiring resource")
                (liftIO $ putStrLn "Releasing resource")
                (onlyFOEs produce)

    runMachineryIO_ $
        Unit @() @Int do
            produceWithBracket
            produceWithBracket
            >>> Unit @Int @Int plus100
            >>> Unit @Int @() consume

{-
[Parallel effect-based (purer & non-IO-fused) machinery interpretation example]
Transform 1 to 101
101
Transform 2 to 102
102
Transform 3 to 103
103
Transform 4 to 104
104

[IO-fused machinery interpretation example]
Acquiring resource
Transform 1 to 101
101
Transform 2 to 102
102
Transform 3 to 103
103
Transform 4 to 104
104
Releasing resource
Acquiring resource
Transform 1 to 101
101
Transform 2 to 102
102
Transform 3 to 103
103
Transform 4 to 104
104
Releasing resource
-}