├── .babelrc ├── .github ├── dependabot.yml └── workflows │ └── codeql-analysis.yml ├── .gitignore ├── .mocharc.yaml ├── .travis.yml ├── LICENSE ├── README.md ├── _helpers └── reusables.js ├── build-custom.sh ├── fp.js ├── index.js ├── maybe.js ├── monadio.js ├── package-lock.json ├── package.json ├── pattern.js ├── publisher.js ├── test ├── fp.js ├── maybe.js ├── monadio.js ├── pattern.js └── publisher.js ├── webpack.config.js └── webpack.custom.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", { 5 | "modules": "commonjs" 6 | } 7 | ] 8 | ], 9 | "plugins": [ 10 | ["@babel/transform-runtime", { 11 | "regenerator": true 12 | }] 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "npm" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '38 4 * * 2' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 37 | # Learn more: 38 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 39 | 40 | steps: 41 | - name: Checkout repository 42 | uses: actions/checkout@v2 43 | 44 | # Initializes the CodeQL tools for scanning. 45 | - name: Initialize CodeQL 46 | uses: github/codeql-action/init@v1 47 | with: 48 | languages: ${{ matrix.language }} 49 | # If you wish to specify custom queries, you can do so here or in a config file. 50 | # By default, queries listed here will override any specified in a config file. 51 | # Prefix the list here with "+" to use these queries and those in the config file. 52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 53 | 54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 55 | # If this step fails, then you should remove it and run the build manually (see below) 56 | - name: Autobuild 57 | uses: github/codeql-action/autobuild@v1 58 | 59 | # ℹ️ Command-line programs to run using the OS shell. 60 | # 📚 https://git.io/JvXDl 61 | 62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 63 | # and modify them (or add more) to build your code if your project 64 | # uses a compiled language 65 | 66 | #- run: | 67 | # make bootstrap 68 | # make release 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@v1 72 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Custom 2 | 3 | dist/*.js* 4 | !/dist/*.min.js* 5 | 6 | 7 | 8 | # Logs 9 | logs 10 | *.log 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | 15 | # Runtime data 16 | pids 17 | *.pid 18 | *.seed 19 | *.pid.lock 20 | 21 | # Directory for instrumented libs generated by jscoverage/JSCover 22 | lib-cov 23 | 24 | # Coverage directory used by tools like istanbul 25 | coverage 26 | 27 | # nyc test coverage 28 | .nyc_output 29 | 30 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 31 | .grunt 32 | 33 | # Bower dependency directory (https://bower.io/) 34 | bower_components 35 | 36 | # node-waf configuration 37 | .lock-wscript 38 | 39 | # Compiled binary addons (https://nodejs.org/api/addons.html) 40 | build/Release 41 | 42 | # Dependency directories 43 | node_modules/ 44 | jspm_packages/ 45 | 46 | # TypeScript v1 declaration files 47 | typings/ 48 | 49 | # Optional npm cache directory 50 | .npm 51 | 52 | # Optional eslint cache 53 | .eslintcache 54 | 55 | # Optional REPL history 56 | .node_repl_history 57 | 58 | # Output of 'npm pack' 59 | *.tgz 60 | 61 | # Yarn Integrity file 62 | .yarn-integrity 63 | 64 | # dotenv environment variables file 65 | .env 66 | 67 | # next.js build output 68 | .next 69 | -------------------------------------------------------------------------------- /.mocharc.yaml: -------------------------------------------------------------------------------- 1 | require: ['@babel/register', 'should'] 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | 2 | language: node_js 3 | 4 | node_js: 5 | - "node" 6 | - "10" 7 | - "11" 8 | - "12" 9 | - "13" 10 | - "14" 11 | - "15" 12 | 13 | before_install: 14 | - npm install -g nyc 15 | - npm install -g codecov 16 | scripts: 17 | - nyc --reporter=lcov mocha && codecov 18 | # - istanbul cover ./node_modules/mocha/bin/_mocha --reporter lcovonly -- -R spec 19 | # - codecov 20 | # after_success: 21 | 22 | 23 | cache: 24 | directories: 25 | - "node_modules" 26 | 27 | matrix: 28 | fast_finish: true 29 | include: 30 | # Master channel. 31 | # All *nix releases are done on the Master channel to take advantage 32 | # of the regex library's multiple pattern SIMD search. 33 | - os: windows 34 | node_js: "node" 35 | - os: linux 36 | node_js: "node" 37 | - os: osx 38 | node_js: "node" 39 | 40 | notifications: 41 | email: 42 | on_success: never 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 TeaEntityLab 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 | # fpEs 2 | 3 | [![npm download](https://img.shields.io/npm/dt/fpes.svg)](https://www.npmjs.com/package/fpes) 4 | [![npm version](https://img.shields.io/npm/v/fpes.svg)](https://www.npmjs.com/package/fpes) 5 | [![codecov](https://codecov.io/gh/TeaEntityLab/fpEs/branch/master/graph/badge.svg)](https://codecov.io/gh/TeaEntityLab/fpEs) 6 | [![Travis CI Build Status](https://travis-ci.com/TeaEntityLab/fpEs.svg?branch=master)](https://travis-ci.com/TeaEntityLab/fpEs) 7 | 8 | [![license](https://img.shields.io/github/license/TeaEntityLab/fpEs.svg?style=social&label=License)](https://github.com/TeaEntityLab/fpEs) 9 | [![stars](https://img.shields.io/github/stars/TeaEntityLab/fpEs.svg?style=social&label=Stars)](https://github.com/TeaEntityLab/fpEs) 10 | [![forks](https://img.shields.io/github/forks/TeaEntityLab/fpEs.svg?style=social&label=Fork)](https://github.com/TeaEntityLab/fpEs) 11 | 12 | 13 | Functional Programming for EcmaScript(Javascript) 14 | 15 | # Why 16 | 17 | Originally I would like to have some features of Optional & Rx-like & PubSub functions; 18 | 19 | however somehow that's too heavy if including them at the same time. 20 | 21 | Thus the implementation just includes the core functions, and more clear to use. 22 | 23 | ## Special thanks: 24 | * [purify](https://github.com/gigobyte/purify) (Inspiration from parts of Maybe implementations for [Fantasy-Land Specs](https://github.com/fantasyland/fantasy-land)) 25 | 26 | # Installation 27 | 28 | ## Node.js 29 | 30 | node >= 6.0 31 | 32 | * Installation: 33 | 34 | ```bash 35 | npm i fpes 36 | ``` 37 | 38 | ## Browser 39 | 40 | bundled files for web/browser usages: 41 | 42 | [all](https://unpkg.com/fpes/dist/bundle.min.js) 43 | 44 | --- 45 | 46 | [fp](https://unpkg.com/fpes/dist/fp.min.js) 47 | 48 | [maybe](https://unpkg.com/fpes/dist/maybe.min.js) 49 | 50 | [monadio](https://unpkg.com/fpes/dist/monadio.min.js) 51 | 52 | [pattern](https://unpkg.com/fpes/dist/pattern.min.js) 53 | 54 | [publisher](https://unpkg.com/fpes/dist/publisher.min.js) 55 | 56 | # Usage 57 | 58 | ## Import 59 | 60 | * You can include the entire library: 61 | 62 | ```javascript 63 | import fpEs from 'fpEs'; 64 | ``` 65 | 66 | * There are 5 modules in this library, you can include them individually: 67 | * Facades: 68 | * maybe 69 | * monadio 70 | * publisher 71 | * FP functions: 72 | * fp 73 | * pattern 74 | 75 | Just include things you need: 76 | 77 | ```javascript 78 | import {Maybe} from "fpEs"; 79 | // or this one: 80 | /* 81 | import Maybe from "fpEs/maybe"; 82 | */ 83 | 84 | var m = Maybe.just(1); // It works 85 | ``` 86 | 87 | or 88 | 89 | ```javascript 90 | import { 91 | compose, curry, 92 | } from "fpEs"; 93 | ``` 94 | 95 | or 96 | 97 | ```javascript 98 | import { 99 | compose, curry, 100 | } from "fpEs/fp"; 101 | ``` 102 | 103 | ## Common FP (Compose, Curry) 104 | 105 | Example: 106 | 107 | ```javascript 108 | 109 | import { 110 | compose, curry, 111 | } from "fpEs/fp"; 112 | 113 | // compose 114 | 115 | console.log(compose((x)=>x-8, (x)=>x+10, (x)=>x*10)(4)) // 42 116 | console.log(compose((x)=>x+2, (x,y)=>x*y)(4,10)) // 42 117 | 118 | // curry 119 | 120 | console.log(curry((x, y, z) => x + y + z)(1,2,3)) // 6 121 | console.log(curry((x, y, z) => x + y + z)(1)(2,3)) // 6 122 | console.log(curry((x, y, z) => x + y + z)(1,2)(3)) // 6 123 | console.log(curry((x, y, z) => x + y + z)(1)(2)(3)) // 6 124 | 125 | ``` 126 | 127 | ## PatternMatching 128 | 129 | Example: 130 | 131 | ```javascript 132 | 133 | import { 134 | either, 135 | inCaseOfObject, inCaseOfEqual, inCaseOfClass, otherwise, 136 | 137 | SumType, ProductType, CompType, 138 | TypeNumber, 139 | TypeString, 140 | TypeNaN, 141 | TypeObject, 142 | TypeArray, 143 | TypeNull, 144 | TypeEqualTo, 145 | TypeClassOf, 146 | TypeRegexMatches, 147 | } from "fpEs/pattern"; 148 | 149 | // PatternMatching 150 | 151 | console.log(either({}, inCaseOfObject((x)=>JSON.stringify(x)), otherwise((x)=>false))); // "{}" 152 | console.log(either([], inCaseOfObject((x)=>JSON.stringify(x)), otherwise((x)=>false))); // false 153 | console.log(either(null, inCaseOfObject((x)=>JSON.stringify(x)), otherwise((x)=>false))); // false 154 | console.log(either(undefined, inCaseOfObject((x)=>JSON.stringify(x)), otherwise((x)=>false))); // false 155 | console.log(either("", inCaseOfObject((x)=>JSON.stringify(x)), otherwise((x)=>false))); // false 156 | 157 | // otherwise 158 | 159 | var err = undefined; 160 | 161 | err = undefined; 162 | either(1, inCaseOfEqual(1, (x)=>x+1)).should.equal(2); 163 | (err === undefined).should.equal(true); 164 | 165 | err = undefined; 166 | try { 167 | either(1, inCaseOfEqual(2, (x)=>x+1)); 168 | } catch (e) { 169 | err = e; 170 | } 171 | (err === undefined).should.equal(false); 172 | 173 | err = undefined; 174 | try { 175 | either(1, inCaseOfEqual(2, (x)=>x+1), otherwise((x)=>x+2)).should.equal(3); 176 | } catch (e) { 177 | err = e; 178 | console.log(e); 179 | } 180 | (err === undefined).should.equal(true); 181 | 182 | // SumType 183 | 184 | var s; 185 | s = new SumType(new ProductType(TypeString, TypeNumber), new ProductType(TypeRegexMatches('c+'))); 186 | console.log(s.apply("1", "2asdf") === undefined); // true 187 | console.log(s.apply("1", 2) === undefined); // false 188 | 189 | console.log(s.apply("1") === undefined); // true 190 | console.log(s.apply("ccc") === undefined); // false 191 | 192 | ``` 193 | 194 | ## Maybe (Sync) 195 | 196 | Example: 197 | 198 | ```javascript 199 | 200 | import Maybe from "fpEs/maybe"; 201 | 202 | var m; 203 | 204 | // map (sync) 205 | 206 | m = Maybe.just(1).map((x)=>x+2).map((x)=>x+3); 207 | console.log(m.unwrap()); // 6 208 | 209 | // isPresent/isNull 210 | 211 | m = Maybe.just(1); 212 | console.log(m.isPresent()); // true 213 | console.log(m.isNull()); // false 214 | m = Maybe.just(null); 215 | console.log(m.isPresent()); // false 216 | console.log(m.isNull()); // true 217 | m = Maybe.just(undefined); 218 | console.log(m.isPresent()); // false 219 | console.log(m.isNull()); // true 220 | 221 | // Or 222 | 223 | m = Maybe.just(1); 224 | console.log(m.or(3).unwrap()); // 1 225 | console.log(m.or(4).unwrap()); // 1 226 | m = Maybe.just(null); 227 | console.log(m.or(3).unwrap()); // 3 228 | console.log(m.or(4).unwrap()); // 4 229 | m = Maybe.just(undefined); 230 | console.log(m.or(3).unwrap()); // 3 231 | console.log(m.or(4).unwrap()); // 4 232 | 233 | // letDo 234 | 235 | m = Maybe.just(1); 236 | v = 0; 237 | m.letDo(function () { 238 | v = 1; 239 | }); 240 | console.log(v); // 1 241 | m = Maybe.just(null); 242 | v = 0; 243 | m.letDo(function () { 244 | v = 1; 245 | }); 246 | console.log(v); // 0 247 | m = Maybe.just(undefined); 248 | v = 0; 249 | m.letDo(function () { 250 | v = 1; 251 | }); 252 | console.log(v); // 0 253 | 254 | // letDo & orDo 255 | m = Maybe.just(0); 256 | v = m.letDo(function (p) { 257 | return p + 2 258 | }).orDo(function () { 259 | return 3 260 | }).unwrap(); 261 | console.log(v); // 2 262 | m = Maybe.just(undefined); 263 | v = m.letDo(function (p) { 264 | return p + 2 265 | }).orDo(function () { 266 | return 3 267 | }).unwrap(); 268 | console.log(v); // 3 269 | 270 | ``` 271 | 272 | ## MonadIO/Rx.Observable (Async,Sync) 273 | 274 | Example: 275 | 276 | ```javascript 277 | 278 | import Maybe from "fpEs/maybe"; 279 | import MonadIO from "fpEs/monadio"; 280 | var {promiseof, doM} = MonadIO; 281 | 282 | 283 | 284 | var p = undefined; 285 | var m = MonadIO.just(0); 286 | var v = 0; 287 | 288 | // sync 289 | 290 | m = MonadIO.just(0); 291 | v = 0; 292 | m 293 | .map((val)=>val+1) 294 | .map((val)=>val+2) 295 | .flatMap((val)=>MonadIO.just(val+1).map((val)=>val+1).map((val)=>val+1)) 296 | .subscribe((val)=>v=val); 297 | 298 | console.log(v); // 6 299 | 300 | // async 301 | 302 | m = MonadIO.just(0); 303 | v = 0; 304 | p = m 305 | .map((val)=>val+1) 306 | .map((val)=>val+2) 307 | .map((val)=>val+3) 308 | .subscribe((val)=>v=val, true); // Async: true 309 | 310 | console.log(v); // 0 311 | p.then(function () { 312 | console.log(v); // 6 313 | }); 314 | 315 | // DoNotation 316 | 317 | v = 0; 318 | p = doM(function *() { 319 | var value = yield promiseof(5); 320 | var value2 = yield promiseof(11); 321 | var value3 = yield Maybe.just(3); 322 | var value4 = yield MonadIO.just(3); 323 | return value + value2 + value3 + value4; 324 | }); 325 | 326 | p.then((x)=>console.log(x)); // 22 327 | 328 | ``` 329 | 330 | ## Publisher(PubSub-like) 331 | 332 | Example: 333 | 334 | ```javascript 335 | 336 | import Publisher from "fpEs/publisher"; 337 | 338 | var p = new Publisher(); 339 | var v = 0; 340 | 341 | // sync 342 | 343 | p = new Publisher(); 344 | v = 0; 345 | p.subscribe((i)=>v=i); 346 | p.publish(1); 347 | 348 | console.log(v); // 1 349 | 350 | // async 351 | 352 | p = new Publisher(); 353 | v = 0; 354 | 355 | p.subscribe((i)=>v=i); 356 | p.publish(1, true); // Async: true 357 | console.log(v); // 0 358 | 359 | setTimeout(()=>{ 360 | console.log(v); // 1 361 | },100); 362 | 363 | // map 364 | 365 | p = new Publisher(); 366 | v = 0; 367 | 368 | p.map((x)=>x+2).map((x)=>x+3).subscribe((i)=>v=i); 369 | p.publish(1, true); 370 | console.log(v); // 0 371 | 372 | setTimeout(()=>{ 373 | console.log(v); // 6 374 | },100); 375 | 376 | // unsubscribe 377 | 378 | p = new Publisher(); 379 | v = 0; 380 | var callback = (i)=>v=i; 381 | 382 | p.subscribe(callback); 383 | p.publish(1); 384 | console.log(v); // 1 385 | v = 0; 386 | p.unsubscribe(callback); 387 | p.publish(1); 388 | console.log(v); // 0 389 | 390 | ``` 391 | -------------------------------------------------------------------------------- /_helpers/reusables.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | getMainAndFollower: function(values) { 3 | let lastButOneValue = (+values[values.length-2])-1; 4 | let lastButOne = lastButOneValue >=0 ? lastButOneValue : 0; 5 | 6 | let lastValue = (+values[values.length-1])-1; 7 | let lastOne = lastValue >=0 ? lastValue : 1; 8 | 9 | let main = values[lastButOne]; 10 | let follower = values[lastOne]; 11 | 12 | return {main, follower} 13 | }, 14 | findArrayEntry: function(f, list) { 15 | for (let entry of list.entries()) { 16 | if (f(entry[1])) { 17 | return entry; 18 | } 19 | } 20 | }, 21 | findLastArrayEntry: function(f, list) { 22 | for (var i = list.length - 1; i >= 0; i--) { 23 | if (f(list[i])) { 24 | return [i, list[i]]; 25 | } 26 | } 27 | }, 28 | sorter: function (list, value, indexOfPosition = "first") { 29 | let listValueConcat = list.concat([value]); 30 | if (typeof value == "number") { 31 | if (indexOfPosition == "first") 32 | return listValueConcat.sort((a, b) => a - b).indexOf(value); 33 | return listValueConcat.sort((a, b) => a - b).lastIndexOf(value); 34 | } 35 | if (indexOfPosition == "first") 36 | return listValueConcat.sort().indexOf(value); 37 | return listValueConcat.sort().lastIndexOf(value); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /build-custom.sh: -------------------------------------------------------------------------------- 1 | for fullpath in ./*.js 2 | do 3 | 4 | if [[ $fullpath = *"index"* ]]; then 5 | continue 6 | fi 7 | if [[ $fullpath = *"config.js"* ]]; then 8 | continue 9 | fi 10 | 11 | npm run build-custom -- --env filename=./$fullpath 12 | 13 | filename="${fullpath##*/}" # Strip longest match of */ from start 14 | dir="${fullpath:0:${#fullpath} - ${#filename}}" # Substring from 0 thru pos of filename 15 | base="${filename%.[^.]*}" # Strip shortest match of . plus at least one non-dot char from end 16 | ext="${filename:${#base} + 1}" # Substring from len of base thru end 17 | if [[ -z "$base" && -n "$ext" ]]; then # If we have an extension and no base, it's really the base 18 | base=".$ext" 19 | ext="" 20 | fi 21 | 22 | done 23 | -------------------------------------------------------------------------------- /fp.js: -------------------------------------------------------------------------------- 1 | const reuseables = require("./_helpers/reusables"); 2 | 3 | function compose(...fns) { 4 | return fns.reduce(function (fn, g) {return function (...args) {return fn(g(...args))}}) 5 | }; 6 | function curry(fn) { 7 | return function (...xs) { 8 | if (xs.length === 0) { 9 | throw Error('EMPTY INVOCATION'); 10 | } 11 | if (xs.length >= fn.length) { 12 | return fn(...xs); 13 | } 14 | return curry(fn.bind(null, ...xs)); 15 | }; 16 | } 17 | 18 | function contains (list, value){ 19 | if (arguments.length == 1) { 20 | // Manually currying 21 | value = list; 22 | return (list) => contains(list, value); 23 | } 24 | 25 | return list.reduce((accum, currentValue)=>{ 26 | return accum ? true : currentValue === value; 27 | }, false) 28 | } 29 | 30 | function difference(...values) { 31 | let {main, follower} = reuseables.getMainAndFollower(values); 32 | 33 | let concatWithoutDuplicate = [...new Set(main.concat(follower))]; 34 | 35 | return Array.prototype.slice.call(concatWithoutDuplicate, main.length, concatWithoutDuplicate.length) 36 | } 37 | 38 | function differenceWithDup (...values) { 39 | let {main, follower} = reuseables.getMainAndFollower(values); 40 | 41 | return follower.filter(x=> { 42 | return !(contains(main,x)); 43 | }) 44 | } 45 | 46 | function zip(...list) { 47 | let result = []; 48 | for(let i=0; ix[i]); 50 | return result; 51 | } 52 | 53 | var reduce = curry(function (fn, init, ...second) { 54 | // console.log(arguments); 55 | var list; 56 | if (arguments.length < 3) { 57 | if (Array.isArray(init)) { 58 | // Simple reduce 59 | return init.reduce(fn); 60 | } else { 61 | // Pass this round, currying it (manual currying) 62 | return function (list) {return reduce(fn, init, list)} 63 | } 64 | } else { 65 | list = second[0]; 66 | }; 67 | return list.reduce(fn, init) 68 | }); 69 | var foldl = curry(function (fn, init, list) {return list.reduce(fn, init)}); 70 | function flatten(list) { 71 | return list.reduce(function (flat, toFlatten) { 72 | return flat.concat(Array.isArray(toFlatten) ? flatten(toFlatten) : toFlatten); 73 | }, []); 74 | } 75 | var map = curry(function (fn, list) {return list.map(fn)}); 76 | var reverse = function (list) {return typeof list === 'string' ? list.split('').reverse().join('') : Array.prototype.slice.call(list, 0).reverse()}; 77 | var prop = curry(function (prop, obj) {return obj[prop]}); 78 | var ifelse = curry(function(test, elsef, fn) {return test() ? fn() : elsef()}); 79 | 80 | function concat(list,...values) { 81 | if (values.length == 0) { 82 | // Manually curry it. 83 | return function (...values) {return concat(list,...values)} 84 | } 85 | let lastValue = values[values.length-1]; 86 | if(typeof lastValue === "function") { 87 | let excludeLast = values.slice(0,values.length-1); 88 | return (excludeLast 89 | .reduce((prev,next)=>(prev.concat(next)),list)) 90 | .filter(lastValue); 91 | } 92 | return values.reduce((prev,next)=>(prev.concat(next)),list); 93 | } 94 | 95 | module.exports = { 96 | compose : compose, 97 | pipe: function (...fns) {return compose(...fns.reverse())}, 98 | 99 | curry, 100 | chunk: curry(function (list, chunk_size) {return Array(Math.ceil(list.length / chunk_size)).fill().map(function (_, index) {return index * chunk_size}).map(function (begin) {return Array.prototype.slice.call(list, begin, begin + chunk_size)})}), 101 | range: function(n) { 102 | return Array.apply(null,Array(n)).map(function (x,i) {return i}) 103 | }, 104 | snooze: ms => new Promise(resolve => setTimeout(resolve, ms)), 105 | debounce: curry(function (fn, timeout) { 106 | var ref = setTimeout(fn, timeout) 107 | return { 108 | ref, 109 | cancel: function () {return clearTimeout(ref)}, 110 | } 111 | }), 112 | schedule: curry(function (fn, interval) { 113 | var ref = setInterval(fn, interval) 114 | return { 115 | ref, 116 | cancel: function () {return clearInterval(ref)}, 117 | } 118 | }), 119 | 120 | map, 121 | reduce, 122 | foldl, 123 | foldr: curry(function (fn, init, list) {return list.reduceRight(fn, init)}), 124 | filter: curry(function (fn, list) {return list.filter(fn)}), 125 | flattenMap: curry(function (fn, list) {return compose(flatten, map)(fn, list)}), 126 | 127 | ifelse, 128 | unary: curry(function (fn, arg) {return fn(arg)}), 129 | not: curry(function (fn, ...args) {return !fn(...args)}), 130 | spread: curry(function (fn, args) {return fn(...args)}), 131 | gather: curry(function (fn, ...args) {return fn(args)}), 132 | partial: curry(function (fn, ...presetArgs) {return function (...laterArgs) {return fn(...presetArgs, ...laterArgs)}}), 133 | partialRight: curry(function (fn, ...presetArgs) {return function (...laterArgs) {return fn(...laterArgs, ...presetArgs)}}), 134 | partialProps: curry(function(fn,presetArgsObj, laterArgsObj) {return fn(Object.assign( {}, presetArgsObj, laterArgsObj))}), 135 | when: curry(function(test, fn) {return ifelse(test, function(){return undefined}, fn)}), 136 | trampoline: function (fn) { 137 | return function (...args){ 138 | var result = fn( ...args ) 139 | while (typeof result === "function") { 140 | result = result() 141 | } 142 | return result 143 | } 144 | }, 145 | 146 | flatten, 147 | reverse, 148 | unique: function (list) {return list.filter(function (val, i, list) {return list.indexOf(val) === i})}, 149 | tail: function (list) {return list.length > 0 ? Array.prototype.slice.call(list, 1) : list}, 150 | shift: function (list) {return Array.prototype.slice.call(list, 0).shift()}, 151 | take: curry(function take(n, list) { 152 | if (n > 0 && list.length > 0) { 153 | var val = list.shift(); 154 | return [].concat(val, take(n - 1, Array.prototype.slice.call(list, 0))) 155 | } 156 | return []; 157 | }), 158 | 159 | prop, 160 | propEq: curry(function (val, p, obj) {return prop(p)(obj) === val}), 161 | get: curry(function (obj, p) {return prop(p, obj)}), 162 | matches: curry(function (rule, obj) { 163 | for(var k in rule) { 164 | if ((!obj.hasOwnProperty(k))||obj[k]!==rule[k]) {return false} 165 | } 166 | return true; 167 | }), 168 | memoize: function (fn) { 169 | var memo = {}; 170 | 171 | return function() { 172 | var args = Array.prototype.slice.call(arguments); 173 | 174 | if (args in memo) { 175 | return memo[args]; 176 | } 177 | return (memo[args] = fn.apply(this, args)); 178 | }; 179 | }, 180 | clone: function (obj) { 181 | if (obj === undefined || obj === NaN) { 182 | return obj; 183 | } 184 | return JSON.parse(JSON.stringify(obj)) 185 | }, 186 | 187 | /** 188 | * Returns truthy values from an array. 189 | * When typ is supplied, returns new array of specified type 190 | */ 191 | compact: function compact(list,typ) { 192 | if(arguments.length === 1) { 193 | if (Array.isArray(list)) { 194 | // if the only one param is an array 195 | return list.filter(x=>x); 196 | } else { 197 | // Curry it manually 198 | typ = list; 199 | return function (list) {return compact(list, typ)}; 200 | } 201 | } 202 | return list.filter(x=> typeof x === typeof typ); 203 | }, 204 | /** 205 | * Concats arrays. 206 | * Concats arrays using provided function 207 | */ 208 | concat: concat, 209 | /** 210 | * Returns true if value specified in present in array. 211 | * @param list {Array} array to be looped 212 | * @param value array to check for in array 213 | * @returns boolean 214 | */ 215 | contains: contains, 216 | /** 217 | * Compares two arrays, first one as main and second 218 | as follower. Returns values in follower that aren't in main 219 | excluding duplicate values in follower 220 | * @param 1st Any number of individual arrays 221 | * @param 2nd {number} Position number of array to be used as main 222 | * @param 3rd {number} Position number of to be used as follower 223 | * @return {Array} of values in follower but not in main without duplicate 224 | */ 225 | difference: difference, 226 | /** 227 | * Compares two arrays, first one as main and second 228 | as follower. Returns values in follower that aren't in main 229 | including duplicate values in follower. 230 | * @param 1st Any number of individual arrays 231 | * @param 2nd {number} Position number of array to be used as main 232 | * @param 3rd {number} Position number of to be used as follower 233 | * @return {Array} of values in follower but not in main including duplicate 234 | */ 235 | differenceWithDup: differenceWithDup, 236 | /** 237 | * Drops specified number of values from array either through left or right. 238 | * Uses passed in function to filter remaining array after values dropped. 239 | * Default dropCount = 1 240 | */ 241 | drop: function drop(list,dropCount=1,direction="left",fn=null) { 242 | 243 | // If the first argument is not kind of `array`-like. 244 | if (!(list && Array.isArray(list))) { 245 | // Manually currying 246 | let args = arguments; 247 | return (list) => drop(list, ...args); 248 | } 249 | 250 | if(dropCount === 0 && !fn) { 251 | return Array.prototype.slice.call(list, 0) 252 | }; 253 | 254 | if(arguments.length === 1 || direction === "left") { 255 | if (!fn) { 256 | return Array.prototype.slice.call(list, +dropCount); 257 | } 258 | 259 | return (Array.prototype.slice.call(list, +dropCount)).filter(x=>fn(x)); 260 | } 261 | if(direction === "right"){ 262 | if(!fn) { 263 | return Array.prototype.slice.call(list, 0, list.length-(+dropCount)); 264 | } 265 | if(dropCount === 0) {return (Array.prototype.slice.call(list, 0)).filter(x=>fn(x))}; 266 | 267 | return (Array.prototype.slice.call(list, 0, list.length-(+dropCount))).filter(x=>fn(x)); 268 | } 269 | }, 270 | /** 271 | * Fills array using specified values. 272 | * Can optionally pass in start and index of array to fill. 273 | * Default startIndex = 0. Default endIndex = length of array. 274 | */ 275 | fill: function fill(list, value, startIndex=0, endIndex=list.length){ 276 | if (!(list && Array.isArray(list))) { 277 | // Manually currying 278 | let args = arguments; 279 | return (list) => fill(list, ...args); 280 | } 281 | 282 | return Array(...list).map((x,i)=> { 283 | if(i>= startIndex && i <= endIndex) { 284 | return x=value; 285 | } else { 286 | return x; 287 | } 288 | }); 289 | }, 290 | /** 291 | * Returns first element for which function 292 | returns true 293 | */ 294 | find: curry(function(fn, list){ 295 | let entry = reuseables.findArrayEntry(fn, list); 296 | if (entry) { 297 | return entry[1]; 298 | } 299 | }), 300 | /** 301 | * Returns index of first element for which function 302 | returns true 303 | */ 304 | findIndex: curry(function(fn, list){ 305 | let entry = reuseables.findArrayEntry(fn, list); 306 | if (entry) { 307 | return entry[0]; 308 | } 309 | 310 | return -1; 311 | }), 312 | /** 313 | * Returns last element for which function 314 | returns true 315 | */ 316 | findLast: curry(function(fn, list){ 317 | let entry = reuseables.findLastArrayEntry(fn, list); 318 | if (entry) { 319 | return entry[1]; 320 | } 321 | }), 322 | /** 323 | * Returns index of last element for which function 324 | returns true 325 | */ 326 | findLastIndex: curry(function(fn, list){ 327 | let entry = reuseables.findLastArrayEntry(fn, list); 328 | if (entry) { 329 | return entry[0]; 330 | } 331 | 332 | return -1; 333 | }), 334 | /** 335 | * Returns the first element of an array. 336 | * Returns an empty array when an empty is empty 337 | */ 338 | head: function(list) { 339 | return list.length == 0 ? [] : list[0]; 340 | }, 341 | /** 342 | * Constructs an object out of key-value pairs arrays. 343 | */ 344 | fromPairs: function(list) { 345 | let obj = {}; 346 | list.forEach(x=> obj[x[0]] = x[1]); 347 | return obj; 348 | }, 349 | /** 350 | * Returns all elements of an array but the last 351 | */ 352 | initial: function(list) { 353 | return Array.prototype.slice.call(list, 0, list.length-1); 354 | }, 355 | /** 356 | * Returns values in two comparing arrays without repetition. 357 | * Arrangement of resulting array is determined by main array. 358 | * @param 1st Any number of individual arrays 359 | * @param 2nd {number} Position number of array to be used as main 360 | * @param 3rd {number} Position number of to be used as follower 361 | * @returns values found in both arrays 362 | */ 363 | intersection: function (...values) { 364 | let list = []; 365 | let {main, follower} = reuseables.getMainAndFollower(values); 366 | 367 | main.forEach(x=>{ 368 | if(list.indexOf(x) ==-1) { 369 | if(follower.indexOf(x) >=0) { 370 | if(list[x] ==undefined) list.push(x) } 371 | } 372 | }) 373 | return list; 374 | }, 375 | /** 376 | * Converts array elements to string joined by specified joiner. 377 | * @param joiner Joins array elements 378 | * @param values different individual arrays 379 | */ 380 | join : function join(joiner, ...values) { 381 | if (values.length > 0) { 382 | return concat([],...values).join(joiner); 383 | } 384 | 385 | // Manually currying 386 | return (...values) => join(joiner, ...values); 387 | }, 388 | /** 389 | * Returns the nth value at the specified index. 390 | * If index is negative, it returns value starting from the right 391 | * @param list the array to be operated on 392 | * @param indexNum the index number of the value to be retrieved 393 | */ 394 | nth: function nth(list, indexNum) { 395 | if (arguments.length == 1) { 396 | // Manually currying 397 | indexNum = list; 398 | return (list) => nth(list, indexNum); 399 | } 400 | 401 | if(indexNum >= 0) { 402 | return list[+indexNum] 403 | }; 404 | return [...list].reverse()[list.length+indexNum]; 405 | }, 406 | pull: function pull(list, ...values){ 407 | if ( !(list && Array.isArray(list)) ) { 408 | // Manually currying 409 | let args = arguments; 410 | return (list) => pull(list, ...args); 411 | } 412 | 413 | return differenceWithDup(values, list); 414 | }, 415 | /** 416 | * Returns the lowest index number of a value if it is to be added to an array. 417 | * @param list {Array} array that value will be added to 418 | * @param value value to evaluate 419 | * @param valueIndex {string} accepts either 'first' or 'last'. 420 | * Specifies either to return the first or last index if the value is to be added to the array. 421 | * Default is 'first' 422 | * @returns {number} 423 | */ 424 | sortedIndex: function sortedIndex(list, value, valueIndex) { 425 | if (!(list && Array.isArray(list))) { 426 | // Manually currying 427 | let args = arguments; 428 | return (list) => sortedIndex(list, ...args); 429 | } 430 | 431 | return reuseables.sorter(list, value, valueIndex); 432 | }, 433 | /** 434 | * Returns sorted array without duplicates 435 | * @param list {Array} array to be sorted 436 | * @returns {Array} sorted array 437 | */ 438 | sortedUniq: function(list){ 439 | const listNoDuplicate = difference([],list); 440 | if(typeof list[0] == "number") { 441 | return listNoDuplicate.sort((a,b)=>a-b); 442 | } 443 | 444 | return listNoDuplicate.sort(); 445 | }, 446 | /** 447 | * Returns unified array with or without duplicates. 448 | * @param list1 {Array} first array 449 | * @param list2 {Array} second array 450 | * @param duplicate {boolean} boolean to include duplicates 451 | * @returns {Array} array with/without duplicates 452 | */ 453 | union: function union(list1, list2, duplicate=false) { 454 | if ( arguments.length < 2 ) { 455 | // Manually currying 456 | let args1 = arguments; 457 | 458 | return (...args2) => union(...args1, ...args2); 459 | } else if (arguments.length === 2 && (! Array.isArray(list2))) { 460 | // curring union(_, list, duplicate) cases 461 | // Manually currying 462 | let args = arguments; 463 | return (list) => union(list, ...args); 464 | } 465 | 466 | if(duplicate) { 467 | return differenceWithDup([],list1.concat(list2)); 468 | } 469 | return difference([],list1.concat(list2)); 470 | }, 471 | /** 472 | * Returns an array of arrays of merged values of corresponding indexes. 473 | * @param list {Array} arrays to be merged. 474 | */ 475 | zip: zip, 476 | /** 477 | * Returns array of arrays the first of which contains all of the first elements in the input arrays, 478 | the second of which contains all of the second elements, and so on. 479 | @param list {Array} Array of grouped elements to be processed. 480 | */ 481 | unzip: (list)=> zip(...list) 482 | }; 483 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | Maybe: require('./maybe'), 3 | MonadIO: require('./monadio'), 4 | Publisher: require('./publisher'), 5 | ...require('./fp'), 6 | 7 | ...require('./pattern'), 8 | }; 9 | -------------------------------------------------------------------------------- /maybe.js: -------------------------------------------------------------------------------- 1 | function isNull(ref) { 2 | return ref === undefined || ref === null 3 | } 4 | function isMaybe(m) { 5 | return m instanceof MaybeDef 6 | } 7 | function isNone(m) { 8 | return m === None 9 | } 10 | 11 | class MaybeDef { 12 | constructor(ref) { 13 | this.ref = ref 14 | } 15 | of(ref) { 16 | if (isNull(ref) || isNone(ref)) { 17 | return None 18 | } 19 | 20 | var m = new MaybeDef(ref) 21 | return m 22 | } 23 | fromFalsy(ref) { 24 | return ref ? this.of(ref) : None 25 | } 26 | fromPredicate(predicate, value) { 27 | if (arguments.length > 1) { 28 | return predicate(value) ? this.of(value) : None 29 | } 30 | 31 | return (x) => this.fromPredicate(predicate, x) 32 | } 33 | 34 | isNull() { 35 | // return isNull(this.ref) 36 | return false 37 | } 38 | isPresent() { 39 | // return !this.isNull() 40 | return true 41 | } 42 | unwrap() { 43 | return this.ref 44 | } 45 | toString() { 46 | // return `Maybe(${JSON.stringify(this.ref)})` 47 | return `Some(${JSON.stringify(this.ref)})` 48 | } 49 | toList() { 50 | return [this.ref] 51 | } 52 | empty() { 53 | return None 54 | } 55 | zero() { 56 | return None 57 | } 58 | 59 | or(ref) { 60 | // return this.isNull() ? this.of(ref) : this 61 | return this 62 | } 63 | orDo(fn) { 64 | // if (this.isNull()) { 65 | // // return this.then(fn); 66 | // // NOTE: It's expectable: null cases 67 | // return this.of(fn()); 68 | // } 69 | return this 70 | } 71 | letDo(fn) { 72 | // if (this.isPresent()) { 73 | // return this.then(fn); 74 | // } 75 | // return this 76 | return this.then(fn) 77 | } 78 | 79 | then(fn) { 80 | return this.of(this.flatMap(fn)) 81 | } 82 | flatMap(fn) { 83 | return fn(this.ref) 84 | } 85 | join() { 86 | let ref = this.ref 87 | if (isMaybe(ref)) { 88 | return ref.join() 89 | } 90 | 91 | return this 92 | } 93 | reduce(reducer, initVal) { 94 | return reducer(initVal, this.ref) 95 | } 96 | filter(predicate) { 97 | return predicate(this.ref) ? this.of(this.ref) : None 98 | } 99 | ap(fnM) { 100 | return fnM.chain(f => this.map(f)) 101 | } 102 | chainRec (f, i) { 103 | let result 104 | let x = i 105 | do { 106 | result = f((x) => {return {value: x, done: false}}, (x) => {return {value: x, done: true}}, x).unwrap() 107 | x = result.value 108 | } while (!result.done) 109 | return this.of(result.value) 110 | } 111 | equals(m) { 112 | return isMaybe(m) && m.unwrap() === this.ref 113 | } 114 | 115 | } 116 | 117 | // Expectable cases of Null 118 | var None = Object.assign(new MaybeDef(), { 119 | isNull: function() { 120 | return true 121 | }, 122 | isPresent: function() { 123 | return false 124 | }, 125 | unwrap: function() { 126 | return null 127 | }, 128 | toString: function() { 129 | return 'None' 130 | }, 131 | toList: function() { 132 | return [] 133 | }, 134 | 135 | or: function(ref) { 136 | return this.of(ref) 137 | }, 138 | orDo: function(fn) { 139 | return this.of(fn()) 140 | }, 141 | letDo: function(fn) { 142 | return this 143 | }, 144 | 145 | join: function() { 146 | return None 147 | }, 148 | reduce: function(reducer, initVal) { 149 | return initVal 150 | }, 151 | filter: function() { 152 | return None 153 | }, 154 | ap: function(fnM) { 155 | return None 156 | }, 157 | equals: function(m) { 158 | return isNone(m) 159 | }, 160 | }) 161 | 162 | // Prevent avoiding aliases in case of leaking. 163 | const aliases = { 164 | 'just': 'of', 165 | 'chain': 'flatMap', 166 | 'bind': 'then', 167 | 'map': 'then', 168 | 169 | 'alt': 'or', 170 | 'extend': 'letDo', 171 | 'extract': 'unwrap', 172 | 173 | 'fantasy-land/of': 'of', 174 | 'fantasy-land/empty': 'empty', 175 | 'fantasy-land/zero': 'zero', 176 | 'fantasy-land/extract': 'extract', 177 | 'fantasy-land/equals': 'equals', 178 | 'fantasy-land/map': 'map', 179 | 'fantasy-land/ap': 'ap', 180 | 'fantasy-land/alt': 'alt', 181 | 'fantasy-land/chain': 'chain', 182 | 'fantasy-land/join': 'join', 183 | 'fantasy-land/extend': 'extend', 184 | 'fantasy-land/reduce': 'reduce', 185 | 'fantasy-land/filter': 'filter', 186 | } 187 | Object.keys(aliases).forEach((key) => { 188 | MaybeDef.prototype[key] = MaybeDef.prototype[aliases[key]] 189 | }); 190 | 191 | 192 | // [MaybeDef].forEach((classInstance) => { 193 | // classInstance.prototype.alt = classInstance.prototype.or 194 | // classInstance.prototype.extend = classInstance.prototype.letDo 195 | // classInstance.prototype.extract = classInstance.prototype.unwrap 196 | // }) 197 | // NOTE: There's only one class for speed-up purposes (ES5 code-gen & .js file size) 198 | 199 | 200 | var Maybe = new MaybeDef({}) 201 | 202 | module.exports = Maybe 203 | -------------------------------------------------------------------------------- /monadio.js: -------------------------------------------------------------------------------- 1 | class MonadIODef { 2 | constructor(effect) { 3 | this.effect = effect; 4 | } 5 | 6 | then(fn) { 7 | var self = this; 8 | return new MonadIODef(function () { 9 | return fn(self.effect()); 10 | }); 11 | } 12 | flatMap(fn) { 13 | return this.then((result)=>fn(result).effect()); 14 | } 15 | of(ref) { 16 | var m = new MonadIODef(()=>ref); 17 | return m; 18 | } 19 | ap(fnM) { 20 | return fnM.chain(f => this.map(f)); 21 | } 22 | 23 | subscribe(fn, asynchronized) { 24 | if (asynchronized) { 25 | return Promise.resolve(0).then(this.effect).then(fn); 26 | } 27 | 28 | fn(this.effect()) 29 | return fn; 30 | } 31 | } 32 | MonadIODef.prototype.just = MonadIODef.prototype.of 33 | MonadIODef.prototype.chain = MonadIODef.prototype.flatMap 34 | MonadIODef.prototype.bind = MonadIODef.prototype.then 35 | MonadIODef.prototype.map = MonadIODef.prototype.then 36 | 37 | var MonadIO = new MonadIODef({}); 38 | MonadIO.fromPromise = function (p) { 39 | var m = new MonadIODef(function () { 40 | return MonadIO.doM(function *() { 41 | return yield p; 42 | }); 43 | }); 44 | return m; 45 | }; 46 | MonadIO.promiseof = function (ref) { 47 | return Promise.resolve(ref); 48 | }; 49 | MonadIO.wrapGenerator = function (genDef) { 50 | var gen = genDef(); 51 | function step(value) { 52 | var result = gen.next(value); 53 | if (result.done) { 54 | return result.value; 55 | } 56 | if (result.value instanceof MonadIODef) { 57 | return result.value.subscribe((x)=>x, true).then(step); 58 | } 59 | return result.value.then(step); 60 | } 61 | return step; 62 | }; 63 | MonadIO.generatorToPromise = function (genDef) { 64 | return Promise.resolve().then(MonadIO.wrapGenerator(genDef)); 65 | } 66 | MonadIO.doM = function (genDef) { 67 | return MonadIO.wrapGenerator(genDef)(); 68 | }; 69 | 70 | module.exports = MonadIO; 71 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fpes", 3 | "version": "1.1.4", 4 | "description": "Functional Programming for EcmaScript(Javascript)", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha --require @babel/register --require should test/**/*.js", 8 | "dev": "webpack -d --watch", 9 | "build": "webpack --mode production && bash build-custom.sh", 10 | "build-custom": "webpack --mode production --config webpack.custom.config.js", 11 | "clean": "find dist -name '*.js*' | sed 's/^src\\///' | xargs rm -f", 12 | "release": "npm version patch; npm publish; git push; git push --tags", 13 | "prepare": "npm run build", 14 | "postpublish": "npm run clean" 15 | }, 16 | "directories": { 17 | "lib": "./", 18 | "test": "./test" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/TeaEntityLab/fpEs.git" 23 | }, 24 | "keywords": [ 25 | "functional-programming", 26 | "fp", 27 | "js", 28 | "es", 29 | "es6", 30 | "es7", 31 | "javascript", 32 | "functional-reactive-programming", 33 | "reactive", 34 | "reactive-programming", 35 | "rx", 36 | "monad", 37 | "monads", 38 | "optional", 39 | "optional-implementations", 40 | "publisher-subscriber", 41 | "publisher-subscriber-pattern", 42 | "curry", 43 | "currying", 44 | "pubsub" 45 | ], 46 | "author": "John Lee", 47 | "license": "MIT", 48 | "bugs": { 49 | "url": "https://github.com/TeaEntityLab/fpEs/issues" 50 | }, 51 | "homepage": "https://github.com/TeaEntityLab/fpEs#readme", 52 | "devDependencies": { 53 | "@babel/cli": "^7.16.8", 54 | "@babel/core": "^7.16.12", 55 | "@babel/plugin-transform-runtime": "^7.16.10", 56 | "@babel/preset-env": "^7.16.11", 57 | "@babel/register": "^7.16.9", 58 | "@babel/runtime": "^7.16.7", 59 | "@gfx/zopfli": "^1.0.15", 60 | "ansi-regex": ">=6.0.1", 61 | "babel-loader": "^9.1.0", 62 | "brotli-webpack-plugin": "^1.1.0", 63 | "compression-webpack-plugin": "^10.0.0", 64 | "glob-parent": ">=6.0.2", 65 | "minimatch": ">=3.0.5", 66 | "mocha": "^10.0.0", 67 | "nanoid": ">=3.2.0", 68 | "path-parse": ">=1.0.7", 69 | "serialize-javascript": "^6.0.0", 70 | "set-value": ">=4.1.0", 71 | "simple-get": ">=4.0.1", 72 | "should": "^13.2.3", 73 | "webpack": "^5.67.0", 74 | "webpack-cli": "^5.0.0" 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /pattern.js: -------------------------------------------------------------------------------- 1 | class Pattern { 2 | constructor(matches, effect) { 3 | this.matches = matches; 4 | this.effect = effect; 5 | } 6 | } 7 | 8 | class Matchable { 9 | matches(...values) { 10 | throw new Error('No implementation'); 11 | } 12 | } 13 | 14 | function isNotNumber(v) { 15 | return isNaN(v) || v.toString().trim() === ''; 16 | } 17 | 18 | function inCaseOfEqual (value, effect) { 19 | return new Pattern((v)=>value === v, effect); 20 | } 21 | function inCaseOfNumber (effect) { 22 | return new Pattern((v)=>!isNotNumber(v), (v)=>effect(+v)); 23 | } 24 | function inCaseOfNaN (effect) { 25 | return new Pattern(isNotNumber, (v)=>effect(+v)); 26 | } 27 | function inCaseOfString (effect) { 28 | return new Pattern((v)=> typeof v === 'string', (v)=>effect(+v)); 29 | } 30 | function inCaseOfObject (effect) { 31 | return new Pattern((v)=> v && typeof v === "object" && (!Array.isArray(v)), effect); 32 | } 33 | function inCaseOfArray (effect) { 34 | return new Pattern((v)=> v && Array.isArray(v), effect); 35 | } 36 | function inCaseOfClass (theClass, effect) { 37 | return new Pattern((v)=> v instanceof theClass, effect); 38 | } 39 | function inCaseOfNull (effect) { 40 | return new Pattern((v)=> v === null || v === undefined, effect); 41 | } 42 | function inCaseOfFunction (effect) { 43 | return inCaseOfClass(Function, effect); 44 | } 45 | function inCaseOfPattern (effect) { 46 | return inCaseOfClass(Pattern, effect); 47 | } 48 | function inCaseOfPatternMatching (effect) { 49 | return inCaseOfClass(PatternMatching, effect); 50 | } 51 | function inCaseOfCompType (effect) { 52 | return inCaseOfClass(CompType, effect); 53 | } 54 | function inCaseOfCompTypeMatchesWithSpread (theCompType, effect) { 55 | return new Pattern((v)=> (TypeArray.matches(v) && theCompType.matches(...v)) || theCompType.matches(v), effect); 56 | } 57 | function inCaseOfRegex (regex, effect) { 58 | return new Pattern((v)=> { 59 | if (typeof regex === 'string') { 60 | regex = new RegExp(regex); 61 | } 62 | 63 | return regex.test(v); 64 | }, effect); 65 | } 66 | 67 | function TypeInCaseOf (matches) { 68 | return new Pattern(matches, ()=>true); 69 | } 70 | 71 | function TypeMatchesAllPatterns (...patterns) { 72 | return TypeInCaseOf((v)=>{ 73 | let matched = true; 74 | for (let pattern of patterns) { 75 | matched = matched && pattern.matches(v); 76 | } 77 | return matched; 78 | }); 79 | } 80 | 81 | function TypeADT(adtDef) { 82 | let patterns = []; 83 | 84 | let primaryTypesMapping = [ 85 | 86 | // Primary 87 | (adt) => { 88 | let theType = TypeString; 89 | return adt === theType || adt === String || theType.matches(adt) ? theType : undefined; 90 | }, 91 | (adt) => { 92 | let theType = TypeNumber; 93 | return adt === theType || adt === Number || theType.matches(adt) ? theType : undefined; 94 | }, 95 | (adt) => { 96 | let theType = TypeFunction; 97 | return adt === theType || adt === Function ? theType : undefined; 98 | }, 99 | 100 | // Rule 101 | (adt) => { 102 | let theType = TypeFunction; 103 | return theType.matches(adt) ? TypeInCaseOf(adt) : undefined; 104 | }, 105 | (adt) => { 106 | let theType = TypePattern; 107 | return theType.matches(adt) ? adt : undefined; 108 | }, 109 | (adt) => { 110 | let theType = TypePatternMatching; 111 | return theType.matches(adt) ? TypeInCaseOf((v)=>adt.matchFor(v)) : undefined; 112 | }, 113 | (adt) => { 114 | let theType = TypeCompType; 115 | return theType.matches(adt) ? adt : undefined; 116 | }, 117 | 118 | // Null/Nan 119 | (adt) => { 120 | let theType = TypeNull; 121 | return adt === theType || theType.matches(adt) ? theType : undefined; 122 | }, 123 | (adt) => { 124 | let theType = TypeInCaseOf((v) => (! (TypeObject.matches(v) || TypeArray.matches(v))) && TypeNaN.matches(v)); 125 | return adt === TypeNaN || theType.matches(adt) ? theType : undefined; 126 | }, 127 | ]; 128 | for (let theTypeMapping of primaryTypesMapping) { 129 | let theType = theTypeMapping(adtDef); 130 | if (theType) { 131 | return theType; 132 | } 133 | } 134 | 135 | if (TypeArray.matches(adtDef)) { 136 | 137 | let subPatternsForOr = adtDef.map((subAdt) => { 138 | return TypeADT(subAdt); 139 | }); 140 | subPatternsForOr.push(otherwise(()=>false)); 141 | let TypeSubVFromAdt = TypeInCaseOf((subV)=>{ 142 | return either(subV, ...subPatternsForOr); 143 | }); 144 | 145 | patterns.push(TypeArray); 146 | patterns.push(TypeInCaseOf((v)=>{ 147 | return v.map((item)=>{ 148 | return TypeSubVFromAdt.matches(item); 149 | }).reduce((prevResult, x) => x && prevResult, true); 150 | 151 | })); 152 | } else if (TypeObject.matches(adtDef)) { 153 | 154 | let patternsForAnd = []; 155 | for (let key in adtDef) { 156 | if (adtDef.hasOwnProperty(key)) { 157 | let fieldName = key; 158 | patternsForAnd.push(TypeInCaseOf((v)=>{ 159 | return TypeADT(adtDef[fieldName]).matches(v[fieldName]); 160 | })); 161 | } 162 | } 163 | 164 | patterns.push(TypeObject); 165 | patterns.push(TypeMatchesAllPatterns(...patternsForAnd)); 166 | } 167 | return TypeMatchesAllPatterns(...patterns); 168 | } 169 | 170 | function otherwise (effect) { 171 | return new Pattern(()=>true, effect); 172 | } 173 | 174 | function either(value, ...patterns) { 175 | for (let pattern of patterns) { 176 | // console.log(pattern.matches(value)); 177 | 178 | if (pattern.matches(value)) { 179 | return pattern.effect(value); 180 | } 181 | } 182 | 183 | throw new Error(`Cannot match ${JSON.stringify(value)}`); 184 | } 185 | 186 | class PatternMatching extends Matchable { 187 | constructor(...patterns) { 188 | super(); 189 | this.patterns = patterns; 190 | } 191 | 192 | matches(value) { 193 | return either(value, ...this.patterns); 194 | } 195 | matchFor(value) { 196 | return this.matches(value); 197 | } 198 | } 199 | 200 | class CompData { 201 | constructor(type, ...values) { 202 | this.type = type; 203 | this.values = values; 204 | } 205 | } 206 | 207 | class CompType extends Matchable { 208 | effect(...values) { 209 | if (this.matches(...values)) { 210 | return new CompData(this, values); 211 | } 212 | } 213 | 214 | apply(...values) { 215 | return this.effect(...values); 216 | } 217 | } 218 | 219 | class SumType extends CompType { 220 | constructor(...types) { 221 | super(); 222 | this.types = types; 223 | } 224 | 225 | matches(...values) { 226 | var type = this.innerMatches(...values); 227 | if (type) { 228 | return true; 229 | } 230 | 231 | return false; 232 | } 233 | effect(...values) { 234 | var type = this.innerMatches(...values); 235 | if (type) { 236 | return type.effect(...values); 237 | } 238 | } 239 | innerMatches(...values) { 240 | for (let type of this.types) { 241 | if (type.matches(...values)) { 242 | return type; 243 | } 244 | } 245 | } 246 | } 247 | 248 | class ProductType extends CompType { 249 | constructor(...types) { 250 | super(); 251 | this.types = types; 252 | } 253 | 254 | matches(...values) { 255 | if (values.length != this.types.length) { 256 | return false; 257 | } 258 | 259 | for (var i = 0; i < values.length; i++) { 260 | if (! this.types[i].matches(values[i])) { 261 | return false; 262 | } 263 | } 264 | 265 | return true; 266 | } 267 | } 268 | 269 | var TypeNumber = inCaseOfNumber(()=>true); 270 | var TypeString = inCaseOfString(()=>true); 271 | var TypeNaN = inCaseOfNaN(()=>true); 272 | var TypeObject = inCaseOfObject(()=>true); 273 | var TypeArray = inCaseOfArray(()=>true); 274 | var TypeNull = inCaseOfNull(()=>true); 275 | var TypeFunction = inCaseOfFunction(()=>true); 276 | var TypePattern = inCaseOfPattern(()=>true); 277 | var TypePatternMatching = inCaseOfPatternMatching(()=>true); 278 | var TypeCompType = inCaseOfCompType(()=>true); 279 | function TypeEqualTo(value) { 280 | return inCaseOfEqual(value, ()=>true); 281 | } 282 | function TypeClassOf(theClass) { 283 | return inCaseOfClass(theClass, ()=>true); 284 | } 285 | function TypeRegexMatches(regex) { 286 | return inCaseOfRegex(regex, ()=>true); 287 | } 288 | function TypeCompTypeMatchesWithSpread(theCompType) { 289 | return inCaseOfCompTypeMatchesWithSpread(theCompType, ()=>true); 290 | } 291 | 292 | module.exports = { 293 | either, 294 | Pattern, 295 | PatternMatching, 296 | inCaseOfEqual, 297 | inCaseOfNumber, 298 | inCaseOfString, 299 | inCaseOfNaN, 300 | inCaseOfObject, 301 | inCaseOfArray, 302 | inCaseOfClass, 303 | inCaseOfNull, 304 | inCaseOfFunction, 305 | inCaseOfPattern, 306 | inCaseOfPatternMatching, 307 | inCaseOfCompType, 308 | inCaseOfCompTypeMatchesWithSpread, 309 | inCaseOfRegex, 310 | otherwise, 311 | 312 | SumType, 313 | ProductType, 314 | CompType, 315 | 316 | TypeNumber, 317 | TypeString, 318 | TypeNaN, 319 | TypeObject, 320 | TypeArray, 321 | TypeNull, 322 | TypeFunction, 323 | TypePattern, 324 | TypePatternMatching, 325 | TypeCompType, 326 | TypeCompTypeMatchesWithSpread, 327 | TypeEqualTo, 328 | TypeClassOf, 329 | TypeRegexMatches, 330 | TypeInCaseOf, 331 | TypeMatchesAllPatterns, 332 | TypeADT, 333 | }; 334 | -------------------------------------------------------------------------------- /publisher.js: -------------------------------------------------------------------------------- 1 | class Publisher { 2 | constructor() { 3 | this.subscribers = []; 4 | } 5 | 6 | map(fn) { 7 | var next = new Publisher(); 8 | next.origin = this; 9 | 10 | this.subscribe((val) => { 11 | next.publish(fn(val)); 12 | }); 13 | return next; 14 | } 15 | subscribe(fn) { 16 | if (this.subscribers.includes(fn)) { 17 | return; 18 | } 19 | 20 | this.subscribers.push(fn); 21 | return fn; 22 | } 23 | unsubscribe(fn) { 24 | this.subscribers = this.subscribers.filter((item)=>item!==fn); 25 | } 26 | clear() { 27 | this.subscribers = [] 28 | } 29 | publish(result,asynchronized) { 30 | this.subscribers.forEach((fn)=>asynchronized ? Promise.resolve(result).then(fn) : fn(result)); 31 | } 32 | } 33 | 34 | module.exports = Publisher; 35 | -------------------------------------------------------------------------------- /test/fp.js: -------------------------------------------------------------------------------- 1 | import { 2 | compose, curry, trampoline, 3 | chunk, range, tail, shift, unique, snooze, 4 | clone, propEq, get, matches, memoize, 5 | flatten, flattenMap, unary, foldl, foldr, take, 6 | compact, concat, contains, difference, differenceWithDup, 7 | reverse, map, reduce, filter, drop, fill, 8 | join, intersection, find, findLast, findIndex, findLastIndex, head, fromPairs, initial, nth, 9 | pull, sortedIndex, sortedUniq, union,zip, unzip 10 | } from '../fp'; 11 | 12 | describe('Fp', function () { 13 | it('compose', function () { 14 | compose((x)=>x-8, (x)=>x+10, (x)=>x*10)(4).should.equal(42) 15 | compose((x)=>x+2, (x,y)=>x*y)(4,10).should.equal(42) 16 | }); 17 | it('curry', function () { 18 | curry((x, y, z) => x + y + z)(1,2,3).should.equal(6) 19 | curry((x, y, z) => x + y + z)(1)(2,3).should.equal(6) 20 | curry((x, y, z) => x + y + z)(1,2)(3).should.equal(6) 21 | curry((x, y, z) => x + y + z)(1)(2)(3).should.equal(6) 22 | }); 23 | it('trampoline', function () { 24 | function fib (n) { 25 | return trampoline(function inner_fib(n, a, b) { 26 | if (n === 0) {return a;} 27 | if (n === 1) {return b;} 28 | return () => inner_fib(n - 1, b, a + b); 29 | })(n, 0, 1); 30 | }; 31 | fib(70).should.equal(190392490709135); 32 | }); 33 | it('chunk', function () { 34 | JSON.stringify(chunk(range(7),3)).should.equal('[[0,1,2],[3,4,5],[6]]') 35 | }); 36 | it('snooze', function () { 37 | return snooze(20).then(()=>'ok').then((x)=>x.should.equal('ok')); 38 | }); 39 | it('unique', function () { 40 | JSON.stringify(unique([0,0,1,2,3,3,4,5,3,4,5,6])).should.equal('[0,1,2,3,4,5,6]') 41 | }); 42 | it('tail shift take', function () { 43 | JSON.stringify(tail(range(3))).should.equal('[1,2]') 44 | JSON.stringify(shift(range(3))).should.equal('0') 45 | JSON.stringify(take(4, range(3))).should.equal('[0,1,2]') 46 | JSON.stringify(take(3, range(3))).should.equal('[0,1,2]') 47 | JSON.stringify(take(2, range(3))).should.equal('[0,1]') 48 | JSON.stringify(take(1, range(3))).should.equal('[0]') 49 | }); 50 | it('propEq get matches', function () { 51 | (propEq(1)('a')({a:1})).should.equal(true); 52 | (propEq(1)('a')({b:1})).should.equal(false); 53 | (matches({a:1})({a:1})).should.equal(true); 54 | (matches({b:1})({a:1})).should.equal(false); 55 | (get({b:1})('b')).should.equal(1); 56 | }); 57 | it('clone', function () { 58 | (clone(null) === null).should.equal(true); 59 | (clone(undefined) === undefined).should.equal(true); 60 | 61 | JSON.stringify(clone(chunk(range(7),3))).should.equal('[[0,1,2],[3,4,5],[6]]') 62 | JSON.stringify(clone({a:3,b:4,c:{d:5}})).should.equal('{"a":3,"b":4,"c":{"d":5}}') 63 | }); 64 | it('flatten, flattenMap, reverse, map, reduce, filter', function () { 65 | // console.log(map((x)=>x*x)([1,2,3])); 66 | 67 | 68 | JSON.stringify(flatten([[[[[[[0],1],2],3],4],5],6])).should.equal('[0,1,2,3,4,5,6]') 69 | JSON.stringify(flattenMap((x)=>[x*x,x*x])([1,2,3])).should.equal('[1,1,4,4,9,9]') 70 | JSON.stringify(flattenMap(unary(parseInt))(["+1","+2","+3"])).should.equal('[1,2,3]') 71 | 72 | JSON.stringify(reverse([6,5,4,3,2,1,0])).should.equal('[0,1,2,3,4,5,6]') 73 | JSON.stringify(map((x)=>x*x)([1,2,3])).should.equal('[1,4,9]') 74 | JSON.stringify(reduce((x,y)=>x+y, 0)([1,2,3])).should.equal('6') 75 | JSON.stringify(reduce((x,y)=>x+y)([1,2,3])).should.equal('6') 76 | JSON.stringify(foldr((x,y)=>x>y?x+y:0, 4)([1,2,3])).should.equal('10') 77 | JSON.stringify(foldl((x,y)=>x>y?x+y:0, 0)([1,2,3])).should.equal('0') 78 | JSON.stringify(filter((x)=>x>2)([1,2,3])).should.equal('[3]'); 79 | 80 | (reduce((x,y)=>x+y, 0)([]) === 0).should.equal(true) 81 | 82 | JSON.stringify(compose(reduce((x,y)=>x+y, 0), map((x)=>x*x), filter((x)=>x<4), flatten) 83 | ([[[[[[[0],1],2],3],4],5],6])) 84 | .should.equal('14'); 85 | }); 86 | 87 | it ('should compact by returning all truthy values', () => { 88 | JSON.stringify(compact([1,2,false, "", 3])).should.equal("[1,2,3]"); 89 | }); 90 | it ('should compact with second argument', () => { 91 | JSON.stringify(compact(["John",1, 2, "Jane"],'')).should.equal('["John","Jane"]'); 92 | }); 93 | it ('should compact in currying way', () => { 94 | JSON.stringify(compose(compact(''))(["John",1, 2, "Jane"])).should.equal('["John","Jane"]'); 95 | }); 96 | 97 | it ('should concat arrays', () => { 98 | JSON.stringify(concat([1,2,3],4,5)).should.equal("[1,2,3,4,5]") 99 | JSON.stringify(concat([1,2,3],[4],[5])).should.equal("[1,2,3,4,5]") 100 | JSON.stringify(concat([1,2,3],[4,5])).should.equal("[1,2,3,4,5]") 101 | }); 102 | it ('should concat in currying way', () => { 103 | JSON.stringify(compose(concat([1,2,3]))(4,5)).should.equal("[1,2,3,4,5]") 104 | }); 105 | it ('should concat with a function', () => { 106 | JSON.stringify(concat([1,2,3],4,5, x=>x>3)).should.equal("[4,5]") 107 | }); 108 | 109 | 110 | it ('should return boolean if value is contain in array or not', () => { 111 | contains([2,3,4,5],5).should.equal(true) 112 | }); 113 | it ('should return boolean if value is contain in array or not in currying way', () => { 114 | contains(5)([2,3,4,5]).should.equal(true) 115 | }); 116 | 117 | 118 | 119 | it (`should find the difference between 2 arrays 120 | when no other arguments are specified with the first as main without duplicates`, () => { 121 | JSON.stringify(difference( 122 | ["Naa", "Kofi", "Mensah"], 123 | ["Naa", "Mensah", "Tsatsu", "Amevor","Amevor"])) 124 | .should.equal('["Tsatsu","Amevor"]'); 125 | }); 126 | 127 | it ('should find the difference between the 3rd and 1st arrays ', () => { 128 | JSON.stringify(difference( 129 | ["May","Joe","John"], 130 | ["Jane", "Joe", "John","Kay","May","Q"], 131 | ["May", "Suzzie", "Fri"], 132 | 3,1)).should.equal('["Joe","John"]') 133 | }); 134 | 135 | it ('should find the difference between the 1st and 3rd arrays', () => { 136 | JSON.stringify(difference( 137 | ["May","Joe","John"], 138 | ["Jane", "Joe", "John","Kay","May","Kai"], 139 | ["May", "Kwei", "Fri"], 140 | 1,3)).should.equal('["Kwei","Fri"]') 141 | }); 142 | 143 | 144 | 145 | it ('should find the difference between the 1st and 3rd arrays', () => { 146 | JSON.stringify(differenceWithDup( 147 | ["May","Joe","John"], 148 | ["Jane", "Joe", "John","Kay","May","Kai"], 149 | ["May", "Kwei", "Fri", "Fri"], 150 | 1,3)).should.equal('["Kwei","Fri","Fri"]') 151 | }); 152 | 153 | it (`should find the difference between 2 arrays 154 | when no other arguments are specified with the first as main 155 | even when no duplicates are involved`, () => { 156 | JSON.stringify(differenceWithDup( 157 | ["Naa", "Kofi", "Mensah"], 158 | ["Naa", "Mensah", "Tsatsu", "Amevor"])) 159 | .should.equal('["Tsatsu","Amevor"]'); 160 | }); 161 | 162 | it ('should find the difference between the 3rd and 1st arrays with duplicates', () => { 163 | JSON.stringify(differenceWithDup( 164 | ["May","Joe","John","Joe"], 165 | ["Jane", "Joe", "John","Kay","May","Q"], 166 | ["May", "Suzzie", "Fri"], 167 | 3,1)).should.equal('["Joe","John","Joe"]') 168 | }); 169 | 170 | 171 | 172 | 173 | it ('should return array when dropCount is 0 and no function is passed', () => { 174 | JSON.stringify(drop([1,2,3],0)).should.equal('[1,2,3]'); 175 | }); 176 | it ('should return array when dropCount is 0 and no function is passed and either right or left is passed', () => { 177 | JSON.stringify(drop([1,2,3],0,"left")).should.equal('[1,2,3]'); 178 | JSON.stringify(drop([1,2,3],0,"right")).should.equal('[1,2,3]'); 179 | }); 180 | it ('should drop one element from left when only array is passed as argument', () => { 181 | JSON.stringify(drop([1,2,3])).should.equal('[2,3]'); 182 | }); 183 | it ('should drop one element from left when array and left is passed as argument', () => { 184 | JSON.stringify(drop([1,2,3],1,"left")).should.equal('[2,3]'); 185 | }); 186 | it ('should drop one element from right when array and right is passed as argument', () => { 187 | JSON.stringify(drop([1,2,3],1,"right")).should.equal('[1,2]'); 188 | }); 189 | it ('should drop specified number of elements from left when only array and dropCount is passed as argument', () => { 190 | JSON.stringify(drop([1,2,3],2)).should.equal('[3]'); 191 | }); 192 | it ('should drop specified number of elements from left when left is passed as argument', () => { 193 | JSON.stringify(drop([1,2,3],2,"left")).should.equal('[3]'); 194 | }); 195 | it ('should drop specified number of elements from right when right is passed as argument', () => { 196 | JSON.stringify(drop([1,2,3],2,"right")).should.equal('[1]'); 197 | }); 198 | 199 | it ('should drop specified number of elements from left and filter remaining values with passed in function', () => { 200 | JSON.stringify(drop([1,2,3,4,5,6],3,"left",x=>x>5)).should.equal('[6]'); 201 | }); 202 | it ('should drop specified number of elements from right and filter remaining values with passed in function', () => { 203 | JSON.stringify(drop([1,2,3,4,5,6],3,"right",x=>x>2)).should.equal('[3]'); 204 | }); 205 | it ('should drop specified number of elements from right and filter remaining values with passed in function in currying way', () => { 206 | JSON.stringify(drop(3,"right",x=>x>2)([1,2,3,4,5,6])).should.equal('[3]'); 207 | }); 208 | 209 | 210 | it ('should fill and return new array with specified value', () => { 211 | JSON.stringify(fill([1,2,3],4)).should.equal('[4,4,4]'); 212 | }); 213 | it ('should fill Array object and return new array with specified value', () => { 214 | JSON.stringify(fill(Array(3),4)).should.equal('[4,4,4]'); 215 | }); 216 | it ('should fill array with specified value till the end when endIndex isn\'t provided', () => { 217 | JSON.stringify(fill([1,1,3,3,5],"*",0)).should.equal('["*","*","*","*","*"]'); 218 | }); 219 | it ('should fill array with specified value till the end when endIndex and startIndex aren\'t provided', () => { 220 | JSON.stringify(fill([1,1,3,3,5],"*")).should.equal('["*","*","*","*","*"]'); 221 | }); 222 | it ('should fill array at specified indexes with specified value', () => { 223 | JSON.stringify(fill([1,1,3,3,5],"*",0,2)).should.equal('["*","*","*",3,5]'); 224 | }); 225 | it ('should return array when startIndex is greater than array', () => { 226 | JSON.stringify(fill([1,1,3,3,5],"*",5)).should.equal('[1,1,3,3,5]'); 227 | }); 228 | it ('should return array when startIndex and endIndex are greater than array', () => { 229 | JSON.stringify(fill([1,1,3,3,5],"*",6,6)).should.equal('[1,1,3,3,5]'); 230 | }); 231 | it ('should return array when startIndex and endIndex are greater than array in currying way', () => { 232 | JSON.stringify(fill("*",6,6)([1,1,3,3,5])).should.equal('[1,1,3,3,5]'); 233 | }); 234 | 235 | 236 | it ('should return first element\'s index for which function equals true', () => { 237 | JSON.stringify(findIndex(x=>x%2==0, [1,3,4,2,6])).should.equal('2'); 238 | JSON.stringify(find(x=>x%2==0, [1,3,4,2,6])).should.equal('4'); 239 | JSON.stringify(compose(findIndex(x=>x%2==0), map((x)=>x/2))([2,6,8,4,12])).should.equal('2'); 240 | }); 241 | 242 | 243 | it ('should return last element\'s index for which function equals true', () => { 244 | JSON.stringify(findLastIndex(x=>x%2==0, [1,3,4,2,6])).should.equal('4'); 245 | JSON.stringify(findLast(x=>x%2==0, [1,3,4,2,6])).should.equal('6'); 246 | JSON.stringify(compose(findLastIndex(x=>x%2==0), map((x)=>x/2))([2,6,8,4,12])).should.equal('4'); 247 | }); 248 | 249 | 250 | it ('should return empty array if passed in array is empty', () => { 251 | JSON.stringify(head([])).should.equal('[]'); 252 | }); 253 | it ('should return first element if array contains elements', () => { 254 | JSON.stringify(head([1,2,3])).should.equal('1'); 255 | }); 256 | 257 | 258 | it ('should return an object with key-values pairs from array', () => { 259 | JSON.stringify(fromPairs([['a', 1], ['b', 2]])).should.equal('{"a":1,"b":2}') 260 | }); 261 | 262 | 263 | it ('should return all but last value of array', () => { 264 | JSON.stringify(initial([1,2,3,4])).should.equal('[1,2,3]'); 265 | }); 266 | 267 | 268 | 269 | it (`should find the intersection between 2 arrays 270 | when no other arguments are specified with the first as main`, () => { 271 | JSON.stringify(intersection( 272 | ["Naa", "Kofi", "Mensah"], 273 | ["Naa", "Mensah", "Tsatsu", "Amevor"])) 274 | .should.equal('["Naa","Mensah"]'); 275 | }); 276 | 277 | it ('should find the intersection between the 3rd and 1st arrays ', () => { 278 | JSON.stringify(intersection( 279 | ["May","Joe","John"], 280 | ["Jane", "Joe", "John","Kay","May","Q"], 281 | ["Joe", "Suzzie", "May"], 282 | 3,1)).should.equal('["Joe","May"]') 283 | }); 284 | 285 | it ('should find the intersection between the 1st and 3rd arrays', () => { 286 | JSON.stringify(intersection( 287 | ["May","Joe","John"], 288 | ["Jane", "Joe", "John","Kay","May","Q"], 289 | ["Joe", "Suzzie", "May"], 290 | 1,3)).should.equal('["May","Joe"]') 291 | }); 292 | 293 | 294 | it ('should join 2 arrays with speficied joiner', () => { 295 | (join("~",[1,2],[3,4])).should.equal('1~2~3~4'); 296 | }); 297 | 298 | it ('should join several arrays with specified joiner', () => { 299 | (join("~",[1,2],[3,4],[5,6],[7,8])).should.equal('1~2~3~4~5~6~7~8'); 300 | }); 301 | it ('should join several arrays in currying way', () => { 302 | (join("~")([1,2],[3,4],[5,6],[7,8])).should.equal('1~2~3~4~5~6~7~8'); 303 | }); 304 | 305 | 306 | it ('should return nth value at the index number specified', () => { 307 | nth([1,2,3,4],2).should.equal(3); 308 | }); 309 | it ('should return nth value at the index number specified in currying way', () => { 310 | nth(2)([1,2,3,4]).should.equal(3); 311 | }); 312 | 313 | it ('should return the nth value starting from the end when a negative number is specified', () => { 314 | nth([1,2,3,4],-2).should.equal(2); 315 | }); 316 | 317 | 318 | it ('should return array excluding specified values', () => { 319 | JSON.stringify(pull(["Naa","Esi","Aku","Awo","Ajo"],"Ajo","Aku")) 320 | .should.equal('["Naa","Esi","Awo"]') 321 | }); 322 | 323 | 324 | 325 | it ('should return lowest index of value if to be added to array', () => { 326 | sortedIndex(["Aaron", "Joe"], "Fred").should.equal(1); 327 | }); 328 | 329 | it (`should correctly return lowest index of value 330 | if to be added to array even if it's an array of numbers`, () => { 331 | sortedIndex([1, 30, 4],21).should.equal(2); 332 | }); 333 | 334 | it (`should correctly return lowest last index if value and array are of different types`, () => { 335 | sortedIndex([30, 50, 15], "Fred").should.equal(3); 336 | }); 337 | 338 | it (`should correctly return lowest index of value 339 | if to be added to array even if it's an array of numbers`, () => { 340 | sortedIndex([4, 5, 5, 5, 6], 5).should.equal(1); 341 | }); 342 | 343 | 344 | 345 | it (`should correctly return highest index of value 346 | if to be added to array even if it's an array of numbers`, () => { 347 | sortedIndex([4, 5, 5, 5, 6], 5,"last").should.equal(4); 348 | }); 349 | it (`should correctly return highest index of value 350 | if to be added to array even if it's an array of numbers in currying way`, () => { 351 | sortedIndex(5,"last")([4, 5, 5, 5, 6]).should.equal(4); 352 | }); 353 | 354 | it (`should correctly return highest last index if value and array are of different types`, () => { 355 | sortedIndex([30, 50, 15], "Fred","last").should.equal(3); 356 | }); 357 | 358 | it ('should return array excluding specified values in currying way', () => { 359 | JSON.stringify(pull("Ajo","Aku")(["Naa","Esi","Aku","Awo","Ajo"])) 360 | .should.equal('["Naa","Esi","Awo"]') 361 | }); 362 | 363 | 364 | 365 | it ('should return sorted array of numbers without duplicates', () => { 366 | JSON.stringify(sortedUniq([1,2,2,3,4])).should.equal("[1,2,3,4]") 367 | }); 368 | 369 | it ('should return sorted array of letters without duplicates', () => { 370 | JSON.stringify(sortedUniq(["Apple","Cat","Boy","Boy"])) 371 | .should.equal('["Apple","Boy","Cat"]') 372 | }); 373 | 374 | 375 | it ('should unify 2 arrays without duplicates', () => { 376 | JSON.stringify(union([2],[1,2])).should.equal('[2,1]'); 377 | }); 378 | it ('should unify 2 arrays without duplicates in currying way', () => { 379 | JSON.stringify(union([2])([1,2])).should.equal('[2,1]'); 380 | }); 381 | 382 | it ('should unify 2 arrays with duplicates', () => { 383 | JSON.stringify(union([2],[1,2],true)).should.equal('[2,1,2]'); 384 | }); 385 | it ('should unify 2 arrays with duplicates in currying way (many forms)', () => { 386 | JSON.stringify(union([2])([1,2],true)).should.equal('[2,1,2]'); 387 | JSON.stringify(union([1,2],true)([2])).should.equal('[2,1,2]'); 388 | }); 389 | 390 | 391 | it ('should zip arrays into one array', () => { 392 | JSON.stringify(zip(['a', 'b'], [1, 2], [true, false])).should.equal('[["a",1,true],["b",2,false]]') 393 | }); 394 | 395 | it (`should zip arrays into one array and return null ("undefined") 396 | as index values for arrays with length less than that of the first array`, () => { 397 | JSON.stringify(zip(['a', 'b','c'], [1, 2], [true, false])).should.equal(`[["a",1,true],["b",2,false],["c",${null},${null}]]`) 398 | }); 399 | 400 | it (`should zip arrays into one array and not return null ("undefined") 401 | as index values for arrays with length greater than that of the first array`, () => { 402 | JSON.stringify(zip(['a', 'b'], [1, 2, 3], [true, false])).should.equal(`[["a",1,true],["b",2,false]]`) 403 | }); 404 | 405 | 406 | it ('should unzip arrays into one array', () => { 407 | JSON.stringify(unzip([["a",1,true],["b",2,false]])).should.equal('[["a","b"],[1,2],[true,false]]') 408 | }); 409 | }) 410 | -------------------------------------------------------------------------------- /test/maybe.js: -------------------------------------------------------------------------------- 1 | import Maybe from '../maybe'; 2 | 3 | describe('Maybe', function () { 4 | it('New/Convert', function () { 5 | var m 6 | m = Maybe.just(0); 7 | m.toList().length.should.equal(1) 8 | m.toList()[0].should.equal(0) 9 | m.toString().should.equal('Some(0)') 10 | m = Maybe.empty() 11 | m.toList().length.should.equal(0) 12 | m.toString().should.equal('None') 13 | 14 | Maybe.fromFalsy(0).should.equal(Maybe.empty()) 15 | Maybe.fromFalsy('').should.equal(Maybe.empty()) 16 | Maybe.fromFalsy(NaN).should.equal(Maybe.empty()) 17 | Maybe.fromFalsy(null).should.equal(Maybe.empty()) 18 | Maybe.fromFalsy(undefined).should.equal(Maybe.empty()) 19 | 20 | Maybe.fromPredicate((x) => x > 3, 3).should.equal(Maybe.empty()) 21 | Maybe.fromPredicate((x) => x > 3, 4).unwrap().should.equal(4) 22 | Maybe.fromPredicate((x) => x > 3)(5).unwrap().should.equal(5) 23 | }); 24 | it('map/bind', function () { 25 | var m; 26 | m = Maybe.just(1).map((a)=>a+2).map((a)=>a+3); 27 | m.unwrap().should.equal(6) 28 | m = Maybe.just(1).bind((a)=>a+2).bind((a)=>a+3); 29 | m.unwrap().should.equal(6) 30 | }); 31 | it('isPresent', function () { 32 | var m; 33 | 34 | m = Maybe.just(1); 35 | m.isPresent().should.equal(true) 36 | m.isNull().should.equal(false) 37 | m = Maybe.just(null); 38 | m.isPresent().should.equal(false) 39 | m.isNull().should.equal(true) 40 | m = Maybe.just(undefined); 41 | m.isPresent().should.equal(false) 42 | m.isNull().should.equal(true) 43 | }); 44 | it('or', function () { 45 | var m; 46 | 47 | m = Maybe.just(1); 48 | m.or(3).unwrap().should.equal(1) 49 | m.or(4).unwrap().should.equal(1) 50 | m = Maybe.just(null); 51 | m.or(3).unwrap().should.equal(3) 52 | m.or(4).unwrap().should.equal(4) 53 | m = Maybe.just(undefined); 54 | m.or(3).unwrap().should.equal(3) 55 | m.or(4).unwrap().should.equal(4) 56 | }); 57 | it('letDo', function () { 58 | var m; 59 | var v; 60 | 61 | m = Maybe.just(1); 62 | v = 0; 63 | m.letDo(function () { 64 | v = 1; 65 | }); 66 | v.should.equal(1) 67 | m = Maybe.just(null); 68 | v = 0; 69 | m.letDo(function () { 70 | v = 1; 71 | }); 72 | v.should.equal(0) 73 | m = Maybe.just(undefined); 74 | v = 0; 75 | m.letDo(function () { 76 | v = 1; 77 | }); 78 | v.should.equal(0) 79 | 80 | // letDo vs orDo 81 | m = Maybe.just(0); 82 | v = m.letDo(function (p) { 83 | return p + 2 84 | }).orDo(function () { 85 | return 3 86 | }).unwrap(); 87 | v.should.equal(2); 88 | m = Maybe.just(undefined); 89 | v = m.letDo(function (p) { 90 | return p + 2 91 | }).orDo(function () { 92 | return 3 93 | }).unwrap(); 94 | v.should.equal(3); 95 | }); 96 | it('map', function () { 97 | var m; 98 | 99 | m = Maybe.just(1).map((x)=>x+2).map((x)=>x+3); 100 | m.unwrap().should.equal(6); 101 | }); 102 | it('flatMap', function () { 103 | var m; 104 | 105 | m = Maybe.just(1).flatMap((x)=>Maybe.just(x+2)).flatMap((x)=>Maybe.just(x+3)); 106 | m.unwrap().should.equal(6); 107 | }); 108 | it('fantasy-land/monad', function () { 109 | var a; 110 | var f; 111 | var m; 112 | 113 | a = 1; 114 | f = (x) => Maybe.of(x + 1); 115 | Maybe.of(a).chain(f).unwrap().should.equal(f(a).unwrap()); 116 | 117 | m = Maybe.of(1); 118 | m.chain(Maybe.of).unwrap().should.equal(m.unwrap()); 119 | }); 120 | it('fantasy-land/chainRec', function () { 121 | Maybe.chainRec((next, done, x) => Maybe.of(x < 1000000 ? next(x + 1) : done(x)), 0).unwrap().should.equal(1000000); 122 | 123 | function fibChainRec (next, done, args) { 124 | var n = args[0], a = args[1], b = args[2]; 125 | 126 | if (n === 0) {return Maybe.of(done(a));} 127 | if (n === 1) {return Maybe.of(done(b));} 128 | return Maybe.of(next([n - 1, b, a + b])); 129 | }; 130 | 131 | ((n)=>Maybe.chainRec(fibChainRec, [n, 0, 1]))(70).unwrap().should.equal(190392490709135); 132 | }); 133 | it('fantasy-land/equals', function () { 134 | Maybe.of(32).equals(Maybe.of(32)).should.equal(true); 135 | }); 136 | it('fantasy-land/join', function () { 137 | Maybe.of(Maybe.of(Maybe.zero())).join().should.equal(Maybe.empty()); 138 | Maybe.of(Maybe.of(Maybe.of(1))).join().unwrap().should.equal(1); 139 | }); 140 | it('fantasy-land/reduce', function () { 141 | Maybe.of(1).reduce(x => x * 3, 3).should.equal(9); 142 | Maybe.of(undefined).reduce(x => x * 3, 3).should.equal(3); 143 | }); 144 | it('fantasy-land/filter', function () { 145 | Maybe.of(1).filter(x => x > 3).should.equal(Maybe.empty()); 146 | Maybe.of(undefined).filter(x => x > 3).should.equal(Maybe.empty()); 147 | Maybe.of(4).filter(x => x > 3).unwrap().should.equal(4); 148 | }); 149 | it('fantasy-land/ap', function () { 150 | var m; 151 | var f; 152 | var u; 153 | var v; 154 | var x; 155 | var y; 156 | 157 | m = Maybe.just(1); 158 | m.ap(Maybe.of(x => x)).unwrap().should.equal(m.unwrap()); 159 | 160 | x = 1; 161 | f = (x) => x + 1; 162 | Maybe.of(x).ap(Maybe.of(f)).unwrap().should.equal(Maybe.of(f(x)).unwrap()); 163 | 164 | u = Maybe.of(f); 165 | y = 1; 166 | Maybe.of(y).ap(u).unwrap().should.equal(u.ap(Maybe.of(f => f(y))).unwrap()); 167 | }); 168 | }) 169 | -------------------------------------------------------------------------------- /test/monadio.js: -------------------------------------------------------------------------------- 1 | import Maybe from '../maybe'; 2 | import MonadIO from '../monadio'; 3 | var {promiseof, doM, generatorToPromise} = MonadIO; 4 | 5 | describe('MonadIO', function () { 6 | it('sync', function () { 7 | var m = MonadIO.just(0); 8 | var v = 0; 9 | 10 | v = 0; 11 | m 12 | .map((val)=>val+1) 13 | .map((val)=>val+2) 14 | .flatMap((val)=>MonadIO.just(val+1).map((val)=>val+1).map((val)=>val+1)) 15 | .subscribe((val)=>v=val); 16 | 17 | v.should.equal(6); 18 | }); 19 | it('async', function () { 20 | var p = undefined; 21 | var m = MonadIO.just(0); 22 | var v = 0; 23 | 24 | v = 0; 25 | p = m 26 | .map((val)=>val+1) 27 | .map((val)=>val+2) 28 | .map((val)=>val+3) 29 | .subscribe((val)=>v=val, true); 30 | 31 | return p.then(()=>v.should.equal(6)); 32 | }); 33 | it('doM', function () { 34 | var p = undefined; 35 | var v = 0; 36 | 37 | v = 0; 38 | p = doM(function *() { 39 | var value = yield promiseof(5); 40 | var value2 = yield promiseof(11); 41 | var value3 = yield Maybe.just(3); 42 | var value4 = yield MonadIO.just(3); 43 | var value5 = yield MonadIO.fromPromise(Promise.resolve(3)); 44 | return value + value2 + value3 + value4 + value5; 45 | }); 46 | 47 | return p.then((v)=>v.should.equal(25)); 48 | }); 49 | it('generatorToPromise', function () { 50 | var p = undefined; 51 | var v = 0; 52 | 53 | v = 0; 54 | p = generatorToPromise(function *() { 55 | var value = yield promiseof(5); 56 | var value2 = yield promiseof(11); 57 | var value3 = yield Maybe.just(3); 58 | var value4 = yield MonadIO.just(3); 59 | var value5 = yield MonadIO.fromPromise(Promise.resolve(3)); 60 | return value + value2 + value3 + value4 + value5; 61 | }); 62 | 63 | return p.then((v)=>v.should.equal(25)); 64 | }); 65 | }) 66 | -------------------------------------------------------------------------------- /test/pattern.js: -------------------------------------------------------------------------------- 1 | import { 2 | either, PatternMatching, Pattern, 3 | inCaseOfEqual, inCaseOfRegex, inCaseOfNull, inCaseOfClass, inCaseOfNumber, inCaseOfNaN, inCaseOfString, inCaseOfObject, inCaseOfArray, 4 | otherwise, 5 | 6 | SumType, ProductType, CompType, 7 | TypeNumber, 8 | TypeString, 9 | TypeNaN, 10 | TypeObject, 11 | TypeArray, 12 | TypeNull, 13 | TypeEqualTo, 14 | TypeClassOf, 15 | TypeRegexMatches, 16 | TypeInCaseOf, TypeMatchesAllPatterns, TypeADT, TypeCompTypeMatchesWithSpread, 17 | } from '../pattern'; 18 | 19 | describe('SumType', function () { 20 | it('Common', function () { 21 | var s; 22 | s = new SumType(new ProductType(TypeString, TypeNumber), new ProductType(TypeRegexMatches('c+'))); 23 | (s.apply("1", "2asdf") !== undefined).should.equal(false); 24 | (s.apply("1", 2) !== undefined).should.equal(true); 25 | 26 | (s.apply("1") !== undefined).should.equal(false); 27 | (s.apply("ccc") !== undefined).should.equal(true); 28 | }); 29 | it('Structural(Manual)', function () { 30 | var s; 31 | 32 | var patternList = [ 33 | TypeObject, 34 | TypeInCaseOf((v) => (+v.id) >= 0), 35 | TypeInCaseOf((v) => TypeObject.matches(v.user) && v.user.id >= 0), 36 | otherwise(()=>false), 37 | ]; 38 | 39 | var customType = TypeMatchesAllPatterns(...patternList); 40 | 41 | s = new SumType(new ProductType(TypeString, TypeNumber), new ProductType(customType)); 42 | (s.apply("1", "2asdf") !== undefined).should.equal(false); 43 | (s.apply("1", 2) !== undefined).should.equal(true); 44 | 45 | (s.apply({id: -1,}) !== undefined).should.equal(false); 46 | (s.apply({id: 30,}) !== undefined).should.equal(false); 47 | (s.apply({id: 30,user: {id: -1,}}) !== undefined).should.equal(false); 48 | (s.apply({id: 30,user: {id: 20,}}) !== undefined).should.equal(true); 49 | }); 50 | it('Structural(ADT) Object', function () { 51 | var s; 52 | 53 | var adt = { 54 | id: Number, 55 | user: { 56 | id: Number, 57 | }, 58 | }; 59 | 60 | var customType = TypeADT(adt); 61 | 62 | s = new SumType(new ProductType(customType)); 63 | (s.apply(undefined) !== undefined).should.equal(false); 64 | (s.apply(null) !== undefined).should.equal(false); 65 | (s.apply([]) !== undefined).should.equal(false); 66 | (s.apply({id: 30,}) !== undefined).should.equal(false); 67 | (s.apply({id: 30,user: {id: NaN,}}) !== undefined).should.equal(false); 68 | 69 | (s.apply({id: 30,user: {id: 20,}}) !== undefined).should.equal(true); 70 | }); 71 | it('Structural(ADT) Array', function () { 72 | var s; 73 | 74 | var adt = [ 75 | { 76 | id: Number, 77 | user: { 78 | id: Number, 79 | }, 80 | }, 81 | { 82 | id: Number, 83 | data: { 84 | id: Number, 85 | }, 86 | nodata: TypeNull, 87 | }, 88 | ]; 89 | 90 | var customType = TypeADT(adt); 91 | 92 | s = new SumType(new ProductType(customType)); 93 | (s.apply(undefined) !== undefined).should.equal(false); 94 | (s.apply(null) !== undefined).should.equal(false); 95 | (s.apply([{id: 30,}]) !== undefined).should.equal(false); 96 | (s.apply([{id: 30,user: {id: NaN,}}]) !== undefined).should.equal(false); 97 | (s.apply([{id: 30,newdata: {id: 20,}}, {id: 30,user: {id: 20,}}]) !== undefined).should.equal(false); 98 | (s.apply([{id: 30,data: {id: 20,}, nodata: 33}, {id: 30,user: {id: 20,}}]) !== undefined).should.equal(false); 99 | 100 | (s.apply([]) !== undefined).should.equal(true); 101 | (s.apply([{id: 30,user: {id: 20,}}]) !== undefined).should.equal(true); 102 | (s.apply([{id: 30,data: {id: 20,}}, {id: 30,user: {id: 20,}}]) !== undefined).should.equal(true); 103 | }); 104 | it('Structural(ADT) Composite with rules', function () { 105 | var s; 106 | var adt; 107 | var customType; 108 | 109 | // Pattern 110 | customType = TypeADT({ 111 | arrayWithOrder: TypeCompTypeMatchesWithSpread(new SumType(new ProductType(TypeString, TypeNumber))), 112 | }); 113 | s = new SumType(new ProductType(customType)); 114 | (s.apply(undefined) !== undefined).should.equal(false); 115 | (s.apply(null) !== undefined).should.equal(false); 116 | (s.apply([]) !== undefined).should.equal(false); 117 | (s.apply({arrayWithOrder: [12, "id"]}) !== undefined).should.equal(false); 118 | (s.apply({arrayWithOrder: ["id", 12]}) !== undefined).should.equal(true); 119 | 120 | // PatternMatching 121 | customType = TypeADT({ 122 | arrayWithOrder: new PatternMatching(TypeCompTypeMatchesWithSpread(new SumType(new ProductType(TypeString, TypeNumber))), otherwise((x)=>false)), 123 | }); 124 | s = new SumType(new ProductType(customType)); 125 | (s.apply(undefined) !== undefined).should.equal(false); 126 | (s.apply(null) !== undefined).should.equal(false); 127 | (s.apply([]) !== undefined).should.equal(false); 128 | (s.apply({arrayWithOrder: [12, "id"]}) !== undefined).should.equal(false); 129 | (s.apply({arrayWithOrder: ["id", 12]}) !== undefined).should.equal(true); 130 | 131 | // Function 132 | customType = TypeADT({ 133 | arrayWithOrder: (list) => new SumType(new ProductType(TypeString, TypeNumber)).matches(...list), 134 | }); 135 | s = new SumType(new ProductType(customType)); 136 | (s.apply(undefined) !== undefined).should.equal(false); 137 | (s.apply(null) !== undefined).should.equal(false); 138 | (s.apply([]) !== undefined).should.equal(false); 139 | (s.apply({arrayWithOrder: [12, "id"]}) !== undefined).should.equal(false); 140 | (s.apply({arrayWithOrder: ["id", 12]}) !== undefined).should.equal(true); 141 | 142 | // CompType 143 | customType = TypeADT({ 144 | data: new SumType(new ProductType(TypeString)), 145 | }); 146 | s = new SumType(new ProductType(customType)); 147 | (s.apply(undefined) !== undefined).should.equal(false); 148 | (s.apply(null) !== undefined).should.equal(false); 149 | (s.apply([]) !== undefined).should.equal(false); 150 | (s.apply({data: [12, "id"]}) !== undefined).should.equal(false); 151 | 152 | (s.apply({data: "the string"}) !== undefined).should.equal(true); 153 | }); 154 | }); 155 | describe('pattern', function () { 156 | it('PatternMatching', function () { 157 | new PatternMatching(inCaseOfObject((x)=>JSON.stringify(x)), otherwise((x)=>false)).matchFor({}).should.equal("{}"); 158 | new PatternMatching(inCaseOfObject((x)=>JSON.stringify(x)), otherwise((x)=>false)).matchFor([]).should.equal(false); 159 | }); 160 | it('inCaseOfObject', function () { 161 | either({}, inCaseOfObject((x)=>JSON.stringify(x)), otherwise((x)=>false)).should.equal("{}"); 162 | either([], inCaseOfObject((x)=>JSON.stringify(x)), otherwise((x)=>false)).should.equal(false); 163 | either(null, inCaseOfObject((x)=>JSON.stringify(x)), otherwise((x)=>false)).should.equal(false); 164 | either(undefined, inCaseOfObject((x)=>JSON.stringify(x)), otherwise((x)=>false)).should.equal(false); 165 | either('', inCaseOfObject((x)=>JSON.stringify(x)), otherwise((x)=>false)).should.equal(false); 166 | either(0, inCaseOfObject((x)=>JSON.stringify(x)), otherwise((x)=>false)).should.equal(false); 167 | }); 168 | it('inCaseOfRegex', function () { 169 | either('ccc', inCaseOfRegex('c+', (x)=>true), otherwise((x)=>false)).should.equal(true); 170 | either('ccc', inCaseOfRegex(/c+/, (x)=>true), otherwise((x)=>false)).should.equal(true); 171 | either('', inCaseOfRegex('c+', (x)=>true), otherwise((x)=>false)).should.equal(false); 172 | }); 173 | it('inCaseOfNull', function () { 174 | either(null, inCaseOfNull((x)=>true), otherwise((x)=>false)).should.equal(true); 175 | either(undefined, inCaseOfNull((x)=>true), otherwise((x)=>false)).should.equal(true); 176 | either('', inCaseOfNull((x)=>true), otherwise((x)=>false)).should.equal(false); 177 | either(0, inCaseOfNull((x)=>true), otherwise((x)=>false)).should.equal(false); 178 | }); 179 | it('inCaseOfArray', function () { 180 | either([], inCaseOfArray((x)=>JSON.stringify(x)), otherwise((x)=>false)).should.equal("[]"); 181 | either({}, inCaseOfArray((x)=>JSON.stringify(x)), otherwise((x)=>false)).should.equal(false); 182 | }); 183 | it('inCaseOfClass', function () { 184 | either(new Number(1), inCaseOfClass(Number, (x)=>x+1), otherwise((x)=>x+2)).should.equal(2); 185 | either(new String("1"), inCaseOfClass(String, (x)=>x), otherwise((x)=>false)).should.equal("1"); 186 | 187 | either(new String("1"), inCaseOfClass(Number, (x)=>x), otherwise((x)=>false)).should.equal(false); 188 | either(new Number(1), inCaseOfClass(String, (x)=>x+1), otherwise((x)=>false)).should.equal(false); 189 | }); 190 | it('inCaseOfNumber', function () { 191 | either(new Number(1), inCaseOfNumber((x)=>x+1), otherwise((x)=>x+2)).should.equal(2); 192 | either(1, inCaseOfNumber((x)=>x+1), otherwise((x)=>x+2)).should.equal(2); 193 | either("1", inCaseOfNumber((x)=>x+1), otherwise((x)=>x+2)).should.equal(2); 194 | either('', inCaseOfNumber((x)=>x+1), otherwise((x)=>3)).should.equal(3); 195 | either(' ', inCaseOfNumber((x)=>x+1), otherwise((x)=>3)).should.equal(3); 196 | either(NaN, inCaseOfNumber((x)=>x+1), otherwise((x)=>3)).should.equal(3); 197 | }); 198 | it('inCaseOfString', function () { 199 | either("1", inCaseOfString((x)=>true), otherwise((x)=>false)).should.equal(true); 200 | either(1, inCaseOfString((x)=>true), otherwise((x)=>false)).should.equal(false); 201 | }); 202 | it('inCaseOfNaN', function () { 203 | either(1, inCaseOfNaN((x)=>true), otherwise((x)=>false)).should.equal(false); 204 | either("1", inCaseOfNaN((x)=>true), otherwise((x)=>false)).should.equal(false); 205 | either(NaN, inCaseOfNaN((x)=>true), otherwise((x)=>false)).should.equal(true); 206 | }); 207 | it('otherwise', function () { 208 | var err = undefined; 209 | 210 | err = undefined; 211 | either(1, inCaseOfEqual(1, (x)=>x+1)).should.equal(2); 212 | (err !== undefined).should.equal(false); 213 | 214 | err = undefined; 215 | try { 216 | either(1, inCaseOfEqual(2, (x)=>x+1)); 217 | } catch (e) { 218 | err = e; 219 | } 220 | (err !== undefined).should.equal(true); 221 | 222 | err = undefined; 223 | try { 224 | either(1, inCaseOfEqual(2, (x)=>x+1), otherwise((x)=>x+2)).should.equal(3); 225 | } catch (e) { 226 | err = e; 227 | console.log(e); 228 | } 229 | (err !== undefined).should.equal(false); 230 | }); 231 | }) 232 | -------------------------------------------------------------------------------- /test/publisher.js: -------------------------------------------------------------------------------- 1 | import Publisher from '../publisher'; 2 | 3 | describe('Publisher', function () { 4 | it('sync', function () { 5 | var p = new Publisher(); 6 | var v = 0; 7 | 8 | v = 0; 9 | p.subscribe((i)=>v=i); 10 | 11 | p.publish(1); 12 | v.should.equal(1) 13 | // Clear 14 | p.clear() 15 | p.publish(2); 16 | v.should.equal(1) 17 | }); 18 | it('async', function (done) { 19 | var p = new Publisher(); 20 | var v = 0; 21 | 22 | v = 0; 23 | p.subscribe((i)=>v=i); 24 | p.publish(1, true); 25 | v.should.equal(0) 26 | 27 | this.timeout(500); 28 | setTimeout(()=>{ 29 | v.should.equal(1) 30 | done(); 31 | },30); 32 | }); 33 | it('unsubscribe', function () { 34 | var p = new Publisher(); 35 | var v = 0; 36 | var callback = (i)=>v=i; 37 | 38 | v = 0; 39 | p.subscribe(callback); 40 | p.publish(1); 41 | v.should.equal(1) 42 | v = 0; 43 | p.unsubscribe(callback); 44 | p.publish(1); 45 | v.should.equal(0) 46 | }); 47 | it('map', function (done) { 48 | var p = new Publisher(); 49 | var v = 0; 50 | 51 | v = 0; 52 | p.map((x)=>x+2).map((x)=>x+3).subscribe((i)=>v=i); 53 | p.publish(1, true); 54 | v.should.equal(0); 55 | 56 | this.timeout(500); 57 | setTimeout(()=>{ 58 | v.should.equal(6) 59 | done(); 60 | },30); 61 | }); 62 | }) 63 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const CompressionWebpackPlugin = require('compression-webpack-plugin') 2 | const productionGzipExtensions = /\.(js|css|json|txt|html|ico|svg)(\?.*)?$/i 3 | const zopfli = require('@gfx/zopfli') 4 | const BrotliPlugin = require('brotli-webpack-plugin') 5 | 6 | const dynamicPluginList = [ 7 | new CompressionWebpackPlugin({ 8 | algorithm(input, compressionOptions, callback) { 9 | return zopfli.gzip(input, compressionOptions, callback) 10 | }, 11 | compressionOptions: { 12 | numiterations: 15 13 | }, 14 | minRatio: 0.99, 15 | test: productionGzipExtensions 16 | }), 17 | new BrotliPlugin({ 18 | test: productionGzipExtensions, 19 | minRatio: 0.99 20 | }) 21 | ] 22 | 23 | module.exports = (env, argv) => { 24 | return { 25 | entry: './index.js', 26 | output: { 27 | filename: argv.mode === 'production' ? './bundle.min.js' : './bundle.js', 28 | library: 'fpEs', 29 | chunkFormat: 'array-push', 30 | }, 31 | mode: argv.mode === 'production' ? 'production' : 'development', 32 | module: { 33 | rules: [ 34 | { 35 | test: /\.js$/, 36 | exclude: /node_modules/, 37 | use: [ 38 | 'babel-loader', 39 | ], 40 | }, 41 | ] 42 | }, 43 | target: ['es5'], 44 | plugins: dynamicPluginList, 45 | }; 46 | }; 47 | -------------------------------------------------------------------------------- /webpack.custom.config.js: -------------------------------------------------------------------------------- 1 | const CompressionWebpackPlugin = require('compression-webpack-plugin') 2 | const productionGzipExtensions = /\.(js|css|json|txt|html|ico|svg)(\?.*)?$/i 3 | const zopfli = require('@gfx/zopfli') 4 | const BrotliPlugin = require('brotli-webpack-plugin') 5 | 6 | const dynamicPluginList = [ 7 | new CompressionWebpackPlugin({ 8 | algorithm(input, compressionOptions, callback) { 9 | return zopfli.gzip(input, compressionOptions, callback) 10 | }, 11 | compressionOptions: { 12 | numiterations: 15 13 | }, 14 | minRatio: 0.99, 15 | test: productionGzipExtensions 16 | }), 17 | new BrotliPlugin({ 18 | test: productionGzipExtensions, 19 | minRatio: 0.99 20 | }) 21 | ] 22 | 23 | module.exports = (env, argv) => { 24 | let filename = env.filename; 25 | let targetFilename = encodeURIComponent(filename.substring(filename.lastIndexOf('/')+1)); 26 | targetFilename = targetFilename.substring(0, targetFilename.lastIndexOf('.')); 27 | 28 | return { 29 | entry: './' + filename, 30 | output: { 31 | filename: './' + targetFilename + (argv.mode === 'production' ? '.min' : '') + '.js', 32 | library: targetFilename, 33 | chunkFormat: 'array-push', 34 | }, 35 | mode: argv.mode === 'production' ? 'production' : 'development', 36 | module: { 37 | rules: [ 38 | { 39 | test: /\.js$/, 40 | exclude: /node_modules/, 41 | use: [ 42 | 'babel-loader', 43 | ], 44 | }, 45 | ] 46 | }, 47 | target: ['es5'], 48 | plugins: dynamicPluginList, 49 | }; 50 | }; 51 | --------------------------------------------------------------------------------