Skip to content
This repository was archived by the owner on Jan 26, 2022. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
.stack-work/*
.vscode
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ This repository contains 4 implementations of the program described above, each
- A standard [monad transformer stack](https://github.com/stepchowfun/effects/blob/master/src/MonadTransformers.hs)
- A [free monad](https://github.com/stepchowfun/effects/blob/master/src/FreeMonad.hs)
- The [`Eff` monad](https://github.com/stepchowfun/effects/blob/master/src/ExtensibleEffects.hs) from the "extensible effects" framework
- A [Registry](https://github.com/etorreborre/effects/blob/master/src/Modules.hs) using the [`registry`](https://github.com/etorreborre/registry) library

## Instructions

Expand Down
3 changes: 3 additions & 0 deletions app/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import qualified BespokeMonad
import qualified ExtensibleEffects
import qualified FreeMonad
import qualified MonadTransformers
import qualified Modules

main :: IO ()
main = do
Expand All @@ -17,3 +18,5 @@ main = do
putStrLn . snd $ FreeMonad.interpret FreeMonad.program
putStrLn "Monad transformers:\n"
putStrLn . snd $ MonadTransformers.interpret MonadTransformers.program
putStrLn "Modules:\n"
Modules.app >>= Modules.run
10 changes: 8 additions & 2 deletions effects.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,25 @@ library
exposed-modules: BespokeMonad
, ExtensibleEffects
, FreeMonad
, Modules
, ModulesPure
, MonadTransformers
build-depends: MonadRandom
, base >= 4.7 && < 5
, extensible-effects
, free
, mtl
, protolude
, random
, registry
default-language: Haskell2010

ghc-options: -fhide-source-paths

executable effects-exe
hs-source-dirs: app
main-is: Main.hs
ghc-options: -threaded -rtsopts -with-rtsopts=-N
ghc-options: -threaded -rtsopts -with-rtsopts=-N -fhide-source-paths
build-depends: base
, effects
default-language: Haskell2010
Expand All @@ -52,7 +58,7 @@ test-suite effects-test
, hspec-core
, mtl
, random
ghc-options: -threaded -rtsopts -with-rtsopts=-N
ghc-options: -threaded -rtsopts -with-rtsopts=-N -fhide-source-paths
default-language: Haskell2010

source-repository head
Expand Down
96 changes: 96 additions & 0 deletions src/Modules.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE RecordWildCards #-}

module Modules where

{-

Here we describe functionalities with simple datatypes, records of functions,
and call them "modules"

Then we define "constructors" those modules and specify how they depend on
each other.

Finally we put them in a "Registry" with the https://github.com/etorreborre/registry
package and wire all of them into a top-level "App" running the whole program using
all the "Modules"

-}
import Data.IORef
import Data.Registry
import Protolude as P hiding (get)
import System.Random (getStdRandom, randomR)

-- | Top level application, created from the registry
app :: IO (App IO)
app = make @(IO (App IO)) registry

registry :: Registry
-- inputs for constructors in the registry
'[IO (Logger IO), IO (Random IO), IO (Accumulator IO)]
-- outputs for constructors in the registry
'[IO (Accumulator IO), IO (Logger IO), IO (Random IO), IO (App IO)]
registry =
fun newAccumulator
+: funTo @IO newLogger
+: funTo @IO newRandom
+: funTo @IO newApp
+: end

-- * Logging module, can go into its own library

newtype Logger m = Logger {
info :: forall a . (Show a) => a -> m ()
}

newLogger :: Logger IO
newLogger = Logger P.print

-- * Random module, implemented using the global random generator
-- for simplicity

newtype Random m = Random {
draw :: Int -> Int -> m Int
}

newRandom :: Random IO
newRandom =
Random {
draw = \l h -> getStdRandom (randomR (l, h))
}

-- * Accumulator module
-- the constructor for this module is effectful
-- because we instantiate an IORef

data Accumulator m = Accumulator {
add :: Int -> m ()
, get :: m Int
}

newAccumulator :: IO (Accumulator IO)
newAccumulator = do
counter <- newIORef 0
pure Accumulator {
add = \n -> modifyIORef counter (+n)
, get = readIORef counter
}

-- * The top-level app containing the main program
-- It depends on other modules for its implementation

newtype App m = App {
run :: m ()
}

newApp :: Logger IO -> Random IO -> Accumulator IO -> App IO
newApp Logger{..} Random{..} Accumulator{..} = App {
run = replicateM_ 10 $
do current <- get
_ <- info current
picked <- draw 0 9
add picked
}
63 changes: 63 additions & 0 deletions src/ModulesPure.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}

{-
Pure instantiation of the application
-}
module ModulesPure where

import Control.Monad.Random (Rand, StdGen, getRandomR, mkStdGen,
runRand)
import Control.Monad.State as State (MonadState, StateT, runStateT, get, modify)
import Control.Monad.Writer (MonadWriter, WriterT, runWriterT, tell)
import Data.Registry
import Modules
import Protolude as P hiding (get)
import System.Random (getStdRandom, randomR)

-- Pure interface for the components
type P = WriterT String (StateT Int (Rand StdGen))

-- | Top level application, created from the registry
appPure :: App P
appPure = make @(App P) registryPure

registryPure :: Registry
-- inputs for constructors in the registry
'[Logger P, Random P, Accumulator P]
-- outputs for constructors in the registry
'[Accumulator P, Logger P, Random P, App P]
registryPure =
fun newAccumulatorPure
+: fun newLoggerPure
+: fun newRandomPure
+: fun newAppPure
+: end

newLoggerPure :: Logger P
newLoggerPure = Logger (tell . P.show)

newRandomPure :: Random P
newRandomPure =
Random {
draw = \l h -> getRandomR (l, h)
}

newAccumulatorPure :: Accumulator P
newAccumulatorPure =
Accumulator {
add = \n -> State.modify (+n)
, get = State.get
}

newAppPure :: Logger P -> Random P -> Accumulator P -> App P
newAppPure Logger{..} Random{..} Accumulator{..} = App {
run = replicateM_ 10 $
do current <- get
_ <- info current
picked <- draw 0 9
add picked
}
1 change: 1 addition & 0 deletions stack.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ packages:
# (e.g., acme-missiles-0.3)
extra-deps:
- extensible-effects-3.1.0.0
- registry-0.1.0.4

# Override default flag values for local packages and extra-deps
flags: {}
Expand Down