From 3a86afec84e52c5b5141ecbd7718a42d4a301e89 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Wed, 13 Dec 2017 00:55:41 +0100 Subject: [PATCH 01/12] initial version --- .eslintrc.json | 28 +++++++ .gitignore | 7 ++ .travis.yml | 21 +++++ README.md | 20 +++++ bench/Bench/Main.js | 20 +++++ bench/Bench/Main.purs | 136 ++++++++++++++++++++++++++++++ bower.json | 25 ++++++ package.json | 18 ++++ src/Control/Monad/Ff.js | 91 ++++++++++++++++++++ src/Control/Monad/Ff.purs | 44 ++++++++++ src/Control/Monad/Ff/Class.purs | 15 ++++ test/Test/Main.js | 70 ++++++++++++++++ test/Test/Main.purs | 142 ++++++++++++++++++++++++++++++++ 13 files changed, 637 insertions(+) create mode 100644 .eslintrc.json create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 README.md create mode 100644 bench/Bench/Main.js create mode 100644 bench/Bench/Main.purs create mode 100644 bower.json create mode 100644 package.json create mode 100644 src/Control/Monad/Ff.js create mode 100644 src/Control/Monad/Ff.purs create mode 100644 src/Control/Monad/Ff/Class.purs create mode 100644 test/Test/Main.js create mode 100644 test/Test/Main.purs diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..d584654 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,28 @@ +{ + "parserOptions": { + "ecmaVersion": 5 + }, + "extends": "eslint:recommended", + "env": { + "commonjs": true + }, + "rules": { + "strict": [2, "global"], + "block-scoped-var": 2, + "consistent-return": 2, + "eqeqeq": [2, "smart"], + "guard-for-in": 2, + "no-caller": 2, + "no-extend-native": 2, + "no-loop-func": 2, + "no-new": 2, + "no-param-reassign": 2, + "no-return-assign": 2, + "no-unused-expressions": 2, + "no-use-before-define": 2, + "radix": [2, "always"], + "indent": [2, 2, { "SwitchCase": 1 }], + "quotes": [2, "double"], + "semi": [2, "always"] + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7050558 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +/.* +!/.gitignore +!/.eslintrc.json +!/.travis.yml +/bower_components/ +/node_modules/ +/output/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..27b95cd --- /dev/null +++ b/.travis.yml @@ -0,0 +1,21 @@ +language: node_js +dist: trusty +sudo: required +node_js: stable +env: + - PATH=$HOME/purescript:$PATH +install: + - TAG=$(wget -q -O - https://github.com/purescript/purescript/releases/latest --server-response --max-redirect 0 2>&1 | sed -n -e 's/.*Location:.*tag\///p') + - wget -O $HOME/purescript.tar.gz https://github.com/purescript/purescript/releases/download/$TAG/linux64.tar.gz + - tar -xvf $HOME/purescript.tar.gz -C $HOME/ + - chmod a+x $HOME/purescript + - npm install -g bower + - npm install + - bower install +script: + - npm run -s build +after_success: +- >- + test $TRAVIS_TAG && + echo $GITHUB_TOKEN | pulp login && + echo y | pulp publish --no-push diff --git a/README.md b/README.md new file mode 100644 index 0000000..957b33d --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +# purescript-ef EXPERIMENTAL + +[![Latest release](http://img.shields.io/github/release/safareli/purescript-ef.svg)](https://github.com/safareli/purescript-ef/releases) +[![Build status](https://travis-ci.org/safareli/purescript-ef.svg?branch=master)](https://travis-ci.org/safareli/purescript-ef) + +Faster and safer implementation of the Effect monad. + +## Ef vs Eff + +`Ef` is faster then `Eff`, plus it's stacksafe. Also it's faster to type :p + +## Installation + +``` +bower install purescript-ef +``` + +## Documentation + +Module documentation is [published on Pursuit](http://pursuit.purescript.org/packages/purescript-ef). diff --git a/bench/Bench/Main.js b/bench/Bench/Main.js new file mode 100644 index 0000000..75c5500 --- /dev/null +++ b/bench/Bench/Main.js @@ -0,0 +1,20 @@ +"use strict"; + +exports.mkArr = function(){ + return []; +}; + +exports.pushToArr = function(xs) { + return function(x) { + return function() { + xs.push(x); + return x; + }; + }; +}; + +exports.log = function(x) { + return function(){ + console.log(x) + } +}; \ No newline at end of file diff --git a/bench/Bench/Main.purs b/bench/Bench/Main.purs new file mode 100644 index 0000000..433c462 --- /dev/null +++ b/bench/Bench/Main.purs @@ -0,0 +1,136 @@ +module Bench.Main where + +import Prelude + +import Control.Monad.Ef (Ef) +import Control.Monad.Ef.Class (liftEf) +import Control.Monad.Eff (Eff) +import Control.Monad.Eff.Class (class MonadEff, liftEff) +import Control.Monad.Eff.Console (CONSOLE) +import Control.Monad.Eff.Unsafe (unsafePerformEff) +import Data.Traversable (for_, intercalate) +import Performance.Minibench (BenchResult, benchWith', withUnits) + + +type BenchEff = (console :: CONSOLE) + +testApply :: forall m. MonadEff BenchEff m => Int -> m Unit +testApply n' = do + arr <- liftEff mkArr + applyLoop (void <<< liftEff <<< pushToArr arr) n' + where + applyLoop :: Monad m => (Int -> m Unit) -> Int -> m Unit + applyLoop eff max = go (pure unit) 0 + where + go acc n | n == max = acc + go acc n = go (acc <* eff n) (n + 1) + + +testBindRight :: forall m. MonadEff BenchEff m => Int -> m Unit +testBindRight n' = do + arr <- liftEff mkArr + bindRightLoop (void <<< liftEff <<< pushToArr arr) n' + where + bindRightLoop :: Monad m => (Int -> m Unit) -> Int -> m Unit + bindRightLoop eff max = go (pure unit) 0 + where + go acc n | n == max = acc + go acc n = go (eff (max - n - 1) >>= const acc) (n + 1) + + +testBindLeft :: forall m. MonadEff BenchEff m => Int -> m Unit +testBindLeft n' = do + arr <- liftEff mkArr + bindLeftLoop (void <<< liftEff <<< pushToArr arr) n' + where + bindLeftLoop :: Monad m => (Int -> m Unit) -> Int -> m Unit + bindLeftLoop eff max = go (pure unit) 0 + where + go acc n | n == max = acc + go acc n = go (acc >>= const (eff n)) (n + 1) + + +testMap :: forall m. MonadEff BenchEff m => Int -> m Unit +testMap n = do + res <- mapLoop n (pure 0) + pure unit + where + mapLoop :: Monad m => Int -> m Int -> m Int + mapLoop max i = + if max == 0 + then i + else mapLoop (max - 1) (map (_ + 1) i) + + +main :: Eff BenchEff Unit +main = do + log header + bench "bind assocR" testBindRight testBindRight [100, 500, 1000, 2000, 4000, 8000, 10000] + bench "bind assocL" testMap testMap [100, 500, 1000, 2000, 4000, 8000] + bench "map" testMap testMap [100, 500, 1000, 2000, 4000, 5000] + bench "apply" testApply testApply [100, 500, 1000, 2000, 4000, 5000] + +extended :: Eff BenchEff Unit +extended = do + log header + timed ["bind assocR", "Ef", "100000"] $ testBindRight 100000 + timed ["bind assocR", "Ef", "1000000"] $ testBindRight 1000000 -- ~ 1 sec +--timed ["bind assocR", "Ef", "10000000"] $ testBindRight 10000000 -- ~ 10 sec +--timed ["bind assocR", "Ef", "100000000"] $ testBindRight 100000000 -- JavaScript heap out of memory + timed ["bind assocL", "Ef", "20000"] $ testBindLeft 20000 + timed ["bind assocL", "Ef", "40000"] $ testBindLeft 40000 + timed ["bind assocL", "Ef", "80000"] $ testBindLeft 80000 + timed ["map", "Ef", "10000"] $ testMap 10000 + timed ["map", "Ef", "20000"] $ testMap 20000 + timed ["map", "Ef", "40000"] $ testMap 40000 + timed ["map", "Ef", "80000"] $ testMap 80000 + timed ["apply", "Ef", "10000"] $ testApply 10000 + timed ["apply", "Ef", "20000"] $ testApply 20000 + timed ["apply", "Ef", "40000"] $ testApply 40000 + +header :: String +header = + "| bench | type | n | mean | stddev | min | max |\n" <> + "| ----- | ---- | - | ---- | ------ | --- | --- |" + +bench + :: String + -> (Int -> Eff BenchEff Unit) + -> (Int -> Ef BenchEff Unit) + -> Array Int + -> Eff BenchEff Unit +bench name buildEff buildEf vals = for_ vals \val -> do + logBench [name <> " build", "Eff", show val] $ benchWith' 2000 \_ -> buildEff val + logBench' [name <> " build", "Ef", show val] $ benchWith' 2000 \_ -> buildEf val + let eff = liftEff $ buildEff val + logBench [name <> " run", "Eff", show val] $ benchWith' 2000 \_ -> unsafePerformEff eff + let ef = liftEf $ buildEf val + logBench' [name <> " run", "Ef", show val] $ benchWith' 2000 \_ -> unsafePerformEff ef + + +timed :: Array String -> Ef BenchEff Unit -> Eff BenchEff Unit +timed msg eff = + logBench' msg $ benchWith' 5 \_ -> unsafePerformEff $ liftEf eff + +logBench'' :: (String -> String) -> Array String -> Eff BenchEff BenchResult -> Eff BenchEff Unit +logBench'' f msg benchEff = do + res <- benchEff + let + logStr = intercalate " | " + $ map f + $ append msg + $ map withUnits [res.mean, res.stdDev, res.min, res.max] + log $ "| " <> logStr <> " |" + +logBench :: Array String -> Eff BenchEff BenchResult -> Eff BenchEff Unit +logBench = logBench'' id + +logBench' :: Array String -> Eff BenchEff BenchResult -> Eff BenchEff Unit +logBench' = logBench'' \s -> "**" <> s <> "**" + +foreign import data Arr :: Type -> Type + +foreign import mkArr :: forall e a. Eff e (Arr a) +foreign import pushToArr :: forall e a. Arr a -> a -> Eff e a +foreign import log :: forall e a. a -> Eff e Unit + diff --git a/bower.json b/bower.json new file mode 100644 index 0000000..f20e7c0 --- /dev/null +++ b/bower.json @@ -0,0 +1,25 @@ +{ + "name": "purescript-ef", + "homepage": "https://github.com/safareli/purescript-ef", + "description": "Faster and safer implementation of the Effect monad", + "license": "MIT", + "repository": { + "type": "git", + "url": "git://github.com/safareli/purescript-ef.git" + }, + "ignore": [ + "**/.*", + "bower_components", + "node_modules", + "output", + "test", + "bower.json", + "package.json" + ], + "dependencies": { + "purescript-prelude": "^3.0.0", + "purescript-eff": "^3.1.0", + "purescript-foldable-traversable": "^3.6.1", + "purescript-minibench": "safareli/purescript-minibench#un-log" + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..fe412d6 --- /dev/null +++ b/package.json @@ -0,0 +1,18 @@ +{ + "private": true, + "scripts": { + "clean": "rimraf output && rimraf .pulp-cache", + "build": "eslint src && pulp build -- --censor-lib --strict", + "test": "pulp test", + "bench:build": "purs compile 'bench/**/*.purs' 'src/**/*.purs' 'bower_components/*/src/**/*.purs'", + "bench:run": "node --expose-gc -e 'require(\"./output/Bench.Main/index.js\").main()'", + "bench:run:extended": "node --expose-gc -e 'require(\"./output/Bench.Main/index.js\").extended()'", + "bench": "npm run bench:build && npm run bench:run" + }, + "devDependencies": { + "eslint": "^3.17.1", + "pulp": "^11.0.0", + "purescript-psa": "^0.5.1", + "rimraf": "^2.6.1" + } +} diff --git a/src/Control/Monad/Ff.js b/src/Control/Monad/Ff.js new file mode 100644 index 0000000..c3ef53e --- /dev/null +++ b/src/Control/Monad/Ff.js @@ -0,0 +1,91 @@ +"use strict"; + + +// Ef a +// = { tag: "EFFECT", _0 :: () -> a, _1 :: Void } +// | { tag: "PURE", _0 :: a, _1 :: Void } +// | { tag: "MAP", _0 :: b -> a, _1 :: Ef b } +// | { tag: "APPLY", _0 :: Ef b, _1 :: Ef (b -> a) } +// | { tag: "BIND", _0 :: b -> Ef a, _1 :: Ef b } + +// Operation a b +// = { tag: "MAP", _0 :: a -> b } +// | { tag: "APPLY", _0 :: Ef a } +// | { tag: "APPLY_FUNC", _0 :: a -> b } +// | { tag: "BIND", _0 :: a -> Ef b } + + +function Ef(tag, _0, _1) { + this.tag = tag; + this._0 = _0; + this._1 = _1; +} + +var EFFECT = "EFFECT"; +var PURE = "PURE"; +var MAP = "MAP"; +var APPLY = "APPLY"; +var BIND = "BIND"; +var APPLY_FUNC = "APPLY_FUNC"; + +exports.liftEffE = function (f) { + return new Ef(EFFECT, f); +}; + +exports.pureE = function (x) { + return new Ef(PURE, x); +}; + +exports.mapE = function (f) { + return function (eff) { + return new Ef(MAP, f, eff); + }; +}; + +exports.applyE = function (effF) { + return function (eff) { + return new Ef(APPLY, eff, effF); + }; +}; + +exports.bindE = function (eff) { + return function (f) { + return new Ef(BIND, f, eff); + }; +}; + +exports.toEff = function (inputEff) { + return function() { + var operations = []; + var eff = inputEff; + var res; + var op; + effLoop: for (;;) { + if (eff.tag === MAP || eff.tag === BIND || eff.tag === APPLY) { + operations.unshift(eff); + eff = eff._1; + } else { + if (eff.tag === EFFECT) { + res = eff._0(); + } else { // eff.tag === PURE + res = eff._0; + } + while ((op = operations.shift())) { + if (op.tag === MAP) { + res = op._0(res); + } else if (op.tag === APPLY_FUNC) { + res = op._0(res); + } else if (op.tag === APPLY) { + eff = op._0; + operations.unshift(new Ef(APPLY_FUNC, res)); + continue effLoop; + } else { // op.tag === BIND + eff = op._0(res); + continue effLoop; + } + } + return res; + } + } + }; +}; \ No newline at end of file diff --git a/src/Control/Monad/Ff.purs b/src/Control/Monad/Ff.purs new file mode 100644 index 0000000..bf890e2 --- /dev/null +++ b/src/Control/Monad/Ff.purs @@ -0,0 +1,44 @@ +module Control.Monad.Ef + ( Ef + , toEff + ) where + +import Control.Applicative (class Applicative) +-- import Control.Applicative (class Applicative, liftA1) +import Control.Apply (class Apply) +import Control.Bind (class Bind) +import Control.Monad (class Monad) +-- import Control.Monad (class Monad, ap) +import Control.Monad.Eff (Eff, kind Effect) +import Data.Functor (class Functor) +import Control.Monad.Eff.Class (class MonadEff) + + +foreign import data Ef :: # Effect -> Type -> Type + +instance functorEf :: Functor (Ef e) where + map = mapE + -- map = liftA1 + +instance applyEf :: Apply (Ef e) where + apply = applyE + -- apply = ap + +instance applicativeEf :: Applicative (Ef e) where + pure = pureE + +instance bindEf :: Bind (Ef e) where + bind = bindE + +instance monadEf :: Monad (Ef e) + +instance monadEEFff :: MonadEff eff (Ef eff) where + liftEff = liftEffE + +foreign import toEff :: forall e a. Ef e a -> Eff e a + +foreign import liftEffE :: forall e a. Eff e a -> Ef e a +foreign import mapE :: forall e a b. (a -> b) -> Ef e a -> Ef e b +foreign import applyE :: forall e a b. Ef e (a -> b) -> Ef e a-> Ef e b +foreign import pureE :: forall e a. a -> Ef e a +foreign import bindE :: forall e a b. Ef e a -> (a -> Ef e b) -> Ef e b \ No newline at end of file diff --git a/src/Control/Monad/Ff/Class.purs b/src/Control/Monad/Ff/Class.purs new file mode 100644 index 0000000..0e898e2 --- /dev/null +++ b/src/Control/Monad/Ff/Class.purs @@ -0,0 +1,15 @@ +module Control.Monad.Ef.Class where + +import Control.Category (id) +import Control.Monad (class Monad) +import Control.Monad.Ef (Ef, toEff) +import Control.Monad.Eff (Eff) + +class Monad m <= MonadEf eff m | m -> eff where + liftEf :: forall a. Ef eff a -> m a + +instance monadEfEf :: MonadEf eff (Ef eff) where + liftEf = id + +instance monadEfEff :: MonadEf eff (Eff eff) where + liftEf = toEff diff --git a/test/Test/Main.js b/test/Test/Main.js new file mode 100644 index 0000000..3feffb8 --- /dev/null +++ b/test/Test/Main.js @@ -0,0 +1,70 @@ +"use strict"; + +exports.mkArr = function(){ + return []; +}; + +exports.unArr = function(xs){ + return xs.slice(0); +}; + +exports.pushToArr = function(xs) { + return function(x) { + return function() { + xs.push(x); + return x; + }; + }; +}; + +exports.assert = function(isOk) { + return function(msg) { + return function() { + if (isOk == false) { + throw new Error("assertion failed: " + msg); + }; + }; + }; +}; + +exports.naturals = function(n) { + var res = []; + for (var index = 0; index < n; index++) { + res[index] = index; + } + return res; +}; + +exports.log = function(x) { + return function(){ + console.log(x) + } +}; + + +exports.time = function(x) { + return function(){ + console.time(x) + } +}; + + +exports.timeEnd = function(x) { + return function(){ + console.timeEnd(x) + } +}; + +exports.plus_ = function(a) { + return function(b) { + return a + b + } +} + +exports.stackSize = function computeCallStack() { + try { + return 1 + computeCallStack() + } catch (_) { + return 1 + } +}() \ No newline at end of file diff --git a/test/Test/Main.purs b/test/Test/Main.purs new file mode 100644 index 0000000..71f298e --- /dev/null +++ b/test/Test/Main.purs @@ -0,0 +1,142 @@ +module Test.Main where + +import Prelude + +import Control.Monad.Eff (Eff) +import Control.Apply (lift2) +import Control.Monad.Eff.Class (class MonadEff, liftEff) +import Control.Monad.Ef (Ef) +import Control.Monad.Ef.Class (liftEf) +import Data.Traversable (for_) +import Performance.Minibench (benchWith) +import Control.Monad.Eff.Unsafe (unsafePerformEff) +import Control.Monad.Eff.Console (CONSOLE) + + +type TestEff = (console :: CONSOLE) + +main :: Eff TestEff Unit +main = liftEf mainF + + +testLift2 :: Ef TestEff Unit +testLift2 = do + arr <- mkArr' + res <- liftEff (pushToArr arr 1) `lift2 plus_` liftEff (pushToArr arr 2) + res' <- liftEff (pure 1) `lift2 plus_` liftEff (pure 2) + assert' ([1, 2] == unArr arr) "lift2 1/3" + assert' (3 == res') "lift2 2/3" + assert' (3 == res) "lift2 3/3" + + +testApply :: forall m. MonadEff TestEff m => Int -> m Unit +testApply n' = do + arr <- liftEff mkArr + applyLoop (void <<< liftEff <<< pushToArr arr) n' + liftEff $ assert (naturals n' == unArr arr) $ "apply " <> show n' + where + applyLoop :: Monad m => (Int -> m Unit) -> Int -> m Unit + applyLoop eff max = go (pure unit) 0 + where + go acc n | n == max = acc + go acc n = go (acc <* eff n) (n + 1) + + + +testBindRight :: forall m. MonadEff TestEff m => Int -> m Unit +testBindRight n' = do + arr <- liftEff mkArr + bindRightLoop (void <<< liftEff <<< pushToArr arr) n' + liftEff $ assert (naturals n' == unArr arr) $ "bind right " <> show n' + where + bindRightLoop :: Monad m => (Int -> m Unit) -> Int -> m Unit + bindRightLoop eff max = go (pure unit) 0 + where + go acc n | n == max = acc + go acc n = go (eff (max - n - 1) >>= const acc) (n + 1) + + + + +testBindLeft :: forall m. MonadEff TestEff m => Int -> m Unit +testBindLeft n' = do + arr <- liftEff mkArr + bindLeftLoop (void <<< liftEff <<< pushToArr arr) n' + liftEff $ assert (naturals n' == unArr arr) $ "bind left " <> show n' + where + bindLeftLoop :: Monad m => (Int -> m Unit) -> Int -> m Unit + bindLeftLoop eff max = go (pure unit) 0 + where + go acc n | n == max = acc + go acc n = go (acc >>= const (eff n)) (n + 1) + + +testMap :: forall m. MonadEff TestEff m => Int -> m Unit +testMap n = do + res <- mapLoop n (pure 0) + liftEff $ assert (res == n) $ "map " <> show n + where + mapLoop :: Monad m => Int -> m Int -> m Int + mapLoop max i = + if max == 0 + then i + else mapLoop (max - 1) (map (_ + 1) i) + + +mainF :: Ef TestEff Unit +mainF = do + testLift2 + log' "\n" + + test "testBindRight" testBindRight testBindRight + test "testBindLeft" testMap testMap + test "testMap" testMap testMap + test "testApply" testApply testApply + + where + test + :: String + -> (Int -> Eff TestEff Unit) + -> (Int -> Ef TestEff Unit) + -> Ef TestEff Unit + test name eff ef = do + log' name + log' "eff" + liftEff $ eff 100 + log' "Ef" + liftEf $ ef 100 + + +foreign import data Arr :: Type -> Type + + +foreign import mkArr :: forall e a. Eff e (Arr a) +mkArr' :: forall e a. Ef e (Arr a) +mkArr' = liftEff mkArr + +foreign import pushToArr :: forall e a. Arr a -> a -> Eff e a +pushToArr' :: forall e a. Arr a -> a -> Ef e a +pushToArr' xs x = liftEff $ pushToArr xs x + +foreign import assert :: forall e. Boolean -> String -> Eff e Unit +assert' :: forall e. Boolean -> String -> Ef e Unit +assert' b msg = liftEff $ assert b msg + +foreign import log :: forall e a. a -> Eff e Unit +log' :: forall e a. a -> Ef e Unit +log' x = liftEff $ log x + +timed :: forall m. MonadEff TestEff m => String -> m Unit -> m Unit +timed msg eff = do + liftEff $ log $ msg <> " ... started" + liftEff (time msg) *> eff <* liftEff (timeEnd msg) + liftEff $ log "\n" + +foreign import time :: forall e. String -> Eff e Unit +foreign import timeEnd :: forall e. String -> Eff e Unit + + +foreign import unArr :: forall a. Arr a -> Array a +foreign import naturals :: Int -> Array Int +foreign import plus_ :: Int -> Int -> Int +foreign import stackSize :: Int \ No newline at end of file From fc36978c9c181c1bcdeb285b31d1ba4e19d3116d Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Wed, 13 Dec 2017 01:24:08 +0100 Subject: [PATCH 02/12] use push/pop instead of unshift/shift --- src/Control/Monad/Ff.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Control/Monad/Ff.js b/src/Control/Monad/Ff.js index c3ef53e..d9e7a14 100644 --- a/src/Control/Monad/Ff.js +++ b/src/Control/Monad/Ff.js @@ -62,7 +62,7 @@ exports.toEff = function (inputEff) { var op; effLoop: for (;;) { if (eff.tag === MAP || eff.tag === BIND || eff.tag === APPLY) { - operations.unshift(eff); + operations.push(eff); eff = eff._1; } else { if (eff.tag === EFFECT) { @@ -70,14 +70,14 @@ exports.toEff = function (inputEff) { } else { // eff.tag === PURE res = eff._0; } - while ((op = operations.shift())) { + while ((op = operations.pop())) { if (op.tag === MAP) { res = op._0(res); } else if (op.tag === APPLY_FUNC) { res = op._0(res); } else if (op.tag === APPLY) { eff = op._0; - operations.unshift(new Ef(APPLY_FUNC, res)); + operations.push(new Ef(APPLY_FUNC, res)); continue effLoop; } else { // op.tag === BIND eff = op._0(res); From 637e9225ba9adc889611c5d6bfbd040922dc3520 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Wed, 13 Dec 2017 01:26:39 +0100 Subject: [PATCH 03/12] use property mutation instead of array push as base effect --- bench/Bench/Main.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bench/Bench/Main.js b/bench/Bench/Main.js index 75c5500..0001a5b 100644 --- a/bench/Bench/Main.js +++ b/bench/Bench/Main.js @@ -1,14 +1,14 @@ "use strict"; exports.mkArr = function(){ - return []; + return { count: 0 }; }; exports.pushToArr = function(xs) { return function(x) { return function() { - xs.push(x); - return x; + xs.count += 1 + return xs; }; }; }; From a1c4cec8264e88ceebb7812b9d3ad2d36e56092a Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Wed, 13 Dec 2017 01:32:18 +0100 Subject: [PATCH 04/12] update benchmar log formating --- bench/Bench/Main.purs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bench/Bench/Main.purs b/bench/Bench/Main.purs index 433c462..b87fbb4 100644 --- a/bench/Bench/Main.purs +++ b/bench/Bench/Main.purs @@ -117,9 +117,8 @@ logBench'' f msg benchEff = do res <- benchEff let logStr = intercalate " | " - $ map f $ append msg - $ map withUnits [res.mean, res.stdDev, res.min, res.max] + $ map (f <<< withUnits) [res.mean, res.stdDev, res.min, res.max] log $ "| " <> logStr <> " |" logBench :: Array String -> Eff BenchEff BenchResult -> Eff BenchEff Unit From 119508d10f0becf132516482f452fdd9faccd113 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Wed, 13 Dec 2017 01:42:20 +0100 Subject: [PATCH 05/12] add more bench --- bench/Bench/Main.purs | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/bench/Bench/Main.purs b/bench/Bench/Main.purs index b87fbb4..ce61394 100644 --- a/bench/Bench/Main.purs +++ b/bench/Bench/Main.purs @@ -73,20 +73,25 @@ main = do extended :: Eff BenchEff Unit extended = do log header + timed ["bind assocR", "Ef", "20000"] $ testBindRight 20000 + timed ["bind assocR", "Ef", "50000"] $ testBindRight 50000 timed ["bind assocR", "Ef", "100000"] $ testBindRight 100000 - timed ["bind assocR", "Ef", "1000000"] $ testBindRight 1000000 -- ~ 1 sec ---timed ["bind assocR", "Ef", "10000000"] $ testBindRight 10000000 -- ~ 10 sec ---timed ["bind assocR", "Ef", "100000000"] $ testBindRight 100000000 -- JavaScript heap out of memory + timed ["bind assocR", "Ef", "1000000"] $ testBindRight 1000000 timed ["bind assocL", "Ef", "20000"] $ testBindLeft 20000 - timed ["bind assocL", "Ef", "40000"] $ testBindLeft 40000 - timed ["bind assocL", "Ef", "80000"] $ testBindLeft 80000 + timed ["bind assocL", "Ef", "50000"] $ testBindLeft 50000 + timed ["bind assocL", "Ef", "100000"] $ testBindLeft 100000 + timed ["bind assocL", "Ef", "1000000"] $ testBindLeft 1000000 timed ["map", "Ef", "10000"] $ testMap 10000 timed ["map", "Ef", "20000"] $ testMap 20000 - timed ["map", "Ef", "40000"] $ testMap 40000 - timed ["map", "Ef", "80000"] $ testMap 80000 + timed ["map", "Ef", "50000"] $ testMap 50000 + timed ["map", "Ef", "100000"] $ testMap 100000 + timed ["map", "Ef", "1000000"] $ testMap 1000000 + timed ["map", "Ef", "10000000"] $ testMap 10000000 timed ["apply", "Ef", "10000"] $ testApply 10000 timed ["apply", "Ef", "20000"] $ testApply 20000 - timed ["apply", "Ef", "40000"] $ testApply 40000 + timed ["apply", "Ef", "50000"] $ testApply 50000 + timed ["apply", "Ef", "100000"] $ testApply 100000 + timed ["apply", "Ef", "1000000"] $ testApply 1000000 header :: String header = @@ -100,12 +105,12 @@ bench -> Array Int -> Eff BenchEff Unit bench name buildEff buildEf vals = for_ vals \val -> do - logBench [name <> " build", "Eff", show val] $ benchWith' 2000 \_ -> buildEff val - logBench' [name <> " build", "Ef", show val] $ benchWith' 2000 \_ -> buildEf val + logBench [name <> " build", "Eff", show val] $ benchWith' 1000 \_ -> buildEff val + logBench' [name <> " build", "Ef", show val] $ benchWith' 1000 \_ -> buildEf val let eff = liftEff $ buildEff val - logBench [name <> " run", "Eff", show val] $ benchWith' 2000 \_ -> unsafePerformEff eff + logBench [name <> " run", "Eff", show val] $ benchWith' 1000 \_ -> unsafePerformEff eff let ef = liftEf $ buildEf val - logBench' [name <> " run", "Ef", show val] $ benchWith' 2000 \_ -> unsafePerformEff ef + logBench' [name <> " run", "Ef", show val] $ benchWith' 1000 \_ -> unsafePerformEff ef timed :: Array String -> Ef BenchEff Unit -> Eff BenchEff Unit From f96efe8fd693bef2561ce5c5c67f4bb1e9e5cd46 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Wed, 13 Dec 2017 17:03:20 +0100 Subject: [PATCH 06/12] use effect function directly without wraping in a node --- src/Control/Monad/Ff.js | 56 ++++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/src/Control/Monad/Ff.js b/src/Control/Monad/Ff.js index d9e7a14..7c8126f 100644 --- a/src/Control/Monad/Ff.js +++ b/src/Control/Monad/Ff.js @@ -2,7 +2,7 @@ // Ef a -// = { tag: "EFFECT", _0 :: () -> a, _1 :: Void } +// = () -> a // | { tag: "PURE", _0 :: a, _1 :: Void } // | { tag: "MAP", _0 :: b -> a, _1 :: Ef b } // | { tag: "APPLY", _0 :: Ef b, _1 :: Ef (b -> a) } @@ -21,15 +21,14 @@ function Ef(tag, _0, _1) { this._1 = _1; } -var EFFECT = "EFFECT"; var PURE = "PURE"; var MAP = "MAP"; var APPLY = "APPLY"; var BIND = "BIND"; var APPLY_FUNC = "APPLY_FUNC"; -exports.liftEffE = function (f) { - return new Ef(EFFECT, f); +exports.liftEffE = function (eff) { + return eff; }; exports.pureE = function (x) { @@ -60,32 +59,37 @@ exports.toEff = function (inputEff) { var eff = inputEff; var res; var op; + var tag; effLoop: for (;;) { - if (eff.tag === MAP || eff.tag === BIND || eff.tag === APPLY) { - operations.push(eff); - eff = eff._1; - } else { - if (eff.tag === EFFECT) { - res = eff._0(); - } else { // eff.tag === PURE - res = eff._0; + tag = eff.tag; + if (tag !== undefined) { + if (tag === MAP || tag === BIND || tag === APPLY) { + operations.push(eff); + eff = eff._1; + continue; } - while ((op = operations.pop())) { - if (op.tag === MAP) { - res = op._0(res); - } else if (op.tag === APPLY_FUNC) { - res = op._0(res); - } else if (op.tag === APPLY) { - eff = op._0; - operations.push(new Ef(APPLY_FUNC, res)); - continue effLoop; - } else { // op.tag === BIND - eff = op._0(res); - continue effLoop; - } + // here `tag === PURE` + res = eff._0; + } else { + // here `typeof eff == "function"` + res = eff(); + } + + while ((op = operations.pop())) { + if (op.tag === MAP) { + res = op._0(res); + } else if (op.tag === APPLY_FUNC) { + res = op._0(res); + } else if (op.tag === APPLY) { + eff = op._0; + operations.push(new Ef(APPLY_FUNC, res)); + continue effLoop; + } else { // op.tag === BIND + eff = op._0(res); + continue effLoop; } - return res; } + return res; } }; }; \ No newline at end of file From d089f049a3795efa0c5a7cc6f42543acd15e5660 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Wed, 13 Dec 2017 20:16:05 +0100 Subject: [PATCH 07/12] in toEff, optimise case when Ef is effect node --- src/Control/Monad/Ff.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Control/Monad/Ff.js b/src/Control/Monad/Ff.js index 7c8126f..e6b3aee 100644 --- a/src/Control/Monad/Ff.js +++ b/src/Control/Monad/Ff.js @@ -54,6 +54,9 @@ exports.bindE = function (eff) { }; exports.toEff = function (inputEff) { + if (typeof inputEff === "function") { + return inputEff; + } return function() { var operations = []; var eff = inputEff; From 487ff162b9a0455d530e0f343c9b8c64c8a36355 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Wed, 13 Dec 2017 20:32:22 +0100 Subject: [PATCH 08/12] add tests to travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 27b95cd..1bf8bc5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,6 +14,7 @@ install: - bower install script: - npm run -s build + - npm run -s test after_success: - >- test $TRAVIS_TAG && From fdf9d6e68c9facd0c07956c61388e72016fff969 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Wed, 13 Dec 2017 20:46:42 +0100 Subject: [PATCH 09/12] add Aff to benchamrks --- .travis.yml | 3 ++- bench/Bench/Main.purs | 57 +++++++++++++++++++++---------------------- bower.json | 7 ++++-- 3 files changed, 35 insertions(+), 32 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1bf8bc5..316e760 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,9 +11,10 @@ install: - chmod a+x $HOME/purescript - npm install -g bower - npm install - - bower install + - bower install --prod script: - npm run -s build + - bower install - npm run -s test after_success: - >- diff --git a/bench/Bench/Main.purs b/bench/Bench/Main.purs index ce61394..2d601fd 100644 --- a/bench/Bench/Main.purs +++ b/bench/Bench/Main.purs @@ -5,6 +5,7 @@ import Prelude import Control.Monad.Ef (Ef) import Control.Monad.Ef.Class (liftEf) import Control.Monad.Eff (Eff) +import Control.Monad.Aff (Aff, launchAff_) import Control.Monad.Eff.Class (class MonadEff, liftEff) import Control.Monad.Eff.Console (CONSOLE) import Control.Monad.Eff.Unsafe (unsafePerformEff) @@ -65,57 +66,55 @@ testMap n = do main :: Eff BenchEff Unit main = do log header - bench "bind assocR" testBindRight testBindRight [100, 500, 1000, 2000, 4000, 8000, 10000] - bench "bind assocL" testMap testMap [100, 500, 1000, 2000, 4000, 8000] - bench "map" testMap testMap [100, 500, 1000, 2000, 4000, 5000] - bench "apply" testApply testApply [100, 500, 1000, 2000, 4000, 5000] + bench3 ">>=R" testBindRight testBindRight testBindRight [100, 500, 1000, 2000, 4000, 8000, 10000] + bench3 ">>=L" testBindLeft testBindLeft testBindLeft [100, 500, 1000, 2000, 4000, 8000] + bench3 "map" testMap testMap testMap [100, 500, 1000, 2000, 4000, 5000] + bench3 "apply" testApply testApply testApply [100, 500, 1000, 2000, 4000, 5000] extended :: Eff BenchEff Unit extended = do log header - timed ["bind assocR", "Ef", "20000"] $ testBindRight 20000 - timed ["bind assocR", "Ef", "50000"] $ testBindRight 50000 - timed ["bind assocR", "Ef", "100000"] $ testBindRight 100000 - timed ["bind assocR", "Ef", "1000000"] $ testBindRight 1000000 - timed ["bind assocL", "Ef", "20000"] $ testBindLeft 20000 - timed ["bind assocL", "Ef", "50000"] $ testBindLeft 50000 - timed ["bind assocL", "Ef", "100000"] $ testBindLeft 100000 - timed ["bind assocL", "Ef", "1000000"] $ testBindLeft 1000000 - timed ["map", "Ef", "10000"] $ testMap 10000 - timed ["map", "Ef", "20000"] $ testMap 20000 - timed ["map", "Ef", "50000"] $ testMap 50000 - timed ["map", "Ef", "100000"] $ testMap 100000 - timed ["map", "Ef", "1000000"] $ testMap 1000000 - timed ["map", "Ef", "10000000"] $ testMap 10000000 - timed ["apply", "Ef", "10000"] $ testApply 10000 - timed ["apply", "Ef", "20000"] $ testApply 20000 - timed ["apply", "Ef", "50000"] $ testApply 50000 - timed ["apply", "Ef", "100000"] $ testApply 100000 - timed ["apply", "Ef", "1000000"] $ testApply 1000000 + bench2 ">>=R" testBindRight testBindRight [20000, 50000, 100000, 1000000] + bench2 ">>=L" testBindLeft testBindLeft [20000, 50000, 100000, 1000000] + bench2 "map" testMap testMap [10000, 20000, 50000, 100000, 1000000, 10000000] + bench2 "apply" testApply testApply [10000, 20000, 50000, 100000, 1000000] header :: String header = "| bench | type | n | mean | stddev | min | max |\n" <> "| ----- | ---- | - | ---- | ------ | --- | --- |" -bench +bench3 :: String -> (Int -> Eff BenchEff Unit) -> (Int -> Ef BenchEff Unit) + -> (Int -> Aff BenchEff Unit) -> Array Int -> Eff BenchEff Unit -bench name buildEff buildEf vals = for_ vals \val -> do +bench3 name buildEff buildEf buildAff vals = for_ vals \val -> do logBench [name <> " build", "Eff", show val] $ benchWith' 1000 \_ -> buildEff val + logBench [name <> " build", "Aff", show val] $ benchWith' 1000 \_ -> buildAff val logBench' [name <> " build", "Ef", show val] $ benchWith' 1000 \_ -> buildEf val let eff = liftEff $ buildEff val logBench [name <> " run", "Eff", show val] $ benchWith' 1000 \_ -> unsafePerformEff eff + let aff = buildAff val + logBench [name <> " run", "Aff", show val] $ benchWith' 1000 \_ -> unsafePerformEff $ launchAff_ aff let ef = liftEf $ buildEf val logBench' [name <> " run", "Ef", show val] $ benchWith' 1000 \_ -> unsafePerformEff ef - -timed :: Array String -> Ef BenchEff Unit -> Eff BenchEff Unit -timed msg eff = - logBench' msg $ benchWith' 5 \_ -> unsafePerformEff $ liftEf eff +bench2 + :: String + -> (Int -> Ef BenchEff Unit) + -> (Int -> Aff BenchEff Unit) + -> Array Int + -> Eff BenchEff Unit +bench2 name buildEf buildAff vals = for_ vals \val -> do + logBench [name <> " build", "Aff", show val] $ benchWith' 4 \_ -> buildAff val + logBench' [name <> " build", "Ef", show val] $ benchWith' 4 \_ -> buildEf val + let aff = buildAff val + logBench [name <> " run", "Aff", show val] $ benchWith' 4 \_ -> unsafePerformEff $ launchAff_ aff + let ef = liftEf $ buildEf val + logBench' [name <> " run", "Ef", show val] $ benchWith' 4 \_ -> unsafePerformEff ef logBench'' :: (String -> String) -> Array String -> Eff BenchEff BenchResult -> Eff BenchEff Unit logBench'' f msg benchEff = do diff --git a/bower.json b/bower.json index f20e7c0..f20d14e 100644 --- a/bower.json +++ b/bower.json @@ -18,8 +18,11 @@ ], "dependencies": { "purescript-prelude": "^3.0.0", - "purescript-eff": "^3.1.0", + "purescript-eff": "^3.1.0" + }, + "devDependencies": { "purescript-foldable-traversable": "^3.6.1", - "purescript-minibench": "safareli/purescript-minibench#un-log" + "purescript-minibench": "safareli/purescript-minibench#un-log", + "purescript-aff": "^4.0.1" } } From ad5675eb9d094fa6cd1b26e5c1a7053e16673187 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Wed, 13 Dec 2017 21:11:02 +0100 Subject: [PATCH 10/12] Make benchmar more fair Mapping was happening on pure node, such operation was optimised explicitly in Aff, so switched to do map on effect so it's more fair --- bench/Bench/Main.purs | 19 +++++++++++++------ package.json | 1 + 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/bench/Bench/Main.purs b/bench/Bench/Main.purs index 2d601fd..1e27977 100644 --- a/bench/Bench/Main.purs +++ b/bench/Bench/Main.purs @@ -53,7 +53,8 @@ testBindLeft n' = do testMap :: forall m. MonadEff BenchEff m => Int -> m Unit testMap n = do - res <- mapLoop n (pure 0) + arr <- liftEff mkArr + res <- mapLoop n (liftEff $ pushToArr arr 0) pure unit where mapLoop :: Monad m => Int -> m Int -> m Int @@ -76,9 +77,15 @@ extended = do log header bench2 ">>=R" testBindRight testBindRight [20000, 50000, 100000, 1000000] bench2 ">>=L" testBindLeft testBindLeft [20000, 50000, 100000, 1000000] - bench2 "map" testMap testMap [10000, 20000, 50000, 100000, 1000000, 10000000] + bench2 "map" testMap testMap [10000, 20000, 50000, 100000, 1000000] + timed ["map", "Ef", "10000000"] $ testMap 10000000 -- Aff can't handle this number, I got `JavaScript heap out of memory` bench2 "apply" testApply testApply [10000, 20000, 50000, 100000, 1000000] +timed :: Array String -> Ef BenchEff Unit -> Eff BenchEff Unit +timed msg ef = do + let eff = liftEf ef + logBench' msg $ benchWith' 5 \_ -> unsafePerformEff eff + header :: String header = "| bench | type | n | mean | stddev | min | max |\n" <> @@ -97,8 +104,8 @@ bench3 name buildEff buildEf buildAff vals = for_ vals \val -> do logBench' [name <> " build", "Ef", show val] $ benchWith' 1000 \_ -> buildEf val let eff = liftEff $ buildEff val logBench [name <> " run", "Eff", show val] $ benchWith' 1000 \_ -> unsafePerformEff eff - let aff = buildAff val - logBench [name <> " run", "Aff", show val] $ benchWith' 1000 \_ -> unsafePerformEff $ launchAff_ aff + let aff = launchAff_ $ buildAff val + logBench [name <> " run", "Aff", show val] $ benchWith' 1000 \_ -> unsafePerformEff aff let ef = liftEf $ buildEf val logBench' [name <> " run", "Ef", show val] $ benchWith' 1000 \_ -> unsafePerformEff ef @@ -111,8 +118,8 @@ bench2 bench2 name buildEf buildAff vals = for_ vals \val -> do logBench [name <> " build", "Aff", show val] $ benchWith' 4 \_ -> buildAff val logBench' [name <> " build", "Ef", show val] $ benchWith' 4 \_ -> buildEf val - let aff = buildAff val - logBench [name <> " run", "Aff", show val] $ benchWith' 4 \_ -> unsafePerformEff $ launchAff_ aff + let aff = launchAff_ $ buildAff val + logBench [name <> " run", "Aff", show val] $ benchWith' 4 \_ -> unsafePerformEff aff let ef = liftEf $ buildEf val logBench' [name <> " run", "Ef", show val] $ benchWith' 4 \_ -> unsafePerformEff ef diff --git a/package.json b/package.json index fe412d6..0df4bb7 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "bench:build": "purs compile 'bench/**/*.purs' 'src/**/*.purs' 'bower_components/*/src/**/*.purs'", "bench:run": "node --expose-gc -e 'require(\"./output/Bench.Main/index.js\").main()'", "bench:run:extended": "node --expose-gc -e 'require(\"./output/Bench.Main/index.js\").extended()'", + "bench:all": "npm run bench:build && npm run bench:run && npm run bench:run:extended", "bench": "npm run bench:build && npm run bench:run" }, "devDependencies": { From c5a83e39621817b2423e593f94c3bea62f1bce37 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Thu, 14 Dec 2017 00:41:47 +0100 Subject: [PATCH 11/12] Use same representation as Eff --- bench/Bench/Main.purs | 8 +-- bower.json | 3 +- src/Control/Monad/Ff.js | 108 +++++++++++++++----------------- src/Control/Monad/Ff.purs | 11 +--- src/Control/Monad/Ff/Class.purs | 10 ++- test/Test/Main.purs | 3 - 6 files changed, 64 insertions(+), 79 deletions(-) diff --git a/bench/Bench/Main.purs b/bench/Bench/Main.purs index 1e27977..7b6b489 100644 --- a/bench/Bench/Main.purs +++ b/bench/Bench/Main.purs @@ -77,15 +77,9 @@ extended = do log header bench2 ">>=R" testBindRight testBindRight [20000, 50000, 100000, 1000000] bench2 ">>=L" testBindLeft testBindLeft [20000, 50000, 100000, 1000000] - bench2 "map" testMap testMap [10000, 20000, 50000, 100000, 1000000] - timed ["map", "Ef", "10000000"] $ testMap 10000000 -- Aff can't handle this number, I got `JavaScript heap out of memory` + bench2 "map" testMap testMap [10000, 20000, 50000, 100000, 1000000, 350000, 700000] bench2 "apply" testApply testApply [10000, 20000, 50000, 100000, 1000000] -timed :: Array String -> Ef BenchEff Unit -> Eff BenchEff Unit -timed msg ef = do - let eff = liftEf ef - logBench' msg $ benchWith' 5 \_ -> unsafePerformEff eff - header :: String header = "| bench | type | n | mean | stddev | min | max |\n" <> diff --git a/bower.json b/bower.json index f20d14e..1dfcde0 100644 --- a/bower.json +++ b/bower.json @@ -18,7 +18,8 @@ ], "dependencies": { "purescript-prelude": "^3.0.0", - "purescript-eff": "^3.1.0" + "purescript-eff": "^3.1.0", + "purescript-unsafe-coerce": "^3.0.0" }, "devDependencies": { "purescript-foldable-traversable": "^3.6.1", diff --git a/src/Control/Monad/Ff.js b/src/Control/Monad/Ff.js index e6b3aee..32a5fe8 100644 --- a/src/Control/Monad/Ff.js +++ b/src/Control/Monad/Ff.js @@ -1,12 +1,12 @@ "use strict"; -// Ef a +// Ef a // = () -> a -// | { tag: "PURE", _0 :: a, _1 :: Void } -// | { tag: "MAP", _0 :: b -> a, _1 :: Ef b } -// | { tag: "APPLY", _0 :: Ef b, _1 :: Ef (b -> a) } -// | { tag: "BIND", _0 :: b -> Ef a, _1 :: Ef b } +// | { Ef a, tag: "PURE", _0 :: a, _1 :: Void } +// | { Ef a, tag: "MAP", _0 :: b -> a, _1 :: Ef b } +// | { Ef a, tag: "APPLY", _0 :: Ef b, _1 :: Ef (b -> a) } +// | { Ef a, tag: "BIND", _0 :: b -> Ef a, _1 :: Ef b } // Operation a b // = { tag: "MAP", _0 :: a -> b } @@ -15,84 +15,78 @@ // | { tag: "BIND", _0 :: a -> Ef b } -function Ef(tag, _0, _1) { - this.tag = tag; - this._0 = _0; - this._1 = _1; -} - var PURE = "PURE"; var MAP = "MAP"; var APPLY = "APPLY"; var BIND = "BIND"; var APPLY_FUNC = "APPLY_FUNC"; -exports.liftEffE = function (eff) { - return eff; -}; - exports.pureE = function (x) { - return new Ef(PURE, x); + return mkEf(PURE, x); }; exports.mapE = function (f) { return function (eff) { - return new Ef(MAP, f, eff); + return mkEf(MAP, f, eff); }; }; exports.applyE = function (effF) { return function (eff) { - return new Ef(APPLY, eff, effF); + return mkEf(APPLY, eff, effF); }; }; exports.bindE = function (eff) { return function (f) { - return new Ef(BIND, f, eff); + return mkEf(BIND, f, eff); }; }; -exports.toEff = function (inputEff) { - if (typeof inputEff === "function") { - return inputEff; - } - return function() { - var operations = []; - var eff = inputEff; - var res; - var op; - var tag; - effLoop: for (;;) { - tag = eff.tag; - if (tag !== undefined) { - if (tag === MAP || tag === BIND || tag === APPLY) { - operations.push(eff); - eff = eff._1; - continue; - } - // here `tag === PURE` - res = eff._0; - } else { - // here `typeof eff == "function"` - res = eff(); + +var mkEf = function (tag, _0, _1) { + var eff = function eff_() { return toEff(eff_) } + eff.tag = tag + eff._0 = _0 + eff._1 = _1 + return eff +} + +var toEff = function (inputEff) { + var operations = []; + var eff = inputEff; + var res; + var op; + var tag; + effLoop: for (;;) { + tag = eff.tag; + if (tag !== undefined) { + if (tag === MAP || tag === BIND || tag === APPLY) { + operations.push(eff); + eff = eff._1; + continue; } + // here `tag === PURE` + res = eff._0; + } else { + // here `typeof eff == "function"` + res = eff(); + } - while ((op = operations.pop())) { - if (op.tag === MAP) { - res = op._0(res); - } else if (op.tag === APPLY_FUNC) { - res = op._0(res); - } else if (op.tag === APPLY) { - eff = op._0; - operations.push(new Ef(APPLY_FUNC, res)); - continue effLoop; - } else { // op.tag === BIND - eff = op._0(res); - continue effLoop; - } + while ((op = operations.pop())) { + if (op.tag === MAP) { + res = op._0(res); + } else if (op.tag === APPLY_FUNC) { + res = op._0(res); + } else if (op.tag === APPLY) { + eff = op._0; + operations.push(new Ef(APPLY_FUNC, res)); + continue effLoop; + } else { // op.tag === BIND + eff = op._0(res); + continue effLoop; } - return res; } - }; + return res; + } }; \ No newline at end of file diff --git a/src/Control/Monad/Ff.purs b/src/Control/Monad/Ff.purs index bf890e2..8c2f216 100644 --- a/src/Control/Monad/Ff.purs +++ b/src/Control/Monad/Ff.purs @@ -1,7 +1,4 @@ -module Control.Monad.Ef - ( Ef - , toEff - ) where +module Control.Monad.Ef (Ef) where import Control.Applicative (class Applicative) -- import Control.Applicative (class Applicative, liftA1) @@ -12,6 +9,7 @@ import Control.Monad (class Monad) import Control.Monad.Eff (Eff, kind Effect) import Data.Functor (class Functor) import Control.Monad.Eff.Class (class MonadEff) +import Unsafe.Coerce (unsafeCoerce) foreign import data Ef :: # Effect -> Type -> Type @@ -33,11 +31,8 @@ instance bindEf :: Bind (Ef e) where instance monadEf :: Monad (Ef e) instance monadEEFff :: MonadEff eff (Ef eff) where - liftEff = liftEffE + liftEff = unsafeCoerce -foreign import toEff :: forall e a. Ef e a -> Eff e a - -foreign import liftEffE :: forall e a. Eff e a -> Ef e a foreign import mapE :: forall e a b. (a -> b) -> Ef e a -> Ef e b foreign import applyE :: forall e a b. Ef e (a -> b) -> Ef e a-> Ef e b foreign import pureE :: forall e a. a -> Ef e a diff --git a/src/Control/Monad/Ff/Class.purs b/src/Control/Monad/Ff/Class.purs index 0e898e2..2a398a1 100644 --- a/src/Control/Monad/Ff/Class.purs +++ b/src/Control/Monad/Ff/Class.purs @@ -1,9 +1,13 @@ -module Control.Monad.Ef.Class where +module Control.Monad.Ef.Class + ( class MonadEf + , liftEf + ) where import Control.Category (id) import Control.Monad (class Monad) -import Control.Monad.Ef (Ef, toEff) +import Control.Monad.Ef (Ef) import Control.Monad.Eff (Eff) +import Unsafe.Coerce (unsafeCoerce) class Monad m <= MonadEf eff m | m -> eff where liftEf :: forall a. Ef eff a -> m a @@ -12,4 +16,4 @@ instance monadEfEf :: MonadEf eff (Ef eff) where liftEf = id instance monadEfEff :: MonadEf eff (Eff eff) where - liftEf = toEff + liftEf = unsafeCoerce \ No newline at end of file diff --git a/test/Test/Main.purs b/test/Test/Main.purs index 71f298e..0f6dd9a 100644 --- a/test/Test/Main.purs +++ b/test/Test/Main.purs @@ -7,9 +7,6 @@ import Control.Apply (lift2) import Control.Monad.Eff.Class (class MonadEff, liftEff) import Control.Monad.Ef (Ef) import Control.Monad.Ef.Class (liftEf) -import Data.Traversable (for_) -import Performance.Minibench (benchWith) -import Control.Monad.Eff.Unsafe (unsafePerformEff) import Control.Monad.Eff.Console (CONSOLE) From d0d3a5f92d30bbd419a61bf5608072013e023042 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Thu, 14 Dec 2017 00:56:43 +0100 Subject: [PATCH 12/12] fix build --- src/Control/Monad/Ff.js | 64 +++++++++++++++++++-------------------- src/Control/Monad/Ff.purs | 2 +- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/Control/Monad/Ff.js b/src/Control/Monad/Ff.js index 32a5fe8..c7bd92d 100644 --- a/src/Control/Monad/Ff.js +++ b/src/Control/Monad/Ff.js @@ -21,36 +21,6 @@ var APPLY = "APPLY"; var BIND = "BIND"; var APPLY_FUNC = "APPLY_FUNC"; -exports.pureE = function (x) { - return mkEf(PURE, x); -}; - -exports.mapE = function (f) { - return function (eff) { - return mkEf(MAP, f, eff); - }; -}; - -exports.applyE = function (effF) { - return function (eff) { - return mkEf(APPLY, eff, effF); - }; -}; - -exports.bindE = function (eff) { - return function (f) { - return mkEf(BIND, f, eff); - }; -}; - - -var mkEf = function (tag, _0, _1) { - var eff = function eff_() { return toEff(eff_) } - eff.tag = tag - eff._0 = _0 - eff._1 = _1 - return eff -} var toEff = function (inputEff) { var operations = []; @@ -80,7 +50,7 @@ var toEff = function (inputEff) { res = op._0(res); } else if (op.tag === APPLY) { eff = op._0; - operations.push(new Ef(APPLY_FUNC, res)); + operations.push({ tag: APPLY_FUNC, _0: res }); continue effLoop; } else { // op.tag === BIND eff = op._0(res); @@ -89,4 +59,34 @@ var toEff = function (inputEff) { } return res; } -}; \ No newline at end of file +}; + +var mkEf = function (tag, _0, _1) { + var eff = function eff_() { return toEff(eff_); }; + eff.tag = tag; + eff._0 = _0; + eff._1 = _1; + return eff; +}; + +exports.pureE = function (x) { + return mkEf(PURE, x); +}; + +exports.mapE = function (f) { + return function (eff) { + return mkEf(MAP, f, eff); + }; +}; + +exports.applyE = function (effF) { + return function (eff) { + return mkEf(APPLY, eff, effF); + }; +}; + +exports.bindE = function (eff) { + return function (f) { + return mkEf(BIND, f, eff); + }; +}; diff --git a/src/Control/Monad/Ff.purs b/src/Control/Monad/Ff.purs index 8c2f216..c35c30e 100644 --- a/src/Control/Monad/Ff.purs +++ b/src/Control/Monad/Ff.purs @@ -6,7 +6,7 @@ import Control.Apply (class Apply) import Control.Bind (class Bind) import Control.Monad (class Monad) -- import Control.Monad (class Monad, ap) -import Control.Monad.Eff (Eff, kind Effect) +import Control.Monad.Eff (kind Effect) import Data.Functor (class Functor) import Control.Monad.Eff.Class (class MonadEff) import Unsafe.Coerce (unsafeCoerce)