├── .gitignore ├── LICENSE ├── README.md ├── bower.json ├── example ├── .gitignore ├── bower.json ├── dist │ └── index.js ├── index.html ├── package.json ├── src │ └── Main.purs └── test │ └── Main.purs ├── package.json ├── src ├── Cycle.js └── Cycle.purs ├── test └── Main.purs └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /bower_components/ 2 | /node_modules/ 3 | /.pulp-cache/ 4 | /output/ 5 | /.psci* 6 | /src/.webpack.js 7 | .psc-ide-port 8 | package-lock.json -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Justin Woo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # purescript-cycle-run 2 | 3 | A purescript interface for [Cycle.js](http://cycle.js.org/) using 4 | [@cycle/run](https://github.com/cyclejs/cyclejs/tree/master/run). 5 | 6 | Requires `purescript-xstream` 7 | 8 | ## If you're looking for a PureScript solution, you're looking for 9 | 10 | ## Usage 11 | 12 | See tests and examples 13 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "purescript-cycle-run", 3 | "license": "MIT", 4 | "repository": { 5 | "type": "git", 6 | "url": "git://github.com/justinwoo/purescript-cycle-run.git" 7 | }, 8 | "ignore": [ 9 | "**/.*", 10 | "node_modules", 11 | "bower_components", 12 | "output" 13 | ], 14 | "dependencies": { 15 | "purescript-console": "^3.0.0", 16 | "purescript-xstream": "^1.0.0", 17 | "purescript-typelevel-prelude": "^2.5.0", 18 | "purescript-record": "^0.2.4" 19 | }, 20 | "devDependencies": { 21 | "purescript-test-unit": "v13.0.0" 22 | }, 23 | "version": "0.1.0" 24 | } 25 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | /bower_components/ 2 | /node_modules/ 3 | /.pulp-cache/ 4 | /output/ 5 | /.psci* 6 | /src/.webpack.js 7 | package.json -------------------------------------------------------------------------------- /example/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "ignore": [ 4 | "**/.*", 5 | "node_modules", 6 | "bower_components", 7 | "output" 8 | ], 9 | "dependencies": { 10 | "purescript-console": "^3.0.0", 11 | "purescript-xstream": "^1.0.0", 12 | "purescript-cycle-run": "../", 13 | "purescript-jquery": "^4.3.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Example 4 | 5 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1", 11 | "build": "pulp browserify --to dist/index.js", 12 | "start": "pulp -w browserify --to dist/index.js" 13 | }, 14 | "author": "", 15 | "license": "MIT", 16 | "dependencies": { 17 | "@cycle/run": "^4.0.0", 18 | "xstream": "^11.0.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /example/src/Main.purs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import Prelude 4 | 5 | import Control.Cycle (runRecord) 6 | import Control.Monad.Eff (Eff) 7 | import Control.Monad.Eff.Console (CONSOLE) 8 | import Control.Monad.Eff.JQuery as JQ 9 | import Control.Monad.Eff.Timer (TIMER) 10 | import Control.XStream (STREAM, Stream, addListener, defaultListener, periodic, startWith) 11 | import DOM (DOM) 12 | import Data.Monoid (mempty) 13 | 14 | type AppEff e = 15 | Eff 16 | ( dom :: DOM 17 | , console :: CONSOLE 18 | , stream :: STREAM 19 | , timer :: TIMER 20 | | e 21 | ) 22 | 23 | main_ :: { display :: Stream Unit, timer :: Stream Int } -> { display :: Stream String, timer :: Stream Unit } 24 | main_ {timer} = 25 | { display: show <$> timer 26 | , timer: mempty 27 | } 28 | 29 | driver :: forall e. 30 | { display :: Stream String -> AppEff e (Stream Unit) 31 | , timer :: Stream Unit -> AppEff e (Stream Int) 32 | } 33 | driver = 34 | { display 35 | , timer 36 | } 37 | where 38 | timer _ = do 39 | startWith 0 <$> periodic 1000 40 | display strings = do 41 | JQ.ready $ do 42 | body <- JQ.body 43 | div <- JQ.create "
" 44 | h1 <- JQ.create "

Periodic:

" 45 | h2 <- JQ.create "

