├── .gitignore ├── src ├── semigroup.sjs ├── ap.sjs ├── kleisli.sjs └── do.sjs ├── test ├── semigroup.js ├── kleisli.js ├── ap.js └── do.js ├── README.md ├── package.json └── Gruntfile.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | bin 3 | build 4 | *~ 5 | -------------------------------------------------------------------------------- /src/semigroup.sjs: -------------------------------------------------------------------------------- 1 | macro $semigroup { 2 | case {_ ($x + $rest ...)} => { 3 | return #{ 4 | $x.concat($semigroup($rest ...)) 5 | }; 6 | } 7 | case {_ ($x)} => { 8 | return #{ 9 | $x 10 | }; 11 | } 12 | } -------------------------------------------------------------------------------- /test/semigroup.js: -------------------------------------------------------------------------------- 1 | var λ = require('fantasy-check/src/adapters/nodeunit'), 2 | Option = require('fantasy-options'); 3 | 4 | exports.semigroup = { 5 | 'concats values': λ.check( 6 | function(a, b) { 7 | var x = Option.of(a), 8 | y = Option.of(b), 9 | s = $semigroup (x + y); 10 | return s.x === a.concat(b); 11 | }, 12 | [String, String] 13 | ) 14 | }; 15 | -------------------------------------------------------------------------------- /src/ap.sjs: -------------------------------------------------------------------------------- 1 | /* 2 | $ap f(x, y, z) 3 | 4 | Desugared: f.ap(x).ap(y).ap(z) 5 | 6 | $ap (f(100))(x, y, z) 7 | 8 | Desugared: f(100).ap(x).ap(y).ap(z) 9 | */ 10 | macro $ap { 11 | case {_ $f:ident($x:expr (,) ...)} => { 12 | return #{ 13 | $f $(.ap($x)) ... 14 | }; 15 | } 16 | case {_ ($f:expr)($x:expr (,) ...)} => { 17 | return #{ 18 | $f $(.ap($x)) ... 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sweet Fantasies 2 | 3 | Library using [sweet.js](http://sweetjs.org/) to make macros 4 | that can be used with [fantasy-land](https://github.com/fantasyland). 5 | 6 | ## Installation 7 | 8 | Requires the installation of sweet.js on the command line. 9 | 10 | ``` 11 | npm install -g sweet.js 12 | ``` 13 | 14 | ## Build 15 | 16 | To run the project using [grunt](http://gruntjs.com/) just call 17 | the following: 18 | 19 | ``` 20 | grunt 21 | ``` 22 | 23 | [ 24 | ![](https://raw.github.com/fantasyland/fantasy-land/master/logo.png) 25 | ](https://github.com/fantasyland/fantasy-land) 26 | -------------------------------------------------------------------------------- /test/kleisli.js: -------------------------------------------------------------------------------- 1 | var λ = require('fantasy-check/src/adapters/nodeunit'), 2 | Identity = require('fantasy-identities'); 3 | 4 | function identityM(x) { 5 | return Identity.of(x); 6 | } 7 | 8 | exports.kleisli = { 9 | 'chain values': λ.check( 10 | function(a) { 11 | var s = $kleisli (Identity.of >=> identityM) > a; 12 | return s.x === a; 13 | }, 14 | [String, String] 15 | ), 16 | 'deferred chain values': λ.check( 17 | function(a) { 18 | var s = $kleisli (Identity.of >=> identityM), 19 | x = s(a); 20 | return x.x === a; 21 | }, 22 | [String, String] 23 | ) 24 | }; 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sweet-fantasies", 3 | "description": "sweet.js macros for Fantasy Land compatible structures.", 4 | "version": "0.0.2", 5 | "license": "MIT", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/pufuwozu/sweet-fantasies.git" 9 | }, 10 | "keywords": [ 11 | "fantasy land", 12 | "macros", 13 | "javascript" 14 | ], 15 | "scripts": { 16 | "test": "grunt" 17 | }, 18 | "devDependencies": { 19 | "grunt-cli": "0.1.9", 20 | "grunt": "0.4.x", 21 | "grunt-rigger": "0.5.x", 22 | "grunt-contrib-concat": "0.3.0", 23 | "grunt-contrib-jshint": "0.6.x", 24 | "grunt-contrib-nodeunit": "0.2.0", 25 | "shelljs": "0.2.x", 26 | "sweet.js": "0.2.x", 27 | "fantasy-check": "0.x.x", 28 | "fantasy-identities": "0.x.x", 29 | "fantasy-options": "0.x.x", 30 | "fantasy-combinators": "0.x.x" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/kleisli.sjs: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | $kleisli (x, y, z) > m 4 | 5 | Desugars into: 6 | 7 | m.chain(x).chain(y).chain(z) 8 | 9 | Partial applied becomes 10 | 11 | var a = $kleisli (x, y, z) 12 | a(m) 13 | 14 | Desugars into: 15 | 16 | var a = function(m) { 17 | return m.chain(a).chain(b).chain(c) 18 | } 19 | a(m) 20 | 21 | */ 22 | macro $kleisli { 23 | case {_ ($x:expr >=> $e ...) > $y:expr $rest ... } => { 24 | return #{ 25 | function(a) { 26 | return $x(a) $kleisli {$e ...} 27 | }($y) 28 | $rest ... 29 | } 30 | } 31 | case {_ ($x:expr >=> $e ...) $rest ... } => { 32 | return #{ 33 | function(a) { 34 | return $x(a) $kleisli {$e ...} 35 | } 36 | $rest ... 37 | } 38 | } 39 | case {_ {$x >=> $y ...}} => { 40 | return #{ 41 | .chain($x) $kleisli {$y ...} 42 | } 43 | } 44 | case {_ {$x}} => { 45 | return #{ 46 | .chain($x) 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /test/ap.js: -------------------------------------------------------------------------------- 1 | var λ = require('fantasy-check/src/adapters/nodeunit'), 2 | Identity = require('fantasy-identities'); 3 | 4 | exports.applicatives = { 5 | 'applies applicatives': λ.check( 6 | function(a, b) { 7 | var f = Identity.of(function(x) { return function(y) { return x+y }}), 8 | x = Identity.of(a), 9 | y = Identity.of(b), 10 | s = $ap f(x, y); 11 | return s.x === a + b; 12 | }, 13 | [Number, Number] 14 | ), 15 | 'supports nesting': λ.check( 16 | function(a, b) { 17 | var f = Identity.of(function(x) { return function(y) { return x+y }}), 18 | x = Identity.of(a), 19 | y = Identity.of(b), 20 | s = $ap f(x, $ap f(x, y)); 21 | return s.x === a + a + b; 22 | }, 23 | [String, String] 24 | ), 25 | 'supports inline creation of applicative': λ.check( 26 | function(a, b) { 27 | var x = Identity.of(a), 28 | y = Identity.of(b), 29 | s = $ap (Identity.of(function(x) { return function(y) { return x+y }}))(x, y); 30 | return s.x === a + b; 31 | }, 32 | [Number, Number] 33 | ) 34 | }; 35 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | var config = { 3 | pkg: grunt.file.readJSON('package.json'), 4 | jshint: { 5 | all: [ 6 | 'Gruntfile.js', 7 | 'bin/*.js' 8 | ] 9 | }, 10 | nodeunit: { 11 | all: [ 12 | 'bin/test.js' 13 | ] 14 | }, 15 | concat: { 16 | srcMacros: { 17 | src: ['src/*.sjs'], 18 | dest: 'bin/src.sjs' 19 | }, 20 | testMacros: { 21 | src: ['test/*.js', 'bin/src.sjs'], 22 | dest: 'bin/test.sjs' 23 | } 24 | }, 25 | macro: { 26 | all: { 27 | } 28 | }, 29 | clean: { 30 | all: { 31 | src: 'bin' 32 | } 33 | }, 34 | sweet: { 35 | all: { 36 | src: 'bin/test.sjs', 37 | dest: 'bin/test.js' 38 | } 39 | } 40 | }; 41 | 42 | grunt.initConfig(config); 43 | 44 | grunt.loadNpmTasks('grunt-contrib-concat'); 45 | grunt.loadNpmTasks('grunt-contrib-jshint'); 46 | grunt.loadNpmTasks('grunt-contrib-nodeunit'); 47 | 48 | grunt.registerMultiTask('macro', 'Run macro sweet.js unit tests with nodeunit.', function() { 49 | grunt.task.run([ 'clean', 50 | 'jshint', 51 | 'concat:srcMacros', 52 | 'concat:testMacros', 53 | 'sweet', 54 | 'nodeunit' 55 | ]); 56 | }); 57 | 58 | grunt.registerMultiTask('clean', 'Clean bin folder', function() { 59 | var shell = require('shelljs'), 60 | options = this.data; 61 | shell.rm('-rf', options.src); 62 | }); 63 | 64 | grunt.registerMultiTask('sweet', 'Run sweet.js', function() { 65 | var shell = require('shelljs'), 66 | options = this.data; 67 | shell.exec('sjs -o ' + options.dest + ' ' + options.src); 68 | }); 69 | 70 | grunt.registerTask('default', ['macro']); 71 | }; -------------------------------------------------------------------------------- /test/do.js: -------------------------------------------------------------------------------- 1 | var λ = require('fantasy-check/src/adapters/nodeunit'), 2 | Identity = require('fantasy-identities'); 3 | 4 | exports.donotation = { 5 | 'chains computations': λ.check( 6 | function(a, b) { 7 | var sum = $do { 8 | x <- Identity.of(a) 9 | y <- Identity.of(b) 10 | return x + y 11 | } 12 | return sum.x === a + b; 13 | }, 14 | [String, String] 15 | ), 16 | 'supports var-bindings': λ.check( 17 | function(a, b) { 18 | var sum = $do { 19 | x <- Identity.of(a) 20 | var k = 'do' 21 | y <- Identity.of(b) 22 | return x + y + k 23 | } 24 | return sum.x === a + b + 'do'; 25 | }, 26 | [String, String] 27 | ), 28 | 'supports var-bindings as tail 1': λ.check( 29 | function(a, b) { 30 | var sum = $do { 31 | x <- Identity.of(a) 32 | y <- Identity.of(b) 33 | var k = 'do' 34 | return x + y + k 35 | } 36 | return sum.x === a + b + 'do'; 37 | }, 38 | [String, String] 39 | ), 40 | 'supports var-bindings as tail 2': λ.check( 41 | function(a, b) { 42 | var sum = $do { 43 | x <- Identity.of(a) 44 | y <- $do { 45 | y <- Identity.of(b) 46 | return y 47 | } 48 | var k = 'do' 49 | return x + y + k 50 | } 51 | return sum.x === a + b + 'do'; 52 | }, 53 | [String, String] 54 | ), 55 | 'supports var-bindings as tail 3': λ.check( 56 | function(a, b) { 57 | var sum = $do { 58 | x <- Identity.of(a) 59 | y <- Identity.of(b) 60 | Identity.of(100) 61 | var k = 'do' 62 | return x + y + k 63 | } 64 | return sum.x === a + b + 'do'; 65 | }, 66 | [String, String] 67 | ), 68 | 'binding name is optional': λ.check( 69 | function(a, b) { 70 | var sum = $do { 71 | x <- Identity.of(a) 72 | Identity.of(100) 73 | y <- Identity.of(b) 74 | return x + y 75 | } 76 | return sum.x === a + b; 77 | }, 78 | [String, String] 79 | ), 80 | 'supports simple if-expressions': λ.check( 81 | function(a, b) { 82 | var sum = $do { 83 | x <- Identity.of(a) 84 | y <- Identity.of(b) 85 | var z = y 86 | if (x === a) return x + z else return '' 87 | } 88 | return sum.x === a + b; 89 | }, 90 | [String, String] 91 | ), 92 | 'supports simple if-else-expressions': λ.check( 93 | function(a, b) { 94 | var sum = $do { 95 | x <- Identity.of(a) 96 | y <- Identity.of(b) 97 | if (x !== a) return x + y else return '' 98 | } 99 | return sum.x === ''; 100 | }, 101 | [String, String] 102 | ), 103 | 'supports if-else-expressions': λ.check( 104 | function(a, b, c) { 105 | var sum = $do { 106 | x <- Identity.of(a) 107 | y <- Identity.of(b) 108 | var z = y 109 | if (x === a) $do { 110 | z <- Identity.of(c) 111 | return x + z 112 | } else $do { 113 | z <- Identity.of('') 114 | return x + z 115 | } 116 | } 117 | return sum.x === a + c; 118 | }, 119 | [String, String, String] 120 | ), 121 | 'supports return then if-else-expressions': λ.check( 122 | function(a, b, c) { 123 | var sum = $do { 124 | x <- Identity.of(a) 125 | y <- Identity.of(b) 126 | var z = y 127 | return if (x === a) $do { 128 | z <- Identity.of(c) 129 | return x + z 130 | } else $do { 131 | z <- Identity.of('') 132 | return x + z 133 | } 134 | } 135 | return sum.x.x === a + c; 136 | }, 137 | [String, String, String] 138 | ), 139 | 'supports if-expressions after non-bound expression': λ.check( 140 | function(a, b) { 141 | var sum = $do { 142 | x <- Identity.of(a) 143 | y <- Identity.of(b) 144 | Identity.of(100) 145 | if (x === a) return x + y else return '' 146 | } 147 | return sum.x === a + b; 148 | }, 149 | [String, String] 150 | ), 151 | 'supports return then if-expressions after non-bound expression': λ.check( 152 | function(a, b, c) { 153 | var sum = $do { 154 | x <- Identity.of(a) 155 | y <- Identity.of(b) 156 | return if (x === a) $do { 157 | z <- Identity.of(c) 158 | return x + z 159 | } else $do { 160 | z <- Identity.of('') 161 | return x + z 162 | } 163 | } 164 | return sum.x.x === a + c; 165 | }, 166 | [String, String, String] 167 | ), 168 | 'supports nested do-blocks': λ.check( 169 | function(a, b) { 170 | var sum = $do { 171 | x <- Identity.of(a) 172 | y <- $do { 173 | z <- Identity.of(b) 174 | return x + z 175 | } 176 | return x + y 177 | } 178 | return sum.x === a + a + b; 179 | }, 180 | [String, String] 181 | ), 182 | 'supports nested do-blocks': λ.check( 183 | function(a, b, c) { 184 | var sum = $do { 185 | x <- Identity.of(a) 186 | var z = 100 187 | y <- $do { 188 | z <- Identity.of(b) 189 | return x + z 190 | } 191 | z <- Identity.of(c) 192 | return x + y + z 193 | } 194 | return sum.x === a + a + b + c; 195 | }, 196 | [String, String, String] 197 | ) 198 | }; 199 | -------------------------------------------------------------------------------- /src/do.sjs: -------------------------------------------------------------------------------- 1 | /* 2 | $do { 3 | x <- foo 4 | y <- bar 5 | z <- baz 6 | return x * y * z 7 | } 8 | 9 | Desugars into: 10 | 11 | foo.chain(function(x) { 12 | return bar.chain(function(y) { 13 | return baz.map(function(z) { 14 | return x * y * z 15 | }) 16 | }) 17 | }) 18 | 19 | var-bindings are supported too: 20 | 21 | $do { 22 | x <- foo 23 | k = 10 24 | y <- bar(x) 25 | z <- baz 26 | return x * y * z * k 27 | } 28 | 29 | Variable binding is optional if monad is executed just for its effects: 30 | 31 | $do { 32 | putStrLn("Hello friend! What's your name?") 33 | name <- readLine() 34 | return name 35 | } 36 | 37 | TODO: 38 | 39 | - do not require last expression to be 'return' 40 | 41 | */ 42 | macro $do { 43 | case {_ {$a:ident <- $do { $doBlock ... } var $($x:ident = $y:expr) (var) ... return $b:expr }} => { 44 | return #{ 45 | function() { 46 | var ma = $do { $doBlock ... } 47 | return ma.map(function($a) { 48 | $(var $x = $y;) ... 49 | return $b 50 | }); 51 | }() 52 | }; 53 | } 54 | case {_ {$a:ident <- $do { $doBlock ... } return $b:expr }} => { 55 | return #{ 56 | function() { 57 | var ma = $do { $doBlock ... } 58 | return ma.map(function($a) { 59 | return $b 60 | }); 61 | }() 62 | }; 63 | } 64 | case {_ {$a:ident <- $do { $doBlock ... } $rest ... }} => { 65 | return #{ 66 | function() { 67 | var ma = $do { $doBlock ... } 68 | return ma.chain(function($a) { 69 | return $do { $rest ... } 70 | }); 71 | }() 72 | }; 73 | } 74 | case {_ {var $($x:ident = $y:expr) (var) ... $rest ... }} => { 75 | return #{ 76 | function() { 77 | $(var $x = $y;) ... 78 | return $do { $rest ... } 79 | }() 80 | }; 81 | } 82 | case {_ {$a:ident <- $ma:expr var $($x:ident = $y:expr) (var) ... return $b:expr }} => { 83 | return #{ 84 | $ma.map(function($a) { 85 | $(var $x = $y;) ... 86 | return $b; 87 | }); 88 | }; 89 | } 90 | case {_ {$a:ident <- $ma:expr return $b:expr }} => { 91 | return #{ 92 | $ma.map(function($a) { 93 | return $b; 94 | }); 95 | }; 96 | } 97 | case {_ {$a:ident <- $ma:expr var $($x:ident = $y:expr) (var) ... return if $rest ...}} => { 98 | return #{ 99 | $ma.map(function($a) { 100 | $(var $x = $y;) ... 101 | $ifelsedo { if $rest ... } 102 | }); 103 | }; 104 | } 105 | case {_ {$a:ident <- $ma:expr return if $rest ...}} => { 106 | return #{ 107 | $ma.map(function($a) { 108 | $ifelsedo { if $rest ... } 109 | }); 110 | }; 111 | } 112 | case {_ {$ma:expr var $($x:ident = $y:expr) (var) ... return $b:expr}} => { 113 | return #{ 114 | $ma.map(function() { 115 | $(var $x = $y;) ... 116 | return $b; 117 | }); 118 | }; 119 | } 120 | case {_ {$ma:expr return $b:expr}} => { 121 | return #{ 122 | $ma.map(function() { 123 | return $b; 124 | }); 125 | }; 126 | } 127 | case {_ {$a:ident <- $ma:expr var $($x:ident = $y:expr) (var) ... if $e:expr return $left:expr else return $right:expr }} => { 128 | return #{ 129 | $ma.map(function($a) { 130 | $(var $x = $y;) ... 131 | if ($e) { 132 | return $left 133 | } else { 134 | return $right 135 | } 136 | }); 137 | }; 138 | } 139 | case {_ {$a:ident <- $ma:expr if $e:expr return $left:expr else return $right:expr }} => { 140 | return #{ 141 | $ma.map(function($a) { 142 | if ($e) { 143 | return $left 144 | } else { 145 | return $right 146 | } 147 | }); 148 | }; 149 | } 150 | case {_ { $ma:expr var $($x:ident = $y:expr) (var) ... if $e:expr return $left:expr else return $right:expr }} => { 151 | return #{ 152 | $ma.map(function() { 153 | $(var $x = $y;) ... 154 | if ($e) { 155 | return $left 156 | } else { 157 | return $right 158 | } 159 | }); 160 | }; 161 | } 162 | case {_ {$ma:expr if $e:expr return $left:expr else return $right:expr }} => { 163 | return #{ 164 | $ma.map(function() { 165 | if ($e) { 166 | return $left 167 | } else { 168 | return $right 169 | } 170 | }); 171 | }; 172 | } 173 | case {_ {$a:ident <- $ma:expr var $($x:ident = $y:expr) (var) ... if $rest ... }} => { 174 | return #{ 175 | $ma.chain(function($a) { 176 | $(var $x = $y;) ... 177 | $ifelsedo { if $rest ... } 178 | }); 179 | }; 180 | } 181 | case {_ {$a:ident <- $ma:expr if $rest ... }} => { 182 | return #{ 183 | $ma.chain(function($a) { 184 | $ifelsedo { if $rest ... } 185 | }); 186 | }; 187 | } 188 | case {_ {$ma:expr var $($x:ident = $y:expr) (var) ... if $rest ... }} => { 189 | return #{ 190 | $ma.chain(function() { 191 | $(var $x = $y;) ... 192 | $ifelsedo { if $rest ... } 193 | }); 194 | }; 195 | } 196 | case {_ {$ma:expr if $rest ... }} => { 197 | return #{ 198 | $ma.chain(function() { 199 | $ifelsedo { if $rest ... } 200 | }); 201 | }; 202 | } 203 | case {_ {$a:ident <- $ma:expr $rest ... }} => { 204 | return #{ 205 | $ma.chain(function($a) { 206 | return $do { $rest ... } 207 | }); 208 | }; 209 | } 210 | case {_ {$ma:expr $rest ... }} => { 211 | return #{ 212 | $ma.chain(function() { 213 | return $do { $rest ... } 214 | }); 215 | }; 216 | } 217 | } 218 | 219 | /* 220 | $do { 221 | a <- foo 222 | if (a == 1) $do { 223 | b <- bar 224 | return b 225 | } else $do { 226 | c <- quux 227 | return c 228 | } 229 | } 230 | 231 | foo.map(function (a$2) { 232 | if (a$2 == 1) { 233 | return bar.map(function (b$6) { 234 | return b$6; 235 | }); 236 | } else { 237 | return quux.map(function (c$9) { 238 | return c$9; 239 | }); 240 | } 241 | }); 242 | 243 | */ 244 | macro $ifelsedo { 245 | case {_ { if $e:expr $do { $left ... } else return $right:expr }} => { 246 | return #{ 247 | if ($e) { 248 | return $do { $left ... } 249 | } else { 250 | return $right 251 | } 252 | }; 253 | } 254 | case {_ { if $e:expr return $left:expr else $do { $right ... } }} => { 255 | return #{ 256 | if ($e) { 257 | return $left 258 | } else { 259 | return $do { $right ... } 260 | } 261 | }; 262 | } 263 | case {_ { if $e:expr $do { $left ... } else $do { $right ... } }} => { 264 | return #{ 265 | if ($e) { 266 | return $do { $left ... } 267 | } else { 268 | return $do { $right ... } 269 | } 270 | }; 271 | } 272 | case {_ { if $e:expr return $left:expr else $rest ... }} => { 273 | return #{ 274 | if ($e) { 275 | return $left 276 | } else $ifelsedo { $rest ... } 277 | }; 278 | } 279 | case {_ { if $e:expr $do { $left ... } else $rest ... }} => { 280 | return #{ 281 | if ($e) { 282 | return $do { $left ... } 283 | } else $ifelsedo { $rest ... } 284 | }; 285 | } 286 | } 287 | --------------------------------------------------------------------------------