├── demos ├── extras │ ├── daggy.js │ ├── data.maybe.umd.js │ ├── reader.js │ ├── todo.html │ └── todo.js ├── step1 │ ├── flickr-base.js │ ├── flickr.html │ ├── flickr.js │ ├── youtube-base.js │ ├── youtube.html │ └── youtube.js ├── step2 │ ├── flickr.html │ └── flickr.js ├── step3 │ ├── flickr.html │ └── flickr.js └── step4 │ ├── flickr.html │ └── flickr.js └── jsbins ├── _loader.html ├── applicatives-problem.js ├── applicatives-solution.js ├── compose-problem.js ├── compose-solution.js ├── curry-problem.js ├── curry-solution.js ├── functor-problem.js ├── functor-solution.js ├── monads-problem.js ├── monads-solution.js ├── monoids-problem.js └── monoids-solution.js /demos/extras/daggy.js: -------------------------------------------------------------------------------- 1 | (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 5 | * 6 | * Permission is hereby granted, free of charge, to any person 7 | * obtaining a copy of this software and associated documentation files 8 | * (the "Software"), to deal in the Software without restriction, 9 | * including without limitation the rights to use, copy, modify, merge, 10 | * publish, distribute, sublicense, and/or sell copies of the Software, 11 | * and to permit persons to whom the Software is furnished to do so, 12 | * subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be 15 | * included in all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | (function(){ 26 | var Maybe, Just, Nothing; 27 | Maybe = (function(){ 28 | Maybe.displayName = 'Maybe'; 29 | var prototype = Maybe.prototype, constructor = Maybe; 30 | function Maybe(){} 31 | prototype.Nothing = function(){ 32 | return Nothing; 33 | }; 34 | prototype.Just = function(a){ 35 | return new Just(a); 36 | }; 37 | prototype.fromNullable = function(a){ 38 | switch (false) { 39 | case a == null: 40 | return new Just(a); 41 | default: 42 | return Nothing; 43 | } 44 | }; 45 | prototype.isNothing = false; 46 | prototype.isJust = false; 47 | prototype.of = function(a){ 48 | return new Just(a); 49 | }; 50 | prototype.ap = function(_){ 51 | throw Error('unimplemented'); 52 | }; 53 | prototype.map = function(_){ 54 | throw Error('unimplemented'); 55 | }; 56 | prototype.chain = function(_){ 57 | throw Error('unimplemented'); 58 | }; 59 | prototype.toString = function(){ 60 | throw Error('unimplemented'); 61 | }; 62 | prototype.isEqual = function(_){ 63 | throw Error('unimplemented'); 64 | }; 65 | prototype.get = function(){ 66 | throw Error('unimplemented'); 67 | }; 68 | prototype.getOrElse = function(_){ 69 | throw Error('unimplemented'); 70 | }; 71 | prototype.orElse = function(_){ 72 | throw Error('unimplemented'); 73 | }; 74 | return Maybe; 75 | }()); 76 | Just = (function(superclass){ 77 | var prototype = extend$((import$(Just, superclass).displayName = 'Just', Just), superclass).prototype, constructor = Just; 78 | function Just(value){ 79 | this.value = value; 80 | } 81 | prototype.isJust = true; 82 | prototype.ap = function(b){ 83 | return b.map(this.value); 84 | }; 85 | prototype.map = function(f){ 86 | return this.of(f(this.value)); 87 | }; 88 | prototype.chain = function(f){ 89 | return f(this.value); 90 | }; 91 | prototype.toString = function(){ 92 | return "Maybe.Just(" + this.value + ")"; 93 | }; 94 | prototype.isEqual = function(a){ 95 | return a.isJust && a.value === this.value; 96 | }; 97 | prototype.get = function(){ 98 | return this.value; 99 | }; 100 | prototype.getOrElse = function(a){ 101 | return this.value; 102 | }; 103 | prototype.orElse = function(){ 104 | return this; 105 | }; 106 | return Just; 107 | }(Maybe)); 108 | Nothing = new (function(superclass){ 109 | var prototype = extend$(import$(constructor, superclass), superclass).prototype; 110 | function constructor(){} 111 | prototype.isNothing = true; 112 | prototype.ap = function(b){ 113 | return b; 114 | }; 115 | prototype.map = function(f){ 116 | return this; 117 | }; 118 | prototype.chain = function(f){ 119 | return this; 120 | }; 121 | prototype.toString = function(){ 122 | return "Maybe.Nothing"; 123 | }; 124 | prototype.isEqual = function(a){ 125 | return a.isNothing; 126 | }; 127 | prototype.get = function(){ 128 | throw new TypeError("Can't extract the value of a Nothing"); 129 | }; 130 | prototype.getOrElse = function(a){ 131 | return a; 132 | }; 133 | prototype.orElse = function(f){ 134 | return f(); 135 | }; 136 | return constructor; 137 | }(Maybe)); 138 | module.exports = new Maybe; 139 | function extend$(sub, sup){ 140 | function fun(){} fun.prototype = (sub.superclass = sup).prototype; 141 | (sub.prototype = new fun).constructor = sub; 142 | if (typeof sup.extended == 'function') sup.extended(sub); 143 | return sub; 144 | } 145 | function import$(obj, src){ 146 | var own = {}.hasOwnProperty; 147 | for (var key in src) if (own.call(src, key)) obj[key] = src[key]; 148 | return obj; 149 | } 150 | }).call(this); 151 | 152 | },{}]},{},[1]) 153 | (1) 154 | }); 155 | ; -------------------------------------------------------------------------------- /demos/extras/reader.js: -------------------------------------------------------------------------------- 1 | define(['daggy'], function(daggy) { 2 | 3 | var Reader = daggy.tagged('run'); 4 | 5 | // Methods 6 | Reader.of = function(a) { 7 | return Reader(function(e) { 8 | return a; 9 | }); 10 | }; 11 | Reader.prototype.chain = function(f) { 12 | var reader = this; 13 | return Reader(function(e) { 14 | return f(reader.run(e)).run(e); 15 | }); 16 | }; 17 | Reader.ask = Reader(function(e) { 18 | return e; 19 | }); 20 | 21 | // Derived 22 | Reader.prototype.map = function(f) { 23 | return this.chain(function(a) { 24 | return Reader.of(f(a)); 25 | }); 26 | }; 27 | Reader.prototype.ap = function(a) { 28 | return this.chain(function(f) { 29 | return a.map(f); 30 | }); 31 | }; 32 | 33 | // Transformer 34 | Reader.ReaderT = function(M) { 35 | var ReaderT = daggy.tagged('run'); 36 | ReaderT.lift = function(m) { 37 | return ReaderT(function(b) { 38 | return m; 39 | }); 40 | }; 41 | ReaderT.of = function(a) { 42 | return ReaderT(function(e) { 43 | return M.of(a); 44 | }); 45 | }; 46 | ReaderT.prototype.chain = function(f) { 47 | var reader = this; 48 | return ReaderT(function(e) { 49 | return reader.run(e).chain(function(a) { 50 | return f(a).run(e); 51 | }); 52 | }); 53 | }; 54 | ReaderT.ask = ReaderT(function(e) { 55 | return M.of(e); 56 | }); 57 | return ReaderT; 58 | }; 59 | 60 | return Reader; 61 | }); 62 | -------------------------------------------------------------------------------- /demos/extras/todo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 13 | 14 | Todo example 15 | 16 | 17 |

Todos (type and hit enter)

18 | 19 |
20 | 21 | 22 | -------------------------------------------------------------------------------- /demos/extras/todo.js: -------------------------------------------------------------------------------- 1 | /*jslint nomen: true */ 2 | requirejs.config({ 3 | shim: {}, 4 | paths: { 5 | domReady: 'https://cdnjs.cloudflare.com/ajax/libs/require-domReady/2.0.1/domReady.min', 6 | handlebars: 'https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/2.0.0/handlebars.amd.min', 7 | ramda: 'https://cdnjs.cloudflare.com/ajax/libs/ramda/0.2.3/ramda.min', 8 | bacon: 'https://cdnjs.cloudflare.com/ajax/libs/bacon.js/0.7.14/Bacon', 9 | hcjs: 'http://looprecur.com/hostedjs/v2/hcjs', 10 | daggy: 'daggy', 11 | reader: 'reader', 12 | maybe: 'data.maybe.umd' 13 | } 14 | }); 15 | 16 | require([ 17 | 'ramda', 18 | 'handlebars', 19 | 'bacon', 20 | 'hcjs', 21 | 'reader', 22 | 'maybe', 23 | 'domReady!' 24 | ], 25 | function (_, H, b, hcjs, Reader, Maybe) { 26 | 27 | // generic helpers 28 | ////////////////////////////////////////////////////////////////////////////// 29 | var Handlebars = H.default, 30 | 31 | targetValue = compose(_.get('value'), _.get('target')), 32 | 33 | getOrElse = _.curry(function(x, m){ return m.getOrElse(x); }), 34 | 35 | $ = function (sel) { return document.querySelector(sel); }.toIO(), 36 | 37 | setHtml = _.curry(function (sel, h) { 38 | return $(sel).map(function(s) { s.innerHTML = h; }); 39 | }), 40 | 41 | getFromStorage = function (name) { 42 | return Maybe.fromNullable(localStorage[name]) 43 | }.toIO(), 44 | 45 | saveToStorage = function (name) { 46 | return function(val) { 47 | localStorage[name] = JSON.stringify(val); 48 | return val; 49 | }.toIO() 50 | }; 51 | 52 | 53 | // pure app 54 | ////////////////////////////////////////////////////////////////////////////// 55 | 56 | var isEnterKey = compose(eq(13), _.get('keyCode')), 57 | 58 | updatePage = setHtml('#main'), 59 | 60 | getTodos = getFromStorage("todos").map(getOrElse("[]")).map(JSON.parse), 61 | 62 | appendTodo = compose(getTodos.map.bind(getTodos), unshift), 63 | 64 | persistTodo = compose(chain(saveToStorage('todos')), appendTodo), 65 | 66 | saveTodo = Reader.ask.map(function (render) { 67 | return compose(chain(updatePage), map(render), persistTodo); 68 | }); 69 | 70 | 71 | // impure calling code 72 | ////////////////////////////////////////////////////////////////////////////// 73 | 74 | var render = Handlebars.compile($('#todo-tpl').runIO().innerHTML); 75 | var input = _.head(document.getElementsByTagName('input')); 76 | 77 | b.fromEventTarget(input, 'keyup').filter(isEnterKey).map(targetValue).onValue(function(t){ 78 | saveTodo.run(render)(t).runIO(); 79 | }); 80 | 81 | getTodos.map(render).chain(updatePage).runIO(); 82 | } 83 | ); 84 | 85 | -------------------------------------------------------------------------------- /demos/step1/flickr-base.js: -------------------------------------------------------------------------------- 1 | /*jslint nomen: true */ 2 | requirejs.config({ 3 | shim: {}, 4 | paths: { 5 | domReady: 'https://cdnjs.cloudflare.com/ajax/libs/require-domReady/2.0.1/domReady.min', 6 | jquery: 'https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min', 7 | ramda: 'https://cdnjs.cloudflare.com/ajax/libs/ramda/0.2.3/ramda.min', 8 | future: 'http://looprecur.com/hostedjs/v2/data.future.umd', 9 | hcjs: 'http://looprecur.com/hostedjs/v2/hcjs' 10 | } 11 | }); 12 | 13 | require([ 14 | 'ramda', 15 | 'jquery', 16 | 'future', 17 | 'hcjs', 18 | 'domReady!' 19 | ], 20 | function (_, $, Future, hcjs) { 21 | 22 | //////////////////////////////////////////// 23 | // Flickr api 24 | 25 | // url :: String -> URL 26 | var url = function (t) { 27 | return 'http://api.flickr.com/services/feeds/photos_public.gne?tags=' + t + '&format=json&jsoncallback=?'; 28 | }; 29 | 30 | }); 31 | -------------------------------------------------------------------------------- /demos/step1/flickr.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 18 | 19 | JS Bin 20 | 21 | 22 | 23 |
24 |

Photo Stream

25 |
26 |
27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /demos/step1/flickr.js: -------------------------------------------------------------------------------- 1 | /*jslint nomen: true */ 2 | requirejs.config({ 3 | shim: {}, 4 | paths: { 5 | domReady: 'https://cdnjs.cloudflare.com/ajax/libs/require-domReady/2.0.1/domReady.min', 6 | jquery: 'https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min', 7 | ramda: 'https://cdnjs.cloudflare.com/ajax/libs/ramda/0.2.3/ramda.min', 8 | future: 'http://looprecur.com/hostedjs/v2/data.future.umd', 9 | hcjs: 'http://looprecur.com/hostedjs/v2/hcjs' 10 | } 11 | }); 12 | 13 | require([ 14 | 'ramda', 15 | 'jquery', 16 | 'future', 17 | 'hcjs', 18 | 'domReady!' 19 | ], 20 | function (_, $, Future, hcjs) { 21 | 22 | // imageTag :: URL -> DOM 23 | var imageTag = function (url) { 24 | return $('', { 25 | src: url 26 | }); 27 | }; 28 | 29 | //////////////////////////////////////////// 30 | // Flickr api 31 | 32 | // url :: String -> URL 33 | var url = function (t) { 34 | return 'http://api.flickr.com/services/feeds/photos_public.gne?tags=' + t + '&format=json&jsoncallback=?'; 35 | }; 36 | 37 | // src :: FlickrItem -> URL 38 | var src = compose(_.get('m'), _.get('media')); 39 | 40 | // srcs :: FlickrSearch -> [URL] 41 | var srcs = compose(map(src), _.get('items')); 42 | 43 | // images :: FlickrSearch -> [DOM] 44 | var images = compose(map(imageTag), srcs); 45 | 46 | // widget :: String -> Future [DOM] 47 | var widget = compose(map(images), getJSON, url); 48 | 49 | 50 | ///////////////////////////////////////////////////////////////////////////////////// 51 | // Test code 52 | 53 | widget('cat').fork(log, setHtml($('#flickr'))); 54 | }); -------------------------------------------------------------------------------- /demos/step1/youtube-base.js: -------------------------------------------------------------------------------- 1 | /*jslint nomen: true */ 2 | requirejs.config({ 3 | shim: {}, 4 | paths: { 5 | domReady: 'https://cdnjs.cloudflare.com/ajax/libs/require-domReady/2.0.1/domReady.min', 6 | jquery: 'https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min', 7 | ramda: 'https://cdnjs.cloudflare.com/ajax/libs/ramda/0.2.3/ramda.min', 8 | future: 'http://looprecur.com/hostedjs/v2/data.future.umd', 9 | hcjs: 'http://looprecur.com/hostedjs/v2/hcjs' 10 | } 11 | }); 12 | 13 | require([ 14 | 'ramda', 15 | 'jquery', 16 | 'future', 17 | 'hcjs', 18 | 'domReady!' 19 | ], 20 | function (_, $, Future, hcjs) { 21 | 22 | ///////////////////////////////////////////////////////////////////////////////////// 23 | // Youtube api 24 | 25 | // url :: String -> URL 26 | var url = function (t) { 27 | return 'http://gdata.youtube.com/feeds/api/videos?q=' + t + '&alt=json'; 28 | }; 29 | 30 | }); 31 | -------------------------------------------------------------------------------- /demos/step1/youtube.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 18 | 19 | JS Bin 20 | 21 | 22 | 23 |
24 |

Photo Stream 1

25 |
26 |
27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /demos/step1/youtube.js: -------------------------------------------------------------------------------- 1 | /*jslint nomen: true */ 2 | requirejs.config({ 3 | shim: {}, 4 | paths: { 5 | domReady: 'https://cdnjs.cloudflare.com/ajax/libs/require-domReady/2.0.1/domReady.min', 6 | jquery: 'https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min', 7 | ramda: 'https://cdnjs.cloudflare.com/ajax/libs/ramda/0.2.3/ramda.min', 8 | future: 'http://looprecur.com/hostedjs/v2/data.future.umd', 9 | hcjs: 'http://looprecur.com/hostedjs/v2/hcjs' 10 | } 11 | }); 12 | 13 | require([ 14 | 'ramda', 15 | 'jquery', 16 | 'future', 17 | 'hcjs', 18 | 'domReady!' 19 | ], 20 | function (_, $, Future, hcjs) { 21 | 22 | // imageTag :: URL -> DOM 23 | var imageTag = function (url) { 24 | return $('', { 25 | src: url 26 | }); 27 | }; 28 | 29 | ///////////////////////////////////////////////////////////////////////////////////// 30 | // Youtube api 31 | 32 | // url :: String -> URL 33 | var url = function (t) { 34 | return 'http://gdata.youtube.com/feeds/api/videos?q=' + t + '&alt=json'; 35 | }; 36 | 37 | // src :: YoutubeEntry -> URL 38 | var src = compose(_.get('url'), _.first, _.get('media$thumbnail'), _.get('media$group')); 39 | 40 | // srcs :: YoutubeSearch -> [URL] 41 | var srcs = compose(map(src), _.get('entry'), _.get('feed')); 42 | 43 | // images :: YoutubeSearch -> [DOM] 44 | var images = compose(map(imageTag), srcs); 45 | 46 | // widget :: Future DOM 47 | var widget = compose(map(images), getJSON, url); 48 | 49 | 50 | ///////////////////////////////////////////////////////////////////////////////////// 51 | // Test code 52 | 53 | widget('cats').fork(log, setHtml($('#youtube'))); 54 | }); 55 | -------------------------------------------------------------------------------- /demos/step2/flickr.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 18 | 19 | JS Bin 20 | 21 | 22 | 23 |
24 |

Photo Stream

25 |
26 |
27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /demos/step2/flickr.js: -------------------------------------------------------------------------------- 1 | /*jslint nomen: true */ 2 | requirejs.config({ 3 | shim: {}, 4 | paths: { 5 | domReady: 'https://cdnjs.cloudflare.com/ajax/libs/require-domReady/2.0.1/domReady.min', 6 | jquery: 'https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min', 7 | ramda: 'https://cdnjs.cloudflare.com/ajax/libs/ramda/0.2.3/ramda.min', 8 | future: 'http://looprecur.com/hostedjs/v2/data.future.umd', 9 | hcjs: 'http://looprecur.com/hostedjs/v2/hcjs' 10 | } 11 | }); 12 | 13 | require([ 14 | 'ramda', 15 | 'jquery', 16 | 'future', 17 | 'hcjs', 18 | 'domReady!' 19 | ], 20 | function (_, $, Future, hcjs) { 21 | 22 | // imageTag :: URL -> DOM 23 | var imageTag = function (url) { 24 | return $('', { 25 | src: url 26 | }); 27 | }; 28 | 29 | ///////////////////////////////////////////////////////////////////////////////////// 30 | // PictureBox 31 | 32 | // PictureBox = data 33 | // val :: Future(a) 34 | var _PictureBox = function (val) { 35 | this.val = val; 36 | this.fork = this.val.fork; 37 | }; 38 | 39 | var PictureBox = function (x) { 40 | return new _PictureBox(x); 41 | }; 42 | 43 | // instance Monoid PictureBox where 44 | _PictureBox.prototype.empty = function () { 45 | return PictureBox(Future.of([])); 46 | }; 47 | _PictureBox.prototype.concat = function (y) { 48 | return PictureBox(this.val.concat(y.val)); 49 | }; 50 | 51 | ///////////////////////////////////////////////////////////////////////////////////// 52 | // Flickr api 53 | 54 | // url :: String -> URL 55 | var url = function (t) { 56 | return 'http://api.flickr.com/services/feeds/photos_public.gne?tags=' + t + '&format=json&jsoncallback=?'; 57 | }; 58 | 59 | // src :: FlickrItem -> URL 60 | var src = compose(_.get('m'), _.get('media')); 61 | 62 | // srcs :: FlickrSearch -> [URL] 63 | var srcs = compose(map(src), _.get('items')); 64 | 65 | // images :: FlickrSearch -> [DOM] 66 | var images = compose(map(imageTag), srcs); 67 | 68 | // widget :: String -> PictureBox 69 | var widget = compose(PictureBox, map(images), getJSON, url); 70 | 71 | 72 | ///////////////////////////////////////////////////////////////////////////////////// 73 | // Test code 74 | 75 | mconcat([widget('cat'), widget('dog')]).fork(log, setHtml($('#flickr'))); 76 | }); 77 | -------------------------------------------------------------------------------- /demos/step3/flickr.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 18 | 19 | JS Bin 20 | 21 | 22 | 23 |
24 |

Photo Stream

25 |
26 |
27 |
28 | 29 | 30 | -------------------------------------------------------------------------------- /demos/step3/flickr.js: -------------------------------------------------------------------------------- 1 | /*jslint nomen: true */ 2 | requirejs.config({ 3 | shim: {}, 4 | paths: { 5 | domReady: 'https://cdnjs.cloudflare.com/ajax/libs/require-domReady/2.0.1/domReady.min', 6 | jquery: 'https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min', 7 | ramda: 'https://cdnjs.cloudflare.com/ajax/libs/ramda/0.2.3/ramda.min', 8 | future: 'http://looprecur.com/hostedjs/v2/data.future.umd', 9 | hcjs: 'http://looprecur.com/hostedjs/v2/hcjs' 10 | } 11 | }); 12 | 13 | require([ 14 | 'ramda', 15 | 'jquery', 16 | 'future', 17 | 'hcjs', 18 | 'domReady!' 19 | ], 20 | function (_, $, Future, hcjs) { 21 | 22 | // imageTag :: URL -> DOM 23 | var imageTag = function (url) { return $('', { src: url }); }; 24 | 25 | ///////////////////////////////////////////////////////////////////////////////////// 26 | // PictureBox 27 | 28 | // PictureBox = data 29 | // val :: Future(a) 30 | var _PictureBox = function(val) { 31 | this.val = val; 32 | this.fork = function(a, b){ return val.fork(a, b); }; 33 | }; 34 | 35 | var PictureBox = function(x){ return new _PictureBox(x); } 36 | 37 | // instance Monoid PictureBox where 38 | _PictureBox.prototype.empty = function () { return PictureBox(Future.of([])); }; 39 | _PictureBox.prototype.concat = function (y) { 40 | return PictureBox(this.val.concat(y.val)); 41 | }; 42 | 43 | _PictureBox.prototype.map = function (f) { return PictureBox(this.val.map(f)); }; 44 | _PictureBox.prototype.of = function (x) { return PictureBox(Future.of(x)) }; 45 | _PictureBox.prototype.ap = function (p2) { return PictureBox(this.val.ap(p2.val)); }; 46 | 47 | ///////////////////////////////////////////////////////////////////////////////////// 48 | // Flickr api 49 | 50 | // url :: String -> URL 51 | var url = function(t) { 52 | return 'http://api.flickr.com/services/feeds/photos_public.gne?tags='+t+'&format=json&jsoncallback=?'; 53 | }; 54 | 55 | // src :: FlickrItem -> URL 56 | var src = compose(_.get('m'), _.get('media')); 57 | 58 | // srcs :: FlickrSearch -> [URL] 59 | var srcs = compose(map(src), _.get('items')); 60 | 61 | // images :: FlickrSearch -> [DOM] 62 | var images = compose(map(imageTag), srcs); 63 | 64 | // widget :: String -> PictureBox 65 | var widget = compose(PictureBox, map(images), getJSON, url); 66 | 67 | 68 | ///////////////////////////////////////////////////////////////////////////////////// 69 | // Test code 70 | 71 | setBothHtml = curry(function(cats, dogs){ 72 | setHtml($('#flickr'), cats); 73 | setHtml($('#flickr2'), dogs); 74 | }) 75 | 76 | liftA2(setBothHtml, widget('cat'), widget('dog')).fork(log, log); 77 | }); 78 | -------------------------------------------------------------------------------- /demos/step4/flickr.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 18 | 19 | JS Bin 20 | 21 | 22 | 23 |
24 |

Photo Stream

25 |
26 |
27 |
28 | 29 | 30 | -------------------------------------------------------------------------------- /demos/step4/flickr.js: -------------------------------------------------------------------------------- 1 | /*jslint nomen: true */ 2 | requirejs.config({ 3 | shim: {}, 4 | paths: { 5 | domReady: 'https://cdnjs.cloudflare.com/ajax/libs/require-domReady/2.0.1/domReady.min', 6 | jquery: 'https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min', 7 | ramda: 'https://cdnjs.cloudflare.com/ajax/libs/ramda/0.2.3/ramda.min', 8 | future: 'http://looprecur.com/hostedjs/v2/data.future.umd', 9 | hcjs: 'http://looprecur.com/hostedjs/v2/hcjs' 10 | } 11 | }); 12 | 13 | require([ 14 | 'ramda', 15 | 'jquery', 16 | 'future', 17 | 'hcjs', 18 | 'domReady!' 19 | ], 20 | function (_, $, Future, hcjs) { 21 | 22 | // imageTag :: URL -> DOM 23 | var imageTag = function (url) { return $('', { src: url }); }; 24 | 25 | var promptUser = function(question) { 26 | return new Future(function(rej, res) { 27 | return res(prompt(question)); 28 | }); 29 | }; 30 | 31 | ///////////////////////////////////////////////////////////////////////////////////// 32 | // PictureBox 33 | 34 | // PictureBox = data 35 | // val :: Future(a) 36 | var _PictureBox = function(val) { 37 | this.val = val; 38 | this.fork = function(a, b){ return val.fork(a, b); }; 39 | }; 40 | 41 | var PictureBox = function(x){ return new _PictureBox(x); }; 42 | 43 | // instance Monoid PictureBox where 44 | _PictureBox.prototype.empty = function () { return PictureBox(Future.of([])); }; 45 | _PictureBox.prototype.concat = function (y) { 46 | return PictureBox(this.val.concat(y.val)); 47 | }; 48 | 49 | _PictureBox.prototype.map = function (f) { return PictureBox(this.val.map(f)); }; 50 | _PictureBox.prototype.of = function (x) { return PictureBox(Future.of(x)) }; 51 | _PictureBox.prototype.ap = function (p2) { return PictureBox(this.val.ap(p2.val)); }; 52 | 53 | ///////////////////////////////////////////////////////////////////////////////////// 54 | // Flickr api 55 | 56 | // url :: String -> URL 57 | var url = function(t) { 58 | return 'http://api.flickr.com/services/feeds/photos_public.gne?tags='+t+'&format=json&jsoncallback=?'; 59 | }; 60 | 61 | // src :: FlickrItem -> URL 62 | var src = compose(_.get('m'), _.get('media')); 63 | 64 | // srcs :: FlickrSearch -> [URL] 65 | var srcs = compose(map(src), _.get('items')); 66 | 67 | // images :: FlickrSearch -> [DOM] 68 | var images = compose(map(imageTag), srcs); 69 | 70 | // search :: String -> Future FlickrSearch 71 | var search = compose(getJSON, url); 72 | 73 | // widget :: String -> PictureBox 74 | var widget = compose(PictureBox, map(images), chain(search), promptUser); 75 | 76 | 77 | ///////////////////////////////////////////////////////////////////////////////////// 78 | // Test code 79 | 80 | widget("Enter a term").fork(log, setHtml($('#flickr'))); 81 | }); 82 | -------------------------------------------------------------------------------- /jsbins/_loader.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JS Bin 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /jsbins/applicatives-problem.js: -------------------------------------------------------------------------------- 1 | requirejs.config({ 2 | shim: {}, 3 | paths: { 4 | domReady: 'https://cdnjs.cloudflare.com/ajax/libs/require-domReady/2.0.1/domReady.min', 5 | ramda: 'https://raw.githack.com/CrossEye/ramda/master/ramda', 6 | maybe: 'http://looprecur.com/hostedjs/v2/maybe', 7 | io: 'http://looprecur.com/hostedjs/v2/io', 8 | future: 'http://looprecur.com/hostedjs/v2/data.future.umd', 9 | hcjs: 'http://looprecur.com/hostedjs/v2/hcjs' 10 | } 11 | }); 12 | 13 | require( 14 | [ 15 | 'ramda', 16 | 'maybe', 17 | 'io', 18 | 'future', 19 | 'hcjs', 20 | 'domReady!' 21 | ], 22 | function (_, Maybe, io, Future) { 23 | console.clear(); 24 | 25 | var runIO = io.runIO; 26 | 27 | 28 | 29 | // Exercise 1 30 | // ========== 31 | // Write a function that add's two possibly null numbers together using Maybe and ap() 32 | console.log("--------Start exercise 1--------"); 33 | 34 | var ex1 = function (x, y) { 35 | return undefined; // define me 36 | }; 37 | 38 | assertEqual(Maybe(5), ex1(2, 3)); 39 | assertEqual(Maybe(null), ex1(null, 3)); 40 | console.log("exercise 1...ok!"); 41 | 42 | 43 | 44 | // Exercise 2 45 | // ========== 46 | // Rewrite 1 to use liftA2 instead of ap() 47 | console.log("--------Start exercise 2--------"); 48 | 49 | 50 | var ex2 = undefined; 51 | 52 | assertEqual(Maybe(5), ex2(Maybe(2), Maybe(3))); 53 | assertEqual(Maybe(null), ex2(Maybe(null), Maybe(3))); 54 | console.log("exercise 2...ok!"); 55 | 56 | 57 | 58 | 59 | // Exercise 3 60 | // ========== 61 | // Make a future by running getPost() and getComments() using applicatives, then renders the page with both 62 | var makeComments = _.reduce(function (acc, c) { 63 | return acc + "
  • " + c + "
  • "; 64 | }, ""); 65 | var render = _.curry(function (post, comments) { 66 | return "
    " + post.title + "
    " + makeComments(comments); 67 | }); 68 | console.log("--------Start exercise 3--------"); 69 | 70 | 71 | var ex3 = undefined; 72 | 73 | 74 | ex3.fork(log, function (html) { 75 | assertEqual("
    Love them futures
  • This class should be illegal
  • Monads are like space burritos
  • ", html); 76 | console.log("exercise 3...ok!"); 77 | }); 78 | 79 | 80 | 81 | 82 | // Exercise 4 83 | // ========== 84 | // setup... 85 | localStorage.player1 = "toby"; 86 | localStorage.player2 = "sally"; 87 | 88 | // Write a function that gets both player1 and player2 from the cache. 89 | var getCache = function (x) { 90 | return localStorage[x]; 91 | }.toIO(); 92 | var game = _.curry(function (p1, p2) { 93 | return p1 + ' vs ' + p2; 94 | }); 95 | console.log("--------Start exercise 4--------"); 96 | 97 | 98 | var ex4 = undefined; 99 | 100 | 101 | assertEqual("toby vs sally", runIO(ex4)); 102 | console.log("exercise 4...ok!"); 103 | 104 | 105 | 106 | 107 | // TEST HELPERS 108 | // ===================== 109 | 110 | function getPost(i) { 111 | return new Future(function (rej, res) { 112 | setTimeout(function () { 113 | res({ 114 | id: i, 115 | title: 'Love them futures' 116 | }); 117 | }, 300); 118 | }); 119 | } 120 | 121 | function getComments(i) { 122 | return new Future(function (rej, res) { 123 | setTimeout(function () { 124 | res(["This class should be illegal", "Monads are like space burritos"]); 125 | }, 300); 126 | }); 127 | } 128 | 129 | }); 130 | -------------------------------------------------------------------------------- /jsbins/applicatives-solution.js: -------------------------------------------------------------------------------- 1 | requirejs.config({ 2 | shim: {}, 3 | paths: { 4 | domReady: 'https://cdnjs.cloudflare.com/ajax/libs/require-domReady/2.0.1/domReady.min', 5 | ramda: 'https://raw.githack.com/CrossEye/ramda/master/ramda', 6 | maybe: 'http://looprecur.com/hostedjs/v2/maybe', 7 | io: 'http://looprecur.com/hostedjs/v2/io', 8 | future: 'http://looprecur.com/hostedjs/v2/data.future.umd', 9 | hcjs: 'http://looprecur.com/hostedjs/v2/hcjs' 10 | } 11 | }); 12 | 13 | require( 14 | [ 15 | 'ramda', 16 | 'maybe', 17 | 'io', 18 | 'future', 19 | 'hcjs', 20 | 'domReady!' 21 | ], 22 | function (_, Maybe, io, Future) { 23 | console.clear(); 24 | 25 | var runIO = io.runIO; 26 | 27 | 28 | 29 | // Exercise 1 30 | // ========== 31 | // Write a function that add's two possibly null numbers together using Maybe and ap() 32 | console.log("--------Start exercise 1--------"); 33 | 34 | var ex1 = function (x, y) { 35 | return Maybe.of(_.add).ap(Maybe(x)).ap(Maybe(y)); 36 | }; 37 | 38 | assertEqual(Maybe(5), ex1(2, 3)); 39 | assertEqual(Maybe(null), ex1(null, 3)); 40 | console.log("exercise 1...ok!"); 41 | 42 | 43 | 44 | // Exercise 2 45 | // ========== 46 | // Rewrite 1 to use liftA2 instead of ap() 47 | console.log("--------Start exercise 2--------"); 48 | 49 | 50 | var ex2 = liftA2(_.add); 51 | 52 | assertEqual(Maybe(5), ex2(Maybe(2), Maybe(3))); 53 | assertEqual(Maybe(null), ex2(Maybe(null), Maybe(3))); 54 | console.log("exercise 2...ok!"); 55 | 56 | 57 | 58 | 59 | // Exercise 3 60 | // ========== 61 | // Make a future by running getPost() and getComments() using applicatives, then renders the page with both 62 | var makeComments = _.reduce(function (acc, c) { 63 | return acc + "
  • " + c + "
  • "; 64 | }, ""); 65 | var render = _.curry(function (post, comments) { 66 | return "
    " + post.title + "
    " + makeComments(comments); 67 | }); 68 | console.log("--------Start exercise 3--------"); 69 | 70 | 71 | var ex3 = Future.of(render).ap(getPost(2)).ap(getComments(2)); 72 | // or 73 | // var ex3 = liftA2(render, getPost(2), getComments(2)) 74 | 75 | 76 | ex3.fork(log, function (html) { 77 | assertEqual("
    Love them futures
  • This class should be illegal
  • Monads are like space burritos
  • ", html); 78 | console.log("exercise 3...ok!"); 79 | }); 80 | 81 | 82 | 83 | 84 | // Exercise 4 85 | // ========== 86 | // setup... 87 | localStorage.player1 = "toby"; 88 | localStorage.player2 = "sally"; 89 | 90 | // Write a function that gets both player1 and player2 from the cache. 91 | var getCache = function (x) { 92 | return localStorage[x]; 93 | }.toIO(); 94 | var game = _.curry(function (p1, p2) { 95 | return p1 + ' vs ' + p2; 96 | }); 97 | console.log("--------Start exercise 4--------"); 98 | 99 | 100 | var ex4 = liftA2(game, getCache('player1'), getCache('player2')); 101 | 102 | 103 | assertEqual("toby vs sally", runIO(ex4)); 104 | console.log("exercise 4...ok!"); 105 | 106 | 107 | 108 | 109 | // TEST HELPERS 110 | // ===================== 111 | 112 | function getPost(i) { 113 | return new Future(function (rej, res) { 114 | setTimeout(function () { 115 | res({ 116 | id: i, 117 | title: 'Love them futures' 118 | }); 119 | }, 300); 120 | }); 121 | } 122 | 123 | function getComments(i) { 124 | return new Future(function (rej, res) { 125 | setTimeout(function () { 126 | res(["This class should be illegal", "Monads are like space burritos"]); 127 | }, 300); 128 | }); 129 | } 130 | 131 | }); 132 | -------------------------------------------------------------------------------- /jsbins/compose-problem.js: -------------------------------------------------------------------------------- 1 | requirejs.config({ 2 | shim: { 3 | }, 4 | paths: { 5 | domReady: 'https://cdnjs.cloudflare.com/ajax/libs/require-domReady/2.0.1/domReady.min', 6 | hcjs: 'http://looprecur.com/hostedjs/v2/hcjs', 7 | ramda: 'https://raw.githack.com/CrossEye/ramda/master/ramda' 8 | } 9 | }); 10 | 11 | require( 12 | [ 13 | 'ramda', 14 | 'hcjs', 15 | 'domReady!' 16 | ], 17 | function (_) { 18 | console.clear(); 19 | 20 | /****************************************** 21 | C O M P O S I T I O N E X A M P L E 22 | ******************************************/ 23 | 24 | // Curried functions are easy to compose. 25 | // Using _.map, _.size, and _.split we can 26 | // make a function that returns the lengths 27 | // of the words in a string. 28 | 29 | var lengths = _.compose( 30 | _.map(_.size), _.split(' ') 31 | ); 32 | console.log(lengths('once upon a time')); 33 | 34 | /******************************************* 35 | Y O U R T U R N 36 | ********************************************/ 37 | 38 | var articles = [ 39 | { 40 | title: 'Everything Sucks', 41 | url: 'http://do.wn/sucks.html', 42 | author: { 43 | name: 'Debbie Downer', 44 | email: 'debbie@do.wn' 45 | } 46 | }, 47 | { 48 | title: 'If You Please', 49 | url: 'http://www.geocities.com/milq', 50 | author: { 51 | name: 'Caspar Milquetoast', 52 | email: 'hello@me.com' 53 | } 54 | } 55 | ]; 56 | 57 | 58 | console.log("--------Start exercise 1--------"); 59 | // -- Challenge 1 ------------------------- 60 | // Write a function to return the first title of the 61 | // articles above using only _.get, _.head, and 62 | // _.compose. 63 | 64 | 65 | var firstTitle = undefined; 66 | 67 | assertEqual('Everything Sucks', firstTitle(articles)); 68 | 69 | console.log("--------Exercise 1 pass!--------"); 70 | 71 | 72 | 73 | console.log("--------Start exercise 2--------"); 74 | // -- Challenge 2 ------------------------- 75 | // Return a list of the author names in 76 | // articles using only _.get, _.compose, and 77 | // _.map. 78 | 79 | var names = undefined; 80 | assertEqual( 81 | ['Debbie Downer', 'Caspar Milquetoast'], 82 | names(articles) 83 | ); 84 | 85 | console.log("--------Exercise 2 pass!--------"); 86 | 87 | 88 | 89 | console.log("--------Start exercise 3--------"); 90 | // -- Challenge 3 ------------------------- 91 | // isAuthor uses the names function above to see if 92 | // a given person wrote any of the articles. 93 | // "Uncompose" the function below by rewrite it 94 | // without _.compose to make it more natural. 95 | 96 | var isAuthor = _.curry(function(x, xs){ 97 | return _.compose(_.contains(x), names)(xs); 98 | }); 99 | 100 | assertEqual( 101 | false, 102 | isAuthor('New Guy', articles) 103 | ); 104 | assertEqual( 105 | true, 106 | isAuthor('Debbie Downer', articles) 107 | ); 108 | 109 | console.log("--------Exercise 3 pass!--------"); 110 | 111 | 112 | 113 | 114 | console.log("--------Start exercise 4--------"); 115 | // -- Challenge 4 ------------------------- 116 | // There is more to point-free programming 117 | // than compose! Let's build ourselves 118 | // another function that combines functions 119 | // to let us write code without glue variables. 120 | 121 | var fork = _.curry(function(lastly, f, g, x) { 122 | return lastly(f(x), g(x)); 123 | }); 124 | 125 | // As you can see, the fork function is a 126 | // pipeline like compose, except it duplicates 127 | // its value, sends it to two functions, then 128 | // sends the results to a combining function. 129 | // 130 | // Your challenge: implement a function to 131 | // compute the average values in a list using 132 | // only fork, _.divide, _.sum, and _.size. 133 | 134 | var avg = undefined; // change this 135 | assertEqual(3, avg([1,2,3,4,5])); 136 | console.log("--------Exercise 4 pass!--------"); 137 | 138 | }) 139 | -------------------------------------------------------------------------------- /jsbins/compose-solution.js: -------------------------------------------------------------------------------- 1 | requirejs.config({ 2 | shim: { 3 | }, 4 | paths: { 5 | domReady: 'https://cdnjs.cloudflare.com/ajax/libs/require-domReady/2.0.1/domReady.min', 6 | hcjs: 'http://looprecur.com/hostedjs/v2/hcjs', 7 | ramda: 'https://raw.githack.com/CrossEye/ramda/master/ramda' 8 | } 9 | }); 10 | 11 | require( 12 | [ 13 | 'ramda', 14 | 'hcjs', 15 | 'domReady!' 16 | ], 17 | function (_) { 18 | console.clear(); 19 | 20 | /****************************************** 21 | C O M P O S I T I O N E X A M P L E 22 | ******************************************/ 23 | 24 | // Curried functions are easy to compose. 25 | // Using _.map, _.size, and _.split we can 26 | // make a function that returns the lengths 27 | // of the words in a string. 28 | 29 | var lengths = _.compose( 30 | _.map(_.size), _.split(' ') 31 | ); 32 | console.log(lengths('once upon a time')); 33 | 34 | /******************************************* 35 | Y O U R T U R N 36 | ********************************************/ 37 | 38 | var articles = [ 39 | { 40 | title: 'Everything Sucks', 41 | url: 'http://do.wn/sucks.html', 42 | author: { 43 | name: 'Debbie Downer', 44 | email: 'debbie@do.wn' 45 | } 46 | }, 47 | { 48 | title: 'If You Please', 49 | url: 'http://www.geocities.com/milq', 50 | author: { 51 | name: 'Caspar Milquetoast', 52 | email: 'hello@me.com' 53 | } 54 | } 55 | ]; 56 | 57 | 58 | console.log("--------Start exercise 1--------"); 59 | // -- Challenge 1 ------------------------- 60 | // Write a function to return the first title of the 61 | // articles above using only _.get, _.head, and 62 | // _.compose. 63 | 64 | 65 | var firstTitle = _.compose(_.get('title'), _.head) 66 | 67 | assertEqual('Everything Sucks', firstTitle(articles)); 68 | 69 | console.log("--------Exercise 1 pass!--------"); 70 | 71 | 72 | 73 | console.log("--------Start exercise 2--------"); 74 | // -- Challenge 2 ------------------------- 75 | // Return a list of the author names in 76 | // articles using only _.get, _.compose, and 77 | // _.map. 78 | 79 | var names = _.map(_.compose(_.get('name'), _.get('author'))) 80 | assertEqual( 81 | ['Debbie Downer', 'Caspar Milquetoast'], 82 | names(articles) 83 | ); 84 | 85 | console.log("--------Exercise 2 pass!--------"); 86 | 87 | 88 | 89 | console.log("--------Start exercise 3--------"); 90 | // -- Challenge 3 ------------------------- 91 | // isAuthor uses the names function above to see if 92 | // a given person wrote any of the articles. 93 | // "Uncompose" the function below by rewrite it 94 | // without _.compose to make it more natural. 95 | var isAuthor = _.curry(function(x, xs){ 96 | return _.contains(x, names(xs)); 97 | }); 98 | 99 | assertEqual( 100 | false, 101 | isAuthor('New Guy', articles) 102 | ); 103 | assertEqual( 104 | true, 105 | isAuthor('Debbie Downer', articles) 106 | ); 107 | 108 | console.log("--------Exercise 3 pass!--------"); 109 | 110 | 111 | 112 | 113 | console.log("--------Start exercise 4--------"); 114 | // -- Challenge 4 ------------------------- 115 | // There is more to point-free programming 116 | // than compose! Let's build ourselves 117 | // another function that combines functions 118 | // to let us write code without glue variables. 119 | 120 | var fork = _.curry(function(lastly, f, g, x) { 121 | return lastly(f(x), g(x)); 122 | }); 123 | 124 | // As you can see, the fork function is a 125 | // pipeline like compose, except it duplicates 126 | // its value, sends it to two functions, then 127 | // sends the results to a combining function. 128 | // 129 | // Your challenge: implement a function to 130 | // compute the average values in a list using 131 | // only fork, _.divide, _.sum, and _.size. 132 | 133 | var avg = fork(_.divide, _.sum, _.size); // change this 134 | assertEqual(3, avg([1,2,3,4,5])); 135 | console.log("--------Exercise 4 pass!--------"); 136 | 137 | }) 138 | -------------------------------------------------------------------------------- /jsbins/curry-problem.js: -------------------------------------------------------------------------------- 1 | requirejs.config({ 2 | shim: {}, 3 | paths: { 4 | domReady: 'https://cdnjs.cloudflare.com/ajax/libs/require-domReady/2.0.1/domReady.min', 5 | hcjs: 'http://looprecur.com/hostedjs/v2/hcjs', 6 | ramda: 'https://raw.githack.com/CrossEye/ramda/master/ramda' 7 | } 8 | }); 9 | 10 | require( 11 | [ 12 | 'ramda', 13 | 'hcjs', 14 | 'domReady!' 15 | ], 16 | function (_) { 17 | 18 | console.clear(); 19 | 20 | 21 | 22 | 23 | /***************************************** 24 | C U R R Y I N G E X A M P L E 25 | ******************************************/ 26 | 27 | // We've got a nice multiply function. 28 | // It takes two arguments. 29 | 30 | console.log(_.multiply(3, 4)); 31 | 32 | // But it has been secretly curried already 33 | // so we can feed it fewer arguments and it 34 | // will return a new function. 35 | // 36 | // How about making a function to double a 37 | // value? Done. 38 | var double = _.multiply(2); 39 | 40 | console.log(double(13)); 41 | 42 | 43 | 44 | 45 | /***************************************** 46 | Y O U R T U R N 47 | ******************************************/ 48 | 49 | // _.split pulls a string apart around a 50 | // given value 51 | console.log(_.split('i', 'mississippi')); 52 | 53 | console.log("--------Start exercise 1--------"); 54 | // -- Challenge 1 ------------------------ 55 | // Make a function called "words" which 56 | // returns a list of words in a string. 57 | // Use only the split function and 58 | // currying. 59 | var words = undefined; // change this 60 | assertEqual( 61 | ['one', 'two', 'three'], 62 | words('one two three') 63 | ); 64 | 65 | console.log("--------Exercise 1 pass!--------"); 66 | 67 | 68 | 69 | 70 | console.log("--------Start exercise 2--------"); 71 | // -- Challenge 2 ------------------------ 72 | // Create a function to triple every 73 | // number in a list using only 74 | // _.multiply and _.map. 75 | 76 | var tripleList = undefined; 77 | assertEqual([3, 6, 9], tripleList([1, 2, 3])); 78 | console.log("--------Exercise 2 pass!--------"); 79 | 80 | 81 | 82 | 83 | console.log("--------Start exercise 3--------"); 84 | // -- Challenge 3 ------------------------ 85 | // Create a function to find the largest 86 | // number in a list. You can use the 87 | // greater(a,b) function which returns the 88 | // greater of its two inputs. You can do 89 | // this with currying and one of the list 90 | // functions _.map, _.filter, or _.reduce. 91 | 92 | var greater = function (a, b) { 93 | return a > b ? a : b; 94 | }; 95 | 96 | var max = undefined; 97 | assertEqual(9, max([1, -3483, 9, 7, 2])); 98 | assertEqual(-1, max([-21, -3483, -2, -1])); 99 | 100 | 101 | console.log("--------Exercise 3 pass!--------"); 102 | 103 | }); 104 | -------------------------------------------------------------------------------- /jsbins/curry-solution.js: -------------------------------------------------------------------------------- 1 | requirejs.config({ 2 | shim: { 3 | }, 4 | paths: { 5 | domReady: 'https://cdnjs.cloudflare.com/ajax/libs/require-domReady/2.0.1/domReady.min', 6 | hcjs: 'http://looprecur.com/hostedjs/v2/hcjs', 7 | ramda: 'https://raw.githack.com/CrossEye/ramda/master/ramda' 8 | } 9 | }); 10 | 11 | require( 12 | [ 13 | 'ramda', 14 | 'hcjs', 15 | 'domReady!' 16 | ], 17 | function (_) { 18 | 19 | /***************************************** 20 | C U R R Y I N G E X A M P L E 21 | ******************************************/ 22 | 23 | // We've got a nice multiply function. 24 | // It takes two arguments. 25 | 26 | console.log( _.multiply(3, 4) ); 27 | 28 | // But it has been secretly curried already 29 | // so we can feed it fewer arguments and it 30 | // will return a new function. 31 | // 32 | // How about making a function to double a 33 | // value? Done. 34 | var double = _.multiply(2); 35 | 36 | console.log( double(13) ); 37 | 38 | /***************************************** 39 | Y O U R T U R N 40 | ******************************************/ 41 | 42 | // _.split pulls a string apart around a 43 | // given value 44 | console.log( _.split('i', 'mississippi') ); 45 | 46 | console.log("--------Start exercise 1--------"); 47 | // -- Challenge 1 ------------------------ 48 | // Make a function called "words" which 49 | // returns a list of words in a string. 50 | // Use only the split function and 51 | // currying. 52 | var words = _.split(' '); // change this 53 | assertEqual( 54 | ['one', 'two', 'three'], 55 | words('one two three') 56 | ); 57 | 58 | console.log("--------Exercise 1 pass!--------"); 59 | 60 | 61 | 62 | console.log("--------Start exercise 2--------"); 63 | // -- Challenge 2 ------------------------ 64 | // Create a function to triple every 65 | // number in a list using only 66 | // _.multiply and _.map. 67 | 68 | var tripleList = _.map(_.multiply(3)); 69 | assertEqual([3,6,9], tripleList([1,2,3])); 70 | console.log("--------Exercise 2 pass!--------"); 71 | 72 | 73 | 74 | console.log("--------Start exercise 3--------"); 75 | // -- Challenge 3 ------------------------ 76 | // Create a function to find the largest 77 | // number in a list. You can use the 78 | // greater(a,b) function which returns the 79 | // greater of its two inputs. You can do 80 | // this with currying and one of the list 81 | // functions _.map, _.filter, or _.reduce. 82 | 83 | var greater = function(a,b) { 84 | return a > b ? a : b; 85 | }; 86 | 87 | var max = _.reduce(greater, -Infinity) 88 | assertEqual(9, max([1,-3483,9,7,2])); 89 | assertEqual(-1, max([-21,-3483,-2,-1])); 90 | 91 | 92 | console.log("--------Exercise 3 pass!--------"); 93 | 94 | }) 95 | -------------------------------------------------------------------------------- /jsbins/functor-problem.js: -------------------------------------------------------------------------------- 1 | requirejs.config({ 2 | shim: {}, 3 | paths: { 4 | domReady: 'https://cdnjs.cloudflare.com/ajax/libs/require-domReady/2.0.1/domReady.min', 5 | hcjs: 'http://looprecur.com/hostedjs/v2/hcjs', 6 | ramda: 'https://raw.githack.com/CrossEye/ramda/master/ramda', 7 | maybe: 'http://looprecur.com/hostedjs/v2/maybe', 8 | id: 'http://looprecur.com/hostedjs/v2/id' 9 | } 10 | }); 11 | 12 | require( 13 | [ 14 | 'ramda', 15 | 'maybe', 16 | 'id', 17 | 'hcjs', 18 | 'domReady!' 19 | ], 20 | function (_, Maybe, Identity) { 21 | console.clear(); 22 | 23 | 24 | 25 | 26 | // Exercise 1 27 | // ========== 28 | // Use _.add(x,y) and map(f,x) to make a function that increments a value inside a functor 29 | console.log("--------Start exercise 1--------"); 30 | 31 | var ex1 = undefined; 32 | 33 | 34 | assertEqual(Identity(3), ex1(Identity(2))); 35 | console.log("exercise 1...ok!"); 36 | 37 | 38 | 39 | 40 | // Exercise 2 41 | // ========== 42 | // Use _.head to get the first element of the list 43 | var xs = Identity(['do', 'ray', 'me', 'fa', 'so', 'la', 'ti', 'do']); 44 | console.log("--------Start exercise 2--------"); 45 | 46 | 47 | var ex2 = undefined; 48 | 49 | 50 | assertEqual(Identity('do'), ex2(xs)); 51 | console.log("exercise 2...ok!"); 52 | 53 | 54 | 55 | 56 | // Exercise 3 57 | // ========== 58 | // Use safeGet and _.head to find the first initial of the user 59 | var safeGet = _.curry(function (x, o) { 60 | return Maybe(o[x]); 61 | }); 62 | var user = { 63 | id: 2, 64 | name: "Albert" 65 | }; 66 | console.log("--------Start exercise 3--------"); 67 | 68 | var ex3 = undefined; 69 | 70 | 71 | assertEqual(Maybe('A'), ex3(user)); 72 | console.log("exercise 3...ok!"); 73 | 74 | 75 | 76 | 77 | // Exercise 4 78 | // ========== 79 | // Use Maybe to rewrite ex4 without an if statement 80 | console.log("--------Start exercise 4--------"); 81 | 82 | var ex4 = function (n) { 83 | if (n) { 84 | return parseInt(n);; 85 | } 86 | }; 87 | 88 | ex4 = undefined; 89 | 90 | 91 | assertEqual(Maybe(4), ex4("4")); 92 | console.log("exercise 4...ok!"); 93 | 94 | }); 95 | -------------------------------------------------------------------------------- /jsbins/functor-solution.js: -------------------------------------------------------------------------------- 1 | requirejs.config({ 2 | shim: {}, 3 | paths: { 4 | domReady: 'https://cdnjs.cloudflare.com/ajax/libs/require-domReady/2.0.1/domReady.min', 5 | hcjs: 'http://looprecur.com/hostedjs/v2/hcjs', 6 | ramda: 'https://raw.githack.com/CrossEye/ramda/master/ramda', 7 | maybe: 'http://looprecur.com/hostedjs/v2/maybe', 8 | id: 'http://looprecur.com/hostedjs/v2/id' 9 | } 10 | }); 11 | 12 | require( 13 | [ 14 | 'ramda', 15 | 'maybe', 16 | 'id', 17 | 'hcjs', 18 | 'domReady!' 19 | ], 20 | function (_, Maybe, Identity) { 21 | console.clear(); 22 | 23 | 24 | 25 | 26 | // Exercise 1 27 | // ========== 28 | // Use _.add(x,y) and map(f,x) to make a function that increments a value inside a functor 29 | console.log("--------Start exercise 1--------"); 30 | 31 | var ex1 = map(_.add(1)); 32 | 33 | 34 | assertEqual(Identity(3), ex1(Identity(2))); 35 | console.log("exercise 1...ok!"); 36 | 37 | 38 | 39 | 40 | // Exercise 2 41 | // ========== 42 | // Use _.head to get the first element of the list 43 | var xs = Identity(['do', 'ray', 'me', 'fa', 'so', 'la', 'ti', 'do']); 44 | console.log("--------Start exercise 2--------"); 45 | 46 | 47 | var ex2 = map(_.head); 48 | 49 | 50 | assertEqual(Identity('do'), ex2(xs)); 51 | console.log("exercise 2...ok!"); 52 | 53 | 54 | 55 | 56 | // Exercise 3 57 | // ========== 58 | // Use safeGet and _.head to find the first initial of the user 59 | var safeGet = _.curry(function (x, o) { 60 | return Maybe(o[x]); 61 | }); 62 | var user = { 63 | id: 2, 64 | name: "Albert" 65 | }; 66 | console.log("--------Start exercise 3--------"); 67 | 68 | var ex3 = compose(map(_.head), safeGet('name')); 69 | 70 | 71 | assertEqual(Maybe('A'), ex3(user)); 72 | console.log("exercise 3...ok!"); 73 | 74 | 75 | 76 | 77 | // Exercise 4 78 | // ========== 79 | // Use Maybe to rewrite ex4 without an if statement 80 | console.log("--------Start exercise 4--------"); 81 | 82 | var ex4 = function (n) { 83 | if (n) { 84 | return parseInt(n); 85 | } 86 | }; 87 | 88 | ex4 = compose(map(parseInt), Maybe); 89 | 90 | 91 | assertEqual(Maybe(4), ex4("4")); 92 | console.log("exercise 4...ok!"); 93 | 94 | }); 95 | -------------------------------------------------------------------------------- /jsbins/monads-problem.js: -------------------------------------------------------------------------------- 1 | requirejs.config({ 2 | shim: {}, 3 | paths: { 4 | domReady: 'https://cdnjs.cloudflare.com/ajax/libs/require-domReady/2.0.1/domReady.min', 5 | ramda: 'https://raw.githack.com/CrossEye/ramda/master/ramda', 6 | maybe: 'http://looprecur.com/hostedjs/v2/maybe', 7 | io: 'http://looprecur.com/hostedjs/v2/io', 8 | future: 'http://looprecur.com/hostedjs/v2/data.future.umd', 9 | hcjs: 'http://looprecur.com/hostedjs/v2/hcjs' 10 | } 11 | }); 12 | 13 | require( 14 | [ 15 | 'ramda', 16 | 'maybe', 17 | 'io', 18 | 'future', 19 | 'hcjs', 20 | 'domReady!' 21 | ], 22 | function (_, Maybe, io, Future) { 23 | console.clear(); 24 | 25 | 26 | var runIO = io.runIO; 27 | 28 | 29 | 30 | // Exercise 1 31 | // ========== 32 | // Use safeGet and mjoin or chain to safetly get the street name 33 | console.log("--------Start exercise 1--------"); 34 | 35 | var safeGet = _.curry(function (x, o) { 36 | return Maybe(o[x]); 37 | }); 38 | var user = { 39 | id: 2, 40 | name: "Albert", 41 | address: { 42 | street: { 43 | number: 22, 44 | name: 'Walnut St' 45 | } 46 | } 47 | }; 48 | 49 | var ex1 = undefined; 50 | 51 | assertEqual(Maybe('Walnut St'), ex1(user)); 52 | console.log("exercise 1...ok!"); 53 | 54 | 55 | 56 | 57 | // Exercise 2 58 | // ========== 59 | // Use monads to get the href, then purely log it. 60 | 61 | console.log("--------Start exercise 2--------"); 62 | 63 | var getHref = function () { 64 | return location.href; 65 | }.toIO(); 66 | var pureLog = function (x) { 67 | console.log(x); 68 | return x; 69 | }.toIO(); 70 | 71 | var ex2 = undefined; 72 | 73 | assertEqual("http://run.jsbin.com/runner", runIO(ex2(null))); 74 | console.log("exercise 2...ok!"); 75 | 76 | 77 | 78 | 79 | // Exercise 3 80 | // ========== 81 | // Use monads to first get the Post with getPost(), then pass it's id in to getComments(). 82 | console.log("--------Start exercise 3--------"); 83 | 84 | var ex3 = undefined; 85 | 86 | ex3(13).fork(log, function (res) { 87 | assertEqual(2, res.length); 88 | console.log("exercise 3...ok!"); 89 | }); 90 | 91 | 92 | 93 | 94 | // HELPERS 95 | // ===================== 96 | 97 | function getPost(i) { 98 | return new Future(function (rej, res) { 99 | setTimeout(function () { 100 | res({ 101 | id: i, 102 | title: 'Love them futures' 103 | }); 104 | }, 300); 105 | }); 106 | } 107 | 108 | function getComments(i) { 109 | return new Future(function (rej, res) { 110 | setTimeout(function () { 111 | res(["This class should be illegal", "Monads are like space burritos"]); 112 | }, 300); 113 | }); 114 | } 115 | 116 | }); 117 | -------------------------------------------------------------------------------- /jsbins/monads-solution.js: -------------------------------------------------------------------------------- 1 | requirejs.config({ 2 | shim: {}, 3 | paths: { 4 | domReady: 'https://cdnjs.cloudflare.com/ajax/libs/require-domReady/2.0.1/domReady.min', 5 | ramda: 'https://raw.githack.com/CrossEye/ramda/master/ramda', 6 | maybe: 'http://looprecur.com/hostedjs/v2/maybe', 7 | io: 'http://looprecur.com/hostedjs/v2/io', 8 | future: 'http://looprecur.com/hostedjs/v2/data.future.umd', 9 | hcjs: 'http://looprecur.com/hostedjs/v2/hcjs' 10 | } 11 | }); 12 | 13 | require( 14 | [ 15 | 'ramda', 16 | 'maybe', 17 | 'io', 18 | 'future', 19 | 'hcjs', 20 | 'domReady!' 21 | ], 22 | function (_, Maybe, io, Future) { 23 | console.clear(); 24 | 25 | 26 | var runIO = io.runIO; 27 | 28 | 29 | 30 | // Exercise 1 31 | // ========== 32 | // Use safeGet and mjoin or chain to safetly get the street name 33 | console.log("--------Start exercise 1--------"); 34 | 35 | var safeGet = _.curry(function (x, o) { 36 | return Maybe(o[x]); 37 | }); 38 | var user = { 39 | id: 2, 40 | name: "Albert", 41 | address: { 42 | street: { 43 | number: 22, 44 | name: 'Walnut St' 45 | } 46 | } 47 | }; 48 | 49 | var ex1 = compose(chain(safeGet('name')), chain(safeGet('street')), safeGet('address')); 50 | 51 | assertEqual(Maybe('Walnut St'), ex1(user)); 52 | console.log("exercise 1...ok!"); 53 | 54 | 55 | 56 | 57 | // Exercise 2 58 | // ========== 59 | // Use monads to get the href, then purely log it. 60 | 61 | console.log("--------Start exercise 2--------"); 62 | 63 | var getHref = function () { 64 | return location.href; 65 | }.toIO(); 66 | var pureLog = function (x) { 67 | console.log(x); 68 | return x; 69 | }.toIO(); 70 | 71 | var ex2 = compose(chain(pureLog), getHref); 72 | 73 | assertEqual("http://run.jsbin.com/runner", runIO(ex2(null))); 74 | console.log("exercise 2...ok!"); 75 | 76 | 77 | 78 | 79 | // Exercise 3 80 | // ========== 81 | // Use monads to first get the Post with getPost(), then pass it's id in to getComments(). 82 | console.log("--------Start exercise 3--------"); 83 | 84 | var ex3 = compose(chain(compose(getComments, _.get('id'))), getPost); 85 | 86 | ex3(13).fork(log, function (res) { 87 | assertEqual(2, res.length); 88 | console.log("exercise 3...ok!"); 89 | }); 90 | 91 | 92 | 93 | 94 | // HELPERS 95 | // ===================== 96 | 97 | function getPost(i) { 98 | return new Future(function (rej, res) { 99 | setTimeout(function () { 100 | res({ 101 | id: i, 102 | title: 'Love them futures' 103 | }); 104 | }, 300); 105 | }); 106 | } 107 | 108 | function getComments(i) { 109 | return new Future(function (rej, res) { 110 | setTimeout(function () { 111 | res(["This class should be illegal", "Monads are like space burritos"]); 112 | }, 300); 113 | }); 114 | } 115 | 116 | }); 117 | -------------------------------------------------------------------------------- /jsbins/monoids-problem.js: -------------------------------------------------------------------------------- 1 | requirejs.config({ 2 | shim: {}, 3 | paths: { 4 | domReady: 'https://cdnjs.cloudflare.com/ajax/libs/require-domReady/2.0.1/domReady.min', 5 | ramda: 'https://raw.githack.com/CrossEye/ramda/master/ramda', 6 | monoids: 'http://looprecur.com/hostedjs/v2/monoids', 7 | hcjs: 'http://looprecur.com/hostedjs/v2/hcjs' 8 | } 9 | }); 10 | 11 | require( 12 | [ 13 | 'ramda', 14 | 'monoids', 15 | 'hcjs', 16 | 'domReady!' 17 | ], 18 | function (_, Monoids) { 19 | console.clear(); 20 | 21 | var Sum = Monoids.Sum; 22 | var Product = Monoids.Product; 23 | var Max = Monoids.Max; 24 | var Min = Monoids.Min; 25 | var Any = Monoids.Any; 26 | var All = Monoids.All; 27 | var getResult = Monoids.getResult; 28 | 29 | 30 | 31 | 32 | // Exercise 1 33 | // ========== 34 | // rewrite the ex1 function to use getResult() mconcat() and Sum() instead of sum() 35 | console.log("--------Start exercise 1--------"); 36 | 37 | var sum = _.reduce(_.add, 0); 38 | 39 | var ex1 = undefined; 40 | 41 | assertEqual(6, ex1([1, 2, 3])); 42 | console.log("exercise 1...ok!"); 43 | 44 | 45 | 46 | 47 | // Exercise 2 48 | // ========== 49 | // Similar to the above, get the Product of the list. 50 | console.log("--------Start exercise 2--------"); 51 | 52 | ex2 = undefined; 53 | 54 | assertEqual(12, ex2([2, 2, 3])); 55 | console.log("exercise 2...ok!"); 56 | 57 | 58 | 59 | // Exercise 3 60 | // ========== 61 | // Similar to the above, get the Max of the list. 62 | console.log("--------Start exercise 3--------"); 63 | ex3 = undefined; 64 | 65 | assertEqual(32, ex3([12, 32, 3])); 66 | console.log("exercise 3...ok!"); 67 | 68 | 69 | 70 | 71 | // Exercise 4 72 | // ========== 73 | // use the function monoid instance to mconcat the functions below to create a full name string. 74 | console.log("--------Start exercise 4--------"); 75 | var firstName = _.get('first'); 76 | var middleName = _.get('middle'); 77 | var lastName = _.get('last'); 78 | var space = _.K(' '); 79 | 80 | var ex4 = undefined; 81 | 82 | var user = { 83 | first: "Bill", 84 | middle: "Jefferson", 85 | last: "Clinton" 86 | }; 87 | assertEqual("Bill Jefferson Clinton", ex4(user)); 88 | console.log("exercise 4...ok!"); 89 | 90 | 91 | 92 | 93 | // Exercise 5 94 | // ========== 95 | // For Tuple to be a monoid, it's x,y must also be monoids. Monoids beget monoids. 96 | // Use this information to complete the definition of Tuple's concat fn. 97 | console.log("--------Start exercise 5--------"); 98 | 99 | var Tuple = _.curry(function (x, y) { 100 | return new _Tuple(x, y); 101 | }); 102 | 103 | var _Tuple = function (x, y) { 104 | this.x = x; 105 | this.y = y; 106 | }; 107 | 108 | _Tuple.prototype.inspect = function () { 109 | return 'Tuple(' + inspectIt(this.x) + ' ' + inspectIt(this.y) + ')'; 110 | }; 111 | 112 | _Tuple.prototype.empty = function () { 113 | return Tuple(this.x.empty(), this.y.empty()); 114 | }; 115 | 116 | 117 | // TODO: DEFINE ME 118 | _Tuple.prototype.concat = function (t2) { 119 | return Tuple(this.x.concat(t2.x), this.y.concat(t2.y)); 120 | }; 121 | 122 | var ex5 = undefined; 123 | 124 | assertEqual(Tuple("abcdef", [1, 2, 3, 4, 5, 6]), ex5); 125 | console.log("exercise 5...ok!"); 126 | 127 | 128 | }); 129 | -------------------------------------------------------------------------------- /jsbins/monoids-solution.js: -------------------------------------------------------------------------------- 1 | requirejs.config({ 2 | shim: {}, 3 | paths: { 4 | domReady: 'https://cdnjs.cloudflare.com/ajax/libs/require-domReady/2.0.1/domReady.min', 5 | ramda: 'https://raw.githack.com/CrossEye/ramda/master/ramda', 6 | monoids: 'http://looprecur.com/hostedjs/v2/monoids', 7 | hcjs: 'http://looprecur.com/hostedjs/v2/hcjs' 8 | } 9 | }); 10 | 11 | require( 12 | [ 13 | 'ramda', 14 | 'monoids', 15 | 'hcjs', 16 | 'domReady!' 17 | ], 18 | function (_, Monoids) { 19 | console.clear(); 20 | 21 | var Sum = Monoids.Sum; 22 | var Product = Monoids.Product; 23 | var Max = Monoids.Max; 24 | var Min = Monoids.Min; 25 | var Any = Monoids.Any; 26 | var All = Monoids.All; 27 | var getResult = Monoids.getResult; 28 | 29 | 30 | 31 | 32 | // Exercise 1 33 | // ========== 34 | // rewrite the ex1 function to use getResult() mconcat() and Sum() instead of sum() 35 | console.log("--------Start exercise 1--------"); 36 | 37 | var sum = _.reduce(_.add, 0); 38 | 39 | var ex1 = compose(getResult, mconcat, map(Sum)); 40 | 41 | assertEqual(6, ex1([1, 2, 3])); 42 | console.log("exercise 1...ok!"); 43 | 44 | 45 | 46 | 47 | // Exercise 2 48 | // ========== 49 | // Similar to the above, get the Product of the list. 50 | console.log("--------Start exercise 2--------"); 51 | 52 | ex2 = compose(getResult, mconcat, map(Product)); 53 | 54 | assertEqual(12, ex2([2, 2, 3])); 55 | console.log("exercise 2...ok!"); 56 | 57 | 58 | 59 | // Exercise 3 60 | // ========== 61 | // Similar to the above, get the Max of the list. 62 | console.log("--------Start exercise 3--------"); 63 | ex3 = compose(getResult, mconcat, map(Max)); 64 | 65 | assertEqual(32, ex3([12, 32, 3])); 66 | console.log("exercise 3...ok!"); 67 | 68 | 69 | 70 | 71 | // Exercise 4 72 | // ========== 73 | // use the function monoid instance to mconcat the functions below to create a full name string. 74 | console.log("--------Start exercise 4--------"); 75 | var firstName = _.get('first'); 76 | var middleName = _.get('middle'); 77 | var lastName = _.get('last'); 78 | var space = _.K(' '); 79 | 80 | var ex4 = mconcat([firstName, space, middleName, space, lastName]); 81 | 82 | var user = { 83 | first: "Bill", 84 | middle: "Jefferson", 85 | last: "Clinton" 86 | }; 87 | assertEqual("Bill Jefferson Clinton", ex4(user)); 88 | console.log("exercise 4...ok!"); 89 | 90 | 91 | 92 | 93 | // Exercise 5 94 | // ========== 95 | // For Tuple to be a monoid, it's x,y must also be monoids. Monoids beget monoids. 96 | // Use this information to complete the definition of Tuple's concat fn. 97 | console.log("--------Start exercise 5--------"); 98 | 99 | var Tuple = _.curry(function (x, y) { 100 | return new _Tuple(x, y); 101 | }); 102 | 103 | var _Tuple = function (x, y) { 104 | this.x = x; 105 | this.y = y; 106 | }; 107 | 108 | _Tuple.prototype.inspect = function () { 109 | return 'Tuple(' + inspectIt(this.x) + ' ' + inspectIt(this.y) + ')'; 110 | }; 111 | 112 | _Tuple.prototype.empty = function () { 113 | return Tuple(this.x.empty(), this.y.empty()); 114 | }; 115 | 116 | 117 | // TODO: DEFINE ME 118 | _Tuple.prototype.concat = function (t2) { 119 | return Tuple(this.x.concat(t2.x), this.y.concat(t2.y)); 120 | }; 121 | 122 | var ex5 = mconcat([Tuple("abc", [1, 2, 3]), Tuple("def", [4, 5, 6])]); 123 | 124 | assertEqual(Tuple("abcdef", [1, 2, 3, 4, 5, 6]), ex5); 125 | console.log("exercise 5...ok!"); 126 | 127 | 128 | }); 129 | --------------------------------------------------------------------------------