From 96bce5ddfdbb8befd1af9e2e5ef0c15be516c7fd Mon Sep 17 00:00:00 2001 From: Jordan Martinez Date: Mon, 15 Feb 2021 13:31:07 -0800 Subject: [PATCH] Implement ParallelAppMExampleLog --- README.md | 1 + recipes/ParallelAppMExampleLog/.gitignore | 13 +++ recipes/ParallelAppMExampleLog/README.md | 79 +++++++++++++ .../ParallelAppMExampleLog/nodeSupported.md | 2 + recipes/ParallelAppMExampleLog/spago.dhall | 27 +++++ recipes/ParallelAppMExampleLog/src/AppM.purs | 54 +++++++++ recipes/ParallelAppMExampleLog/src/Main.purs | 106 ++++++++++++++++++ recipes/ParallelAppMExampleLog/web/index.html | 13 +++ recipes/ParallelAppMExampleLog/web/index.js | 2 + 9 files changed, 297 insertions(+) create mode 100644 recipes/ParallelAppMExampleLog/.gitignore create mode 100644 recipes/ParallelAppMExampleLog/README.md create mode 100644 recipes/ParallelAppMExampleLog/nodeSupported.md create mode 100644 recipes/ParallelAppMExampleLog/spago.dhall create mode 100644 recipes/ParallelAppMExampleLog/src/AppM.purs create mode 100644 recipes/ParallelAppMExampleLog/src/Main.purs create mode 100644 recipes/ParallelAppMExampleLog/web/index.html create mode 100644 recipes/ParallelAppMExampleLog/web/index.js diff --git a/README.md b/README.md index d859ee46..1ae291c3 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,7 @@ Running a web-compatible recipe: | :heavy_check_mark: | :heavy_check_mark: ([try](https://try.ps.ai/?github=JordanMartinez/purescript-cookbook/master/recipes/MemoizeFibonacci/src/Main.purs)) | [MemoizeFibonacci](recipes/MemoizeFibonacci) | This recipe demonstrates correct and incorrect use of the [`memoize`](https://pursuit.purescript.org/packages/purescript-memoize/docs/Data.Function.Memoize#v:memoize) function by calculating the fibonacci sequence. | | | :heavy_check_mark: ([try](https://try.ps.ai/?github=JordanMartinez/purescript-cookbook/master/recipes/NumbersHalogenHooks/src/Main.purs)) | [NumbersHalogenHooks](recipes/NumbersHalogenHooks) | A Halogen port of the ["Random - Numbers" Elm Example](https://elm-lang.org/examples/numbers). | | | :heavy_check_mark: ([try](https://try.ps.ai/?github=JordanMartinez/purescript-cookbook/master/recipes/NumbersReactHooks/src/Main.purs)) | [NumbersReactHooks](recipes/NumbersReactHooks) | A React port of the ["Random - Numbers" Elm Example](https://elm-lang.org/examples/numbers). | +| :heavy_check_mark: | :heavy_check_mark: ([try](https://try.ps.ai/?github=JordanMartinez/purescript-cookbook/master/recipes/ParallelAppMExampleLog/src/Main.purs)) | [ParallelAppMExampleLog](recipes/ParallelAppMExampleLog) | Demonstrates how to use `parSequence`/`parTraverse` and how to define a `Parallel` instance for a `ReaderT r Aff`-based `AppM` monad. | | :heavy_check_mark: | | [PayloadHttpApiNode](recipes/PayloadHttpApiNode) | Implements a simple 'quote' API using the [payload](https://github.com/hoodunit/purescript-payload) HTTP backend. | | | :heavy_check_mark: ([try](https://try.ps.ai/?github=JordanMartinez/purescript-cookbook/master/recipes/PositionsHalogenHooks/src/Main.purs)) | [PositionsHalogenHooks](recipes/PositionsHalogenHooks) | A Halogen port of the ["Random - Positions" Elm Example](https://elm-lang.org/examples/positions). | | | :heavy_check_mark: ([try](https://try.ps.ai/?github=JordanMartinez/purescript-cookbook/master/recipes/PositionsReactHooks/src/Main.purs)) | [PositionsReactHooks](recipes/PositionsReactHooks) | A React port of the ["Random - Positions" Elm Example](https://elm-lang.org/examples/positions). | diff --git a/recipes/ParallelAppMExampleLog/.gitignore b/recipes/ParallelAppMExampleLog/.gitignore new file mode 100644 index 00000000..fcea5928 --- /dev/null +++ b/recipes/ParallelAppMExampleLog/.gitignore @@ -0,0 +1,13 @@ +/bower_components/ +/node_modules/ +/.pulp-cache/ +/output/ +/generated-docs/ +/.psc-package/ +/.psc* +/.purs* +/.psa* +/.spago +/web-dist/ +/prod-dist/ +/prod/ diff --git a/recipes/ParallelAppMExampleLog/README.md b/recipes/ParallelAppMExampleLog/README.md new file mode 100644 index 00000000..21b58a0e --- /dev/null +++ b/recipes/ParallelAppMExampleLog/README.md @@ -0,0 +1,79 @@ +# ParallelAppMExampleLog + +Demonstrates how to use `parSequence`/`parTraverse` and how to define a `Parallel` instance for a `ReaderT r Aff`-based `AppM` monad. + +The `AppM` file demonstrates how to properly implement a `Parallel` instance when the `AppM` monad is not defined in the same file where it's used. + +## Expected Behavior: + +Prints the following to the console. If viewing this through the web browser, open the console with dev tools first, then reload/refresh the page: +``` +Running computation in 1 second: print all items in array sequentially +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +Running computation in 1 second: print all items in array in parallel +1 +3 +9 +5 +4 +7 +8 +2 +6 +10 +Running computation in 1 second: print all items in array in parallel using parTraverse +9 +8 +7 +3 +10 +2 +6 +1 +5 +4 +Running computation in 1 second: race multiple computations & stop all others when one finishes +Array 2: 1 +Array 3: 1 +Array 3: 2 +Array 4: 1 +Array 3: 3 +Array 1: 1 +Array 2: 2 +Array 2: 3 +Array 4: 2 +Array 3: 4 +Array 4: 3 +Array 2: 4 +Array 1: 2 +Array 4: 4 +Array 3: 5 +Array 1: 3 +Array 4: 5 +Array 4: 6 +Array 3: 6 +Array 2: 5 +Array 2: 6 +Array 2: 7 +Array 4: 7 +Array 1: 4 +Array 2: 8 +Array 3: 7 +Array 4: 8 +Array 1: 5 +Array 3: 8 +Array 4: 9 +Array 2: 9 +Array 3: 9 +Array 1: 6 +Array 4: 10 +``` diff --git a/recipes/ParallelAppMExampleLog/nodeSupported.md b/recipes/ParallelAppMExampleLog/nodeSupported.md new file mode 100644 index 00000000..7bd63dd1 --- /dev/null +++ b/recipes/ParallelAppMExampleLog/nodeSupported.md @@ -0,0 +1,2 @@ +This file just indicates that the node backend is supported. +It is used for CI and autogeneration purposes. diff --git a/recipes/ParallelAppMExampleLog/spago.dhall b/recipes/ParallelAppMExampleLog/spago.dhall new file mode 100644 index 00000000..d010c35f --- /dev/null +++ b/recipes/ParallelAppMExampleLog/spago.dhall @@ -0,0 +1,27 @@ +{ name = "ParallelAppMExampleLog" +, dependencies = + [ "console" + , "control" + , "effect" + , "either" + , "foldable-traversable" + , "functions" + , "functors" + , "maybe" + , "newtype" + , "prelude" + , "psci-support" + , "refs" + , "transformers" + , "aff" + , "random" + , "unfoldable" + ] +, packages = ../../packages.dhall +, sources = [ "recipes/ParallelAppMExampleLog/src/**/*.purs" ] +} +{- +sources does not work with paths relative to this config + sources = [ "src/**/*.purs" ] +Paths must be relative to where this command is run +-} diff --git a/recipes/ParallelAppMExampleLog/src/AppM.purs b/recipes/ParallelAppMExampleLog/src/AppM.purs new file mode 100644 index 00000000..c9db05ff --- /dev/null +++ b/recipes/ParallelAppMExampleLog/src/AppM.purs @@ -0,0 +1,54 @@ +-- | This module demonstrates how to implement a `Parallel` instance +-- | correctly for a `ReaderT r Aff`-based `AppM` Monad +module ParallelAppMExampleLog.AppM + ( AppM(..), runAppM + -- Don't forget to export ParAppM! + -- Otherwise, you get an unhelpful Type Class Constraint error + , ParAppM(..) + , Env + ) where + +import Prelude + +import Control.Monad.Reader (class MonadAsk, ReaderT, asks, runReaderT) +import Control.Parallel (class Parallel, parallel, sequential) +import Effect.Aff (Aff, ParAff) +import Effect.Aff.Class (class MonadAff) +import Effect.Class (class MonadEffect) +import Type.Equality (class TypeEquals, from) + +-- Simple Environment info +type Env = { name :: String } + +-- | The 'sequential' version of our application's monad. +-- | Base monad here is the 'sequential' version of Aff. +newtype AppM a = AppM (ReaderT Env Aff a) + +runAppM :: Env -> AppM ~> Aff +runAppM env (AppM m) = runReaderT m env + +instance monadAskAppM :: TypeEquals e Env => MonadAsk e AppM where + ask = AppM $ asks from + +derive newtype instance functorAppM :: Functor AppM +derive newtype instance applyAppM :: Apply AppM +derive newtype instance applicativeAppM :: Applicative AppM +derive newtype instance bindAppM :: Bind AppM +derive newtype instance monadAppM :: Monad AppM +derive newtype instance monadEffectAppM :: MonadEffect AppM +derive newtype instance monadAffAppM :: MonadAff AppM + +-- | The 'parallel' version of our application's monad. +-- | The base monad here is the parallel version of `Aff`: `ParAff` +newtype ParAppM a = ParAppM (ReaderT Env ParAff a) + +derive newtype instance functorParAppM :: Functor ParAppM +derive newtype instance applyParAppM :: Apply ParAppM +derive newtype instance applicativeParAppM :: Applicative ParAppM + +-- Now we can implement Parallel for our AppM +-- by delegating it to the underlying base monads +instance parallelAppM :: Parallel ParAppM AppM where + parallel (AppM readerT) = ParAppM $ parallel readerT + + sequential (ParAppM readerT) = AppM $ sequential readerT diff --git a/recipes/ParallelAppMExampleLog/src/Main.purs b/recipes/ParallelAppMExampleLog/src/Main.purs new file mode 100644 index 00000000..d28e2774 --- /dev/null +++ b/recipes/ParallelAppMExampleLog/src/Main.purs @@ -0,0 +1,106 @@ +module ParallelAppMExampleLog.Main where + +import Prelude + +import Control.Parallel (class Parallel, parallel, sequential, parOneOf, parTraverse_) +import Control.Monad.Reader (class MonadAsk, ReaderT, asks, runReaderT) +import Data.Foldable (for_) +import Data.Int (toNumber) +import Data.Traversable (traverse) +import Data.Unfoldable (range) +import Effect (Effect) +import Effect.Aff (Aff, ParAff, Milliseconds(..), delay, launchAff_, parallel, sequential) +import Effect.Aff.Class (class MonadAff) +import Effect.Class (class MonadEffect, liftEffect) +import Effect.Console (log) +import Effect.Random (randomInt) +import Type.Equality (class TypeEquals, from) + +-- Create an array from 1 to 10 as Strings. +array :: Array String +array = map show $ range 1 10 + +main :: Effect Unit +main = + -- Starts running a computation and blocks until it finishes. + let runComputation computationName computation = do + liftEffect $ log $ "Running computation in 1 second: " <> computationName + delay (Milliseconds 1000.0) + computation + + in launchAff_ do + runComputation "print all items in array sequentially" do + for_ array \element -> do + liftEffect $ log element + + let delayComputationWithRandomAmount = do + delayAmount <- liftEffect $ randomInt 20 600 + delay $ Milliseconds $ toNumber delayAmount + + runComputation "print all items in array in parallel" do + sequential $ for_ array \element -> do + -- slow down speed of computation based on some random value + -- to show that things are working in parallel. + parallel do + delayComputationWithRandomAmount + liftEffect $ log element + + -- same computation as before but with less boilerplate. + runComputation "print all items in array in parallel using parTraverse" do + array # parTraverse_ \element -> do + delayComputationWithRandomAmount + liftEffect $ log element + + runComputation "race multiple computations & stop all others when one finishes" do + let + -- same as before + arrayComputation index = do + let shownIndex = "Array " <> show index <> ": " + array # traverse \element -> do + delayComputationWithRandomAmount + liftEffect $ log $ shownIndex <> element + + void $ parOneOf [ arrayComputation 1 + , arrayComputation 2 + , arrayComputation 3 + , arrayComputation 4 + ] + +-- Everything below this line is a copy of +-- the `AppM.purs` file (excluding imports) + +-- Simple Environment info +type Env = { name :: String } + +-- | The 'sequential' version of our application's monad. +-- | Base monad here is the 'sequential' version of Aff. +newtype AppM a = AppM (ReaderT Env Aff a) + +runAppM :: Env -> AppM ~> Aff +runAppM env (AppM m) = runReaderT m env + +instance monadAskAppM :: TypeEquals e Env => MonadAsk e AppM where + ask = AppM $ asks from + +derive newtype instance functorAppM :: Functor AppM +derive newtype instance applyAppM :: Apply AppM +derive newtype instance applicativeAppM :: Applicative AppM +derive newtype instance bindAppM :: Bind AppM +derive newtype instance monadAppM :: Monad AppM +derive newtype instance monadEffectAppM :: MonadEffect AppM +derive newtype instance monadAffAppM :: MonadAff AppM + +-- | The 'parallel' version of our application's monad. +-- | The base monad here is the parallel version of `Aff`: `ParAff` +newtype ParAppM a = ParAppM (ReaderT Env ParAff a) + +derive newtype instance functorParAppM :: Functor ParAppM +derive newtype instance applyParAppM :: Apply ParAppM +derive newtype instance applicativeParAppM :: Applicative ParAppM + +-- Now we can implement Parallel for our AppM +-- by delegating it to the underlying base monads +instance parallelAppM :: Parallel ParAppM AppM where + parallel (AppM readerT) = ParAppM $ parallel readerT + + sequential (ParAppM readerT) = AppM $ sequential readerT diff --git a/recipes/ParallelAppMExampleLog/web/index.html b/recipes/ParallelAppMExampleLog/web/index.html new file mode 100644 index 00000000..6b80b685 --- /dev/null +++ b/recipes/ParallelAppMExampleLog/web/index.html @@ -0,0 +1,13 @@ + + + + + + ParallelAppMExampleLog + + + + + + + diff --git a/recipes/ParallelAppMExampleLog/web/index.js b/recipes/ParallelAppMExampleLog/web/index.js new file mode 100644 index 00000000..9a2a327b --- /dev/null +++ b/recipes/ParallelAppMExampleLog/web/index.js @@ -0,0 +1,2 @@ +"use strict"; +require("../../../output/ParallelAppMExampleLog.Main/index.js").main();