" 46 | JQ.append h1 div 47 | JQ.append h2 div 48 | JQ.append div body 49 | addListener 50 | defaultListener 51 | { next = \x -> JQ.setText x h2 52 | } 53 | strings 54 | pure mempty 55 | 56 | main :: forall e. AppEff e Unit 57 | main = 58 | void $ runRecord main_ driver 59 | -------------------------------------------------------------------------------- /example/test/Main.purs: -------------------------------------------------------------------------------- 1 | module Test.Main where 2 | 3 | import Prelude 4 | import Control.Monad.Eff (Eff) 5 | import Control.Monad.Eff.Console (CONSOLE, log) 6 | 7 | main :: forall e. Eff (console :: CONSOLE | e) Unit 8 | main = do 9 | log "You should add some tests." 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "purescript-cycle-run", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "pulp test" 11 | }, 12 | "author": "", 13 | "license": "MIT", 14 | "dependencies": { 15 | "@cycle/run": "^4.0.0", 16 | "xstream": "^11.0.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Cycle.js: -------------------------------------------------------------------------------- 1 | var Cycle = require('@cycle/run'); 2 | 3 | exports._run = function (_main, _driver) { 4 | return function () { 5 | function main (sources) { 6 | return { 7 | main: _main(sources.main) 8 | }; 9 | } 10 | function driver (sink) { 11 | return _driver(sink)(); 12 | } 13 | var dispose = Cycle.run(main, { 14 | main: driver 15 | }); 16 | return function (unit) { 17 | return function () { 18 | dispose(); 19 | }; 20 | }; 21 | }; 22 | }; 23 | 24 | function wrapDriver(eff) { 25 | return function (sink) { 26 | return eff(sink)(); 27 | } 28 | } 29 | 30 | exports._runRecord = function (main, _driver) { 31 | return function () { 32 | var driver = {}; 33 | var keys = Object.keys(_driver); 34 | for (var i = 0; i < keys.length; i++) { 35 | var key = keys[i]; 36 | var eff = _driver[key]; 37 | driver[key] = wrapDriver(eff); 38 | } 39 | 40 | var dispose = Cycle.run(main, driver); 41 | 42 | return function (unit) { 43 | return function () { 44 | dispose(); 45 | }; 46 | }; 47 | }; 48 | }; 49 | -------------------------------------------------------------------------------- /src/Cycle.purs: -------------------------------------------------------------------------------- 1 | module Control.Cycle 2 | ( run 3 | , runRecord 4 | , Dispose 5 | , class CycleRunRecord 6 | , class CycleRunRowList 7 | ) where 8 | 9 | import Prelude 10 | 11 | import Control.Monad.Eff (Eff) 12 | import Control.XStream (Stream) 13 | import Data.Function.Uncurried (Fn2, runFn2) 14 | import Type.Row (class ListToRow, class RowToList, Cons, Nil, kind RowList) 15 | 16 | type Dispose e = Unit -> Eff e Unit 17 | 18 | -- | Cycle run for one source input and one sink output. 19 | run :: forall e a b. 20 | (a -> Stream b) -> 21 | (Stream b -> Eff e a) -> 22 | Eff e (Dispose e) 23 | run = runFn2 _run 24 | 25 | -- | Cycle run for a record of source inputs to sink outputs. 26 | runRecord :: forall sources sinks drivers e 27 | . CycleRunRecord sources sinks drivers 28 | => (Record sources -> Record sinks) 29 | -> Record drivers 30 | -> Eff e (Dispose e) 31 | runRecord = runFn2 _runRecord 32 | 33 | foreign import _runRecord :: forall main drivers e. Fn2 34 | main 35 | drivers 36 | (Eff e (Dispose e)) 37 | 38 | class CycleRunRecord (sourceRow :: # Type) (sinkRow :: # Type) (driverRow :: # Type) 39 | | sourceRow -> sinkRow driverRow 40 | , sinkRow -> sourceRow driverRow 41 | , driverRow -> sourceRow sinkRow 42 | 43 | instance cycleRunRecord :: 44 | ( RowToList sourceRow sourceList 45 | , RowToList sinkRow sinkList 46 | , RowToList driverRow driverList 47 | , CycleRunRowList sourceList sinkList driverList 48 | , ListToRow sourceList sourceRow 49 | , ListToRow sinkList sinkRow 50 | , ListToRow driverList driverRow 51 | ) => CycleRunRecord sourceRow sinkRow driverRow 52 | 53 | class CycleRunRowList (sourceList :: RowList) (sinkList :: RowList) (driverList :: RowList) 54 | | sourceList -> sinkList driverList 55 | , sinkList -> sourceList driverList 56 | , driverList -> sourceList sinkList 57 | 58 | instance cycleRunRowListCons :: 59 | ( CycleRunRowList sourceTail sinkTail driverTail 60 | ) => CycleRunRowList 61 | (Cons k b sourceTail) 62 | (Cons k (Stream a) sinkTail) 63 | (Cons k ((Stream a) -> Eff e b) driverTail) 64 | 65 | instance cycleRunRowListNil :: CycleRunRowList Nil Nil Nil 66 | 67 | foreign import _run :: forall e a b. Fn2 68 | (a -> Stream b) 69 | (Stream b -> Eff e a) 70 | (Eff e (Dispose e)) 71 | -------------------------------------------------------------------------------- /test/Main.purs: -------------------------------------------------------------------------------- 1 | module Test.Main where 2 | 3 | import Prelude 4 | 5 | import Control.Alt ((<|>)) 6 | import Control.Alternative (empty) 7 | import Control.Cycle (run, runRecord) 8 | import Control.Monad.Aff (Aff, Canceler(Canceler), makeAff, runAff) 9 | import Control.Monad.Eff (Eff) 10 | import Control.Monad.Eff.Class (liftEff) 11 | import Control.Monad.Eff.Console (log) 12 | import Control.Monad.Eff.Ref (modifyRef, newRef, readRef) 13 | import Control.XStream (Stream, addListener, fromArray) 14 | import Data.Array (snoc) 15 | import Data.Either (Either(Right, Left)) 16 | import Data.Monoid (mempty) 17 | import Test.Unit (success, suite, test) 18 | import Test.Unit.Assert (equal) 19 | import Test.Unit.Main (runTest) 20 | 21 | arrayFromStream :: forall a. Stream a -> Aff _ (Array a) 22 | arrayFromStream s = makeAff \cb -> do 23 | ref <- newRef empty 24 | cancel <- addListener 25 | { next: \a -> modifyRef ref $ flip snoc a 26 | , error: cb <<< Left 27 | , complete: pure $ cb <<< Right =<< readRef ref 28 | } 29 | s 30 | pure $ Canceler (const $ pure cancel) 31 | 32 | expectStream :: forall a. Eq a => Show a => Array a -> Stream a -> Aff _ Unit 33 | expectStream xs a = 34 | equal xs =<< arrayFromStream a 35 | 36 | main :: Eff _ Unit 37 | main = runTest do 38 | suite "Cycle" do 39 | test "test" do 40 | expectStream [1] (fromArray [1]) 41 | test "run" do 42 | makeAff \cb -> do 43 | dispose <- run 44 | (\sink -> (_ * 2) <$> sink <|> fromArray [1,2,3]) 45 | (\sink -> do 46 | _ <- runAff cb success 47 | pure mempty 48 | ) 49 | pure $ Canceler (const $ liftEff $ dispose unit) 50 | test "runRecord" do 51 | makeAff \cb -> do 52 | let drivers = 53 | { a: \sink -> do 54 | _ <- runAff cb success 55 | pure $ fromArray [4,5,6] 56 | , b: const $ pure unit 57 | } :: { a :: Stream Int -> Eff _ (Stream Int), b :: Stream Unit -> Eff _ Unit } 58 | let main' sources = 59 | { a: fromArray [1,2,3] <|> sources.a 60 | , b: fromArray [unit] 61 | } :: { a :: Stream Int, b :: Stream Unit } 62 | dispose <- runRecord 63 | main' 64 | drivers 65 | pure $ Canceler (const $ liftEff $ dispose unit) 66 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@cycle/base@4.2": 6 | version "4.2.0" 7 | resolved "https://registry.yarnpkg.com/@cycle/base/-/base-4.2.0.tgz#ed6043c3c92ed2442d0e4b9ae603aec05e960f0b" 8 | 9 | "@cycle/xstream-adapter@3.x.x": 10 | version "3.1.0" 11 | resolved "https://registry.yarnpkg.com/@cycle/xstream-adapter/-/xstream-adapter-3.1.0.tgz#97b64f3161d91913666be81ee11a13f3d1e754b4" 12 | 13 | "@cycle/xstream-run@^4.2.0": 14 | version "4.2.0" 15 | resolved "https://registry.yarnpkg.com/@cycle/xstream-run/-/xstream-run-4.2.0.tgz#156b1ed0a9561169d13617aefa9548470e7c1c00" 16 | dependencies: 17 | "@cycle/base" "4.2" 18 | "@cycle/xstream-adapter" "3.x.x" 19 | 20 | symbol-observable@^1.0.2: 21 | version "1.0.4" 22 | resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.4.tgz#29bf615d4aa7121bdd898b22d4b3f9bc4e2aa03d" 23 | 24 | xstream@^9.3.0: 25 | version "9.3.0" 26 | resolved "https://registry.yarnpkg.com/xstream/-/xstream-9.3.0.tgz#7cc9c91970c84644fc00d59621027259cc2dad0e" 27 | dependencies: 28 | symbol-observable "^1.0.2" 29 | --------------------------------------------------------------------------------