├── .all-contributorsrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.yml ├── .github └── stale.yml ├── .gitignore ├── .npmignore ├── .quokka ├── .travis.yml ├── .vscode └── settings.json ├── CNAME ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── _internal ├── __tests__ │ ├── call.test.js │ ├── iterableSerialReduce.test.js │ └── iterableSerialReduceWhile.test.js ├── call.js ├── debug │ └── signature.js ├── escapeRegExp.js ├── isFunctor.js ├── isIterable.js ├── isThenable.js ├── iterableSerialReduce.js ├── iterableSerialReduceWhile.js └── maybeExec.js ├── bin └── patch-readme │ ├── index.mjs │ ├── interop │ └── fs.mjs │ ├── main.mjs │ └── markdown.mjs ├── combinators ├── I.js ├── I.mdx ├── K.js ├── K.mdx ├── S.js ├── S.mdx ├── W.js ├── W.mdx └── __tests__ │ ├── I.test.js │ ├── K.test.js │ ├── S.test.js │ └── W.test.js ├── console ├── __tests__ │ ├── error.test.js │ ├── log.test.js │ └── logF.test..js ├── error.js ├── error.mdx ├── log.js ├── log.mdx ├── logF.js └── logF.mdx ├── core ├── __tests__ │ ├── after.test.js │ ├── into.test.js │ ├── pipeR.test.js │ └── run.test.js ├── after.js ├── into.js ├── into.mdx ├── pipe.mdx ├── pipe │ ├── __tests__ │ │ ├── pipe.test.js │ │ └── sync.test.js │ ├── index.js │ └── sync.js ├── pipeR.js ├── pipeR.mdx ├── run.js └── run.mdx ├── docz └── getting-started.mdx ├── doczrc.js ├── examples ├── async-simple │ ├── .eslintignore │ ├── .eslintrc.yml │ ├── .vscode │ │ └── settings.json │ ├── README.md │ ├── index.mjs │ ├── package-lock.json │ └── package.json ├── conditionals │ ├── .eslintignore │ ├── .eslintrc.yml │ ├── .vscode │ │ └── settings.json │ ├── README.md │ ├── cond.mjs │ ├── cond2.mjs │ ├── ifElse.mjs │ ├── package-lock.json │ └── package.json ├── express-hello-world │ ├── .eslintignore │ ├── .eslintrc.yml │ ├── .vscode │ │ └── settings.json │ ├── README.md │ ├── index.mjs │ ├── interop │ │ └── express.mjs │ ├── main.mjs │ ├── package-lock.json │ └── package.json ├── express-static-files │ ├── .eslintignore │ ├── .eslintrc.yml │ ├── .vscode │ │ └── settings.json │ ├── README.md │ ├── index.mjs │ ├── interop │ │ └── express.mjs │ ├── main.mjs │ ├── package-lock.json │ ├── package.json │ └── public │ │ ├── c64.gif │ │ └── mojo.jpg ├── fizz-buzz │ ├── .eslintignore │ ├── .eslintrc.yml │ ├── .vscode │ │ └── settings.json │ ├── README.md │ ├── index.mjs │ ├── main.mjs │ ├── package-lock.json │ └── package.json ├── hello-world │ ├── .eslintignore │ ├── .eslintrc.yml │ ├── .vscode │ │ └── settings.json │ ├── README.md │ ├── index.mjs │ ├── package-lock.json │ └── package.json ├── map-filter-reduce │ ├── .eslintignore │ ├── .eslintrc.yml │ ├── .gitignore │ ├── .vscode │ │ └── settings.json │ ├── README.md │ ├── __tests__ │ │ └── main.test.mjs │ ├── index.mjs │ ├── lib │ │ ├── __tests__ │ │ │ └── math.test.mjs │ │ └── math.mjs │ ├── main.mjs │ ├── package-lock.json │ └── package.json ├── parcel-hello-world │ ├── .eslintignore │ ├── .eslintrc.yml │ ├── .gitignore │ ├── .vscode │ │ └── settings.json │ ├── README.md │ ├── index.html │ ├── index.js │ ├── interop │ │ └── dom.js │ ├── main.js │ ├── package-lock.json │ └── package.json ├── recursion │ ├── .eslintignore │ ├── .eslintrc.yml │ ├── README.md │ ├── index.mjs │ ├── package-lock.json │ └── package.json └── star-wars-console │ ├── .eslintignore │ ├── .eslintrc.yml │ ├── .vscode │ └── settings.json │ ├── README.md │ ├── api.mjs │ ├── index.mjs │ ├── interop │ └── readline.mjs │ ├── main.mjs │ ├── package-lock.json │ └── package.json ├── function ├── __tests__ │ ├── curry.test.js │ ├── lift.test.js │ ├── liftA.test.js │ ├── liftP.test.js │ ├── swap.test.js │ └── tap.test.js ├── curry.js ├── curry.mdx ├── lift.js ├── lift.mdx ├── liftA.js ├── liftP.js ├── swap.js ├── swap.mdx ├── tap.js └── tap.mdx ├── index.js ├── index.mdx ├── list ├── __tests__ │ ├── ap.test.js │ ├── filter.test.js │ ├── flatMap.test.js │ ├── head.test.js │ ├── join.test.js │ ├── map.test.js │ ├── range.test.js │ ├── reduce.test.js │ ├── reduceWhile.test.js │ ├── sort.test.js │ └── tail.test.js ├── ap.js ├── ap.mdx ├── filter.js ├── filter.mdx ├── flatMap.js ├── flatMap.mdx ├── head.js ├── head.mdx ├── join.js ├── map.js ├── map.mdx ├── range.js ├── range.mdx ├── reduce.js ├── reduce.mdx ├── reduceWhile.js ├── reduceWhile.mdx ├── sort.js ├── sort.mdx ├── tail.js └── tail.mdx ├── logic ├── __tests__ │ ├── allPass.test.js │ ├── anyPass.test.js │ ├── cond.test.js │ ├── ifElse.test.js │ ├── ifError.test.js │ ├── unless.test.js │ └── when.test.js ├── allPass.js ├── allPass.mdx ├── anyPass.js ├── anyPass.mdx ├── cond.js ├── cond.mdx ├── ifElse.js ├── ifElse.mdx ├── ifError.js ├── ifError.mdx ├── unless.js ├── unless.mdx ├── when.js └── when.mdx ├── media ├── .DS_Store ├── MS_logo.psd ├── MS_logo_1200.png ├── MS_logo_200.png ├── MS_logo_400.png ├── MS_logo_64.png └── MS_logo_800.png ├── net ├── __tests__ │ └── axios.tests.js └── axios.js ├── object ├── __tests__ │ ├── clone.test.js │ ├── pathOr.test.js │ └── propOr.test.js ├── clone.js ├── pathOr.js └── propOr.js ├── package-lock.json ├── package.json ├── string ├── __tests__ │ ├── append.test.js │ ├── prepend.test.js │ ├── replace.test.js │ └── template.test.js ├── append.js ├── append.mdx ├── prepend.js ├── prepend.mdx ├── replace.js ├── replace.mdx ├── template.js └── template.mdx ├── styleguide.mdx ├── threading ├── __tests__ │ └── sleep.test.js ├── sleep.js └── sleep.mdx └── type ├── Either.js ├── Just.js ├── Left.js ├── Maybe.js ├── Nothing.js ├── Right.js ├── __tests__ ├── Either.test.js ├── Just.test.js ├── Left.test.js ├── Maybe.test.js ├── Nothing.test.js ├── Right.test.js └── is.test.js ├── _allTypes.js ├── is.js └── is.mdx /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | trim_trailing_whitespace = true 7 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | .docz/ 2 | coverage/ 3 | examples/ 4 | docs/ 5 | interop/ 6 | static/ -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | es6: true 3 | node: true 4 | jest: true 5 | browser: false 6 | extends: airbnb-base 7 | parserOptions: 8 | ecmaVersion: 2018 9 | rules: 10 | indent: 11 | - error 12 | - 2 13 | - { flatTernaryExpressions: true } 14 | linebreak-style: 15 | - error 16 | - unix 17 | quotes: 18 | - error 19 | - single 20 | semi: 21 | - error 22 | - never 23 | comma-dangle: 24 | - error 25 | - never 26 | prefer-template: error 27 | prefer-promise-reject-errors: off 28 | array-bracket-spacing: 29 | - error 30 | - always 31 | camelcase: warn 32 | no-confusing-arrow: off 33 | no-useless-escape: warn 34 | no-nested-ternary: off 35 | no-sequences: off 36 | no-plusplus: off 37 | no-shadow: off 38 | implicit-arrow-linebreak: off 39 | import/no-extraneous-dependencies: warn 40 | import/prefer-default-export: off 41 | max-len: warn 42 | arrow-parens: [error, as-needed] 43 | prefer-object-spread: off -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-stale - https://github.com/probot/stale 2 | 3 | # Number of days of inactivity before an Issue or Pull Request becomes stale 4 | daysUntilStale: 60 5 | 6 | # Number of days of inactivity before an Issue or Pull Request with the stale label is closed. 7 | # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. 8 | daysUntilClose: 7 9 | 10 | # Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable 11 | exemptLabels: 12 | - pinned 13 | - security 14 | - "[Status] Maybe Later" 15 | 16 | # Set to true to ignore issues in a project (defaults to false) 17 | exemptProjects: false 18 | 19 | # Set to true to ignore issues in a milestone (defaults to false) 20 | exemptMilestones: false 21 | 22 | # Label to use when marking as stale 23 | staleLabel: stale 24 | 25 | # Comment to post when marking as stale. Set to `false` to disable 26 | markComment: > 27 | This issue has been automatically marked as stale because it has not had 28 | recent activity. It will be closed if no further activity occurs. Thank you 29 | for your contributions. 30 | 31 | # Comment to post when removing the stale label. 32 | # unmarkComment: > 33 | # Your comment here. 34 | 35 | # Comment to post when closing a stale Issue or Pull Request. 36 | # closeComment: > 37 | # Your comment here. 38 | 39 | # Limit the number of actions per hour, from 1-30. Default is 30 40 | limitPerRun: 30 41 | 42 | # Limit to only `issues` or `pulls` 43 | # only: issues 44 | 45 | # Optionally, specify configuration settings that are specific to just 'issues' or 'pulls': 46 | # pulls: 47 | # daysUntilStale: 30 48 | # markComment: > 49 | # This pull request has been automatically marked as stale because it has not had 50 | # recent activity. It will be closed if no further activity occurs. Thank you 51 | # for your contributions. 52 | 53 | # issues: 54 | # exemptLabels: 55 | # - confirmed -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .docz/ 2 | coverage/ 3 | node_modules/ 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .docz/ 2 | .lib/ 3 | **/.vscode/ 4 | bin/ 5 | coverage/ 6 | docs/ 7 | examples/ 8 | **/__tests__/ 9 | **/node_modules/ 10 | .all-contributorsrc 11 | .editorconfig 12 | .eslintignore 13 | .eslintrc.yml 14 | .travis.yml 15 | -------------------------------------------------------------------------------- /.quokka: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "params": { 4 | "env": "MOJI_DEBUG=true" 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "10.14.2" 4 | 5 | addons: 6 | ssh_known_hosts: 7 | - github.com 8 | 9 | stages: 10 | - build 11 | - test 12 | - name: deploy 13 | if: (NOT type IN (pull_request)) AND (branch = master) 14 | 15 | jobs: 16 | include: 17 | - stage: build 18 | script: 19 | - npm run build 20 | - stage: test 21 | script: 22 | - npm run test:coveralls 23 | - stage: deploy 24 | script: 25 | - echo -e "//registry.npmjs.org/:_authToken=$NPM_AUTH_TOKEN\n" > ~/.npmrc 26 | - echo -e "machine github.com\n login $CI_USER\n password $CI_USER_PASSWORD\n" > ~/.netrc 27 | - npm version patch -m "Version %s [skip ci]" 28 | - git push origin HEAD:master 29 | - git push --tags 30 | - npm run patch-readme --silent > NEW-README.md 31 | - mv NEW-README.md README.md 32 | - npm publish 33 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": false, 3 | "editor.codeActionsOnSave": { 4 | "source.fixAll.eslint": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | mojiscript.js.org 2 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at jthoms+github@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 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 | -------------------------------------------------------------------------------- /_internal/__tests__/call.test.js: -------------------------------------------------------------------------------- 1 | const apply = require('../call') 2 | 3 | describe('function/call', () => { 4 | const increase = num => num + 1 5 | 6 | test('sync', () => { 7 | const expected = 888 8 | const actual = apply(increase)(887) 9 | expect(actual).toBe(expected) 10 | }) 11 | 12 | test('async', () => { 13 | const expected = 888 14 | const actual = apply(increase)(Promise.resolve(887)) 15 | expect(actual).resolves.toBe(expected) 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /_internal/__tests__/iterableSerialReduce.test.js: -------------------------------------------------------------------------------- 1 | const iterableSerialReduce = require('../iterableSerialReduce') 2 | 3 | describe('internal/iterableSerialReduce', () => { 4 | const add = (x, y) => x + y 5 | const asyncAdd = (x, y) => Promise.resolve().then(() => x + y) 6 | function* iterator() { 7 | yield 1 8 | yield 2 9 | yield 3 10 | } 11 | 12 | test('sync array', () => { 13 | const expected = 6 14 | const actual = iterableSerialReduce(add, 0, [ 1, 2, 3 ]) 15 | expect(actual).resolves.toBe(expected) 16 | }) 17 | 18 | test('sync array 2', () => { 19 | const expected = 6 20 | const actual = iterableSerialReduce(add, null, [ 2, 3 ], Promise.resolve(1)) 21 | expect(actual).resolves.toBe(expected) 22 | }) 23 | 24 | test('sync iterator', () => { 25 | const expected = 6 26 | const actual = iterableSerialReduce(add, 0, iterator()) 27 | expect(actual).resolves.toBe(expected) 28 | }) 29 | 30 | test('async array', () => { 31 | const expected = 6 32 | const actual = iterableSerialReduce(asyncAdd, 0, [ 1, 2, 3 ]) 33 | expect(actual).resolves.toBe(expected) 34 | }) 35 | 36 | test('async iterator', () => { 37 | const expected = 6 38 | const actual = iterableSerialReduce(asyncAdd, 0, iterator()) 39 | expect(actual).resolves.toBe(expected) 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /_internal/__tests__/iterableSerialReduceWhile.test.js: -------------------------------------------------------------------------------- 1 | const iterableSerialReduceWhile = require('../iterableSerialReduceWhile') 2 | 3 | describe('internal/iterableSerialReduceWhile', () => { 4 | const add = (x, y) => x + y 5 | const asyncAdd = (x, y) => Promise.resolve().then(() => x + y) 6 | const predicate = acc => () => acc <= 2 7 | function* iterator() { 8 | yield 1 9 | yield 2 10 | yield 3 11 | } 12 | 13 | test('sync array', () => { 14 | const expected = 3 15 | const actual = iterableSerialReduceWhile(predicate, add, 0, [ 1, 2, 3 ]) 16 | expect(actual).resolves.toBe(expected) 17 | }) 18 | 19 | test('sync array 2', () => { 20 | const expected = 3 21 | const actual = iterableSerialReduceWhile(predicate, add, null, [ 2, 3 ], Promise.resolve(1)) 22 | expect(actual).resolves.toBe(expected) 23 | }) 24 | 25 | test('sync iterator', () => { 26 | const expected = 3 27 | const actual = iterableSerialReduceWhile(predicate, add, 0, iterator()) 28 | expect(actual).resolves.toBe(expected) 29 | }) 30 | 31 | test('async array', () => { 32 | const expected = 3 33 | const actual = iterableSerialReduceWhile(predicate, asyncAdd, 0, [ 1, 2, 3 ]) 34 | expect(actual).resolves.toBe(expected) 35 | }) 36 | 37 | test('async iterator', () => { 38 | const expected = 3 39 | const actual = iterableSerialReduceWhile(predicate, asyncAdd, 0, iterator()) 40 | expect(actual).resolves.toBe(expected) 41 | }) 42 | }) 43 | -------------------------------------------------------------------------------- /_internal/call.js: -------------------------------------------------------------------------------- 1 | const isThenable = require('./isThenable') 2 | 3 | /* 4 | * Takes a value or a Promise and applies func to it. 5 | */ 6 | // call :: Function -> Any -> Any | Promise 7 | const call = func => value => 8 | isThenable(value) 9 | ? value.then(func) 10 | : func(value) 11 | 12 | module.exports = call 13 | -------------------------------------------------------------------------------- /_internal/debug/signature.js: -------------------------------------------------------------------------------- 1 | const isFunction = value => typeof value === 'function' 2 | const isArray = value => value != null && (value.constructor === Array) 3 | const isNumber = value => value != null && (value.constructor === Number) 4 | const isString = value => value != null && (value.constructor === String) 5 | const isObject = value => value != null && (value.constructor === Object) 6 | 7 | const simplifyValue = value => 8 | value === null ? 'null' 9 | : isNumber(value) || isString(value) ? JSON.stringify(value) 10 | : isArray(value) && value.length < 10 ? `[${value.join(',')}]` 11 | : isArray(value) ? `Array(${value.length})` 12 | : isObject(value) && isFunction(value.inspect) ? value.inspect() 13 | : isFunction(value) ? value.name || 'function' 14 | : typeof value 15 | 16 | const parseSignature = signature => { 17 | const split = /(\w+)\s*::\s*(.*)/.exec(signature) 18 | const args = split[2].split('->').map(x => x.trim()) // ? 19 | return { 20 | method: split[1], 21 | args: args.slice(0, args.length - 1), 22 | returnType: args[args.length - 1] 23 | } 24 | } 25 | 26 | /* 27 | { 28 | method: "add", 29 | types: [], 30 | target: null, 31 | args: [] 32 | returnType: null 33 | } 34 | */ 35 | 36 | const internalSignature = ({ method, args, returnType }) => func => { 37 | const name = method || func.name || 'anonymous' 38 | 39 | // temp object is used to create a named function. 40 | const temp = { 41 | [name]: value => { 42 | Object.defineProperty(func, 'name', { value: `${name} (${simplifyValue(value)})` }) 43 | if (args.length <= 1) { 44 | return func(value) 45 | } 46 | return internalSignature({ method: `${name} (${simplifyValue(value)})`, args: args.slice(1), returnType })(func(value)) 47 | } 48 | } 49 | temp[name].inspect = () => `${name} :: ${args.join(' -> ')} -> ${returnType}` 50 | 51 | return temp[name] 52 | } 53 | 54 | const signature = signature => 55 | isString(signature) 56 | ? internalSignature(parseSignature(signature)) 57 | : internalSignature(signature) 58 | 59 | module.exports = signature 60 | -------------------------------------------------------------------------------- /_internal/escapeRegExp.js: -------------------------------------------------------------------------------- 1 | /* from: https://stackoverflow.com/a/1144788/504836 */ 2 | 3 | const escapeRegExp = str => str.replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1') 4 | 5 | module.exports = escapeRegExp 6 | -------------------------------------------------------------------------------- /_internal/isFunctor.js: -------------------------------------------------------------------------------- 1 | const is = require('../type/is') 2 | 3 | const isFunction = is(Function) 4 | 5 | const isFunctor = functor => 6 | functor != null && (isFunction(functor['fantasy-land/map']) || isFunction(functor.map)) 7 | 8 | module.exports = isFunctor 9 | -------------------------------------------------------------------------------- /_internal/isIterable.js: -------------------------------------------------------------------------------- 1 | const is = require('../type/is') 2 | 3 | const isFunction = is(Function) 4 | 5 | const isIterable = iterable => 6 | iterable != null 7 | && (isFunction(iterable[Symbol.iterator]) || isFunction(iterable[Symbol.asyncIterator])) 8 | 9 | module.exports = isIterable 10 | -------------------------------------------------------------------------------- /_internal/isThenable.js: -------------------------------------------------------------------------------- 1 | const isThenable = obj => obj != null && typeof obj.then === 'function' 2 | 3 | module.exports = isThenable 4 | -------------------------------------------------------------------------------- /_internal/iterableSerialReduce.js: -------------------------------------------------------------------------------- 1 | const iterableSerialReduceWhile = require('./iterableSerialReduceWhile') 2 | 3 | const iterableSerialReduce = iterableSerialReduceWhile.bind(null, null) 4 | 5 | module.exports = iterableSerialReduce 6 | -------------------------------------------------------------------------------- /_internal/iterableSerialReduceWhile.js: -------------------------------------------------------------------------------- 1 | const is = require('../type/is') 2 | 3 | const isFunction = is(Function) 4 | 5 | const getIterator = iterable => 6 | isFunction(iterable.next) ? iterable 7 | : isFunction(iterable[Symbol.asyncIterator]) ? iterable[Symbol.asyncIterator]() 8 | : iterable[Symbol.iterator]() 9 | 10 | const iterableSerialReduceWhile = async ( 11 | predicate, 12 | func, 13 | initial, 14 | iterable, 15 | promise = Promise.resolve(initial) 16 | ) => { 17 | const iterator = getIterator(iterable) 18 | const acc = await promise 19 | const { value, done } = await iterator.next() 20 | return done || (predicate && !predicate(acc)(value)) 21 | ? acc 22 | : iterableSerialReduceWhile( 23 | predicate, 24 | func, 25 | initial, 26 | iterator, 27 | promise.then(acc => func(acc, value)) 28 | ) 29 | } 30 | 31 | module.exports = iterableSerialReduceWhile 32 | -------------------------------------------------------------------------------- /_internal/maybeExec.js: -------------------------------------------------------------------------------- 1 | const is = require('../type/is') 2 | 3 | const isFunction = is(Function) 4 | 5 | const maybeExec = maybeFunc => value => (isFunction(maybeFunc) ? maybeFunc(value) : maybeFunc) 6 | 7 | module.exports = maybeExec 8 | -------------------------------------------------------------------------------- /bin/patch-readme/index.mjs: -------------------------------------------------------------------------------- 1 | import log from '../../console/log' 2 | import run from '../../core/run' 3 | import { readFileUtf8 } from './interop/fs' 4 | import main from './main' 5 | 6 | const dependencies = { 7 | baseUrl: 'https://github.com/joelnet/MojiScript/tree/master', 8 | readFile: readFileUtf8, 9 | log 10 | } 11 | const state = `${process.cwd()}/README.md` 12 | 13 | run({ dependencies, state, main }) 14 | -------------------------------------------------------------------------------- /bin/patch-readme/interop/fs.mjs: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import { promisify } from 'util' 3 | import curry from '../../../function/curry' 4 | 5 | export const readFile = curry (2) (promisify (fs.readFile)) 6 | 7 | export const readFileUtf8 = file => readFile (file) ('utf8') 8 | -------------------------------------------------------------------------------- /bin/patch-readme/main.mjs: -------------------------------------------------------------------------------- 1 | import pipe from '../../core/pipe' 2 | import { getInternalLinks, prependAllLinks } from './markdown' 3 | import W from '../../combinators/W' 4 | 5 | const prependLinksInReadme = baseUrl => W(readme => pipe([ 6 | getInternalLinks, 7 | prependAllLinks(baseUrl)(readme) 8 | ])) 9 | 10 | const main = ({ log, readFile, baseUrl }) => pipe([ 11 | readFile, 12 | prependLinksInReadme(baseUrl), 13 | log 14 | ]) 15 | 16 | export default main 17 | -------------------------------------------------------------------------------- /bin/patch-readme/markdown.mjs: -------------------------------------------------------------------------------- 1 | import S from 'sanctuary' // eslint-disable-line import/no-extraneous-dependencies 2 | import pipe from '../../core/pipe' 3 | import replace from '../../string/replace' 4 | 5 | const rxLink = /\[([^\]]*)]\(([^#][^)]*)\)/gi 6 | const rxExternalLink = /:\/\// 7 | const isInternalLink = ([ , link ]) => !S.test(rxExternalLink)(link) 8 | 9 | export const getAllLinks = pipe([ 10 | S.matchAll(rxLink), 11 | S.map(({ groups }) => S.justs(groups)) 12 | ]) 13 | 14 | export const getInternalLinks = pipe([ 15 | getAllLinks, 16 | S.filter(isInternalLink) 17 | ]) 18 | 19 | export const prependLink = baseUrl => document => ([ text, link ]) => replace(`[${text}](${link})`)(`[${text}](${baseUrl}/${link})`)(document) 20 | 21 | export const prependAllLinks = baseUrl => S.reduce(prependLink(baseUrl)) 22 | -------------------------------------------------------------------------------- /combinators/I.js: -------------------------------------------------------------------------------- 1 | const I = x => x 2 | 3 | module.exports = I 4 | -------------------------------------------------------------------------------- /combinators/I.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: I 3 | menu: combinators 4 | --- 5 | 6 | # combinators/I 7 | 8 | `I :: a -> a` 9 | 10 | `I` returns its argument 11 | 12 | ```javascript 13 | import { I } from 'mojiscript' 14 | 15 | I (888) //=> 888 16 | ``` 17 | -------------------------------------------------------------------------------- /combinators/K.js: -------------------------------------------------------------------------------- 1 | const K = x => () => x 2 | 3 | module.exports = K 4 | -------------------------------------------------------------------------------- /combinators/K.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: K 3 | menu: combinators 4 | --- 5 | 6 | # combinators/K 7 | 8 | `K :: a -> b -> a` 9 | 10 | `K`, when applied to any argument x, yields a one-argument constant function K (x), which, when applied to any argument, returns x 11 | 12 | ```javascript 13 | import { K } from 'mojiscript' 14 | 15 | const always = K (888) 16 | 17 | always () //=> 888 18 | ``` 19 | -------------------------------------------------------------------------------- /combinators/S.js: -------------------------------------------------------------------------------- 1 | const S = f => g => x => f(x)(g(x)) 2 | 3 | module.exports = S 4 | -------------------------------------------------------------------------------- /combinators/S.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: S 3 | menu: combinators 4 | --- 5 | 6 | # combinators/S 7 | 8 | `S :: Function -> Function -> Any -> Any` 9 | 10 | `S` is a substitution operator. It takes three arguments and then returns the first argument applied to the third, which is then applied to the result of the second argument applied to the third. 11 | 12 | ```javascript 13 | import { S } from './combinators/S' 14 | 15 | const add = x => y => x + y 16 | const multiply = x => y => x * y 17 | 18 | S (add) (multiply (3)) (10) //=> 40 19 | ``` 20 | -------------------------------------------------------------------------------- /combinators/W.js: -------------------------------------------------------------------------------- 1 | const W = f => x => f(x)(x) 2 | 3 | module.exports = W 4 | -------------------------------------------------------------------------------- /combinators/W.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: W 3 | menu: combinators 4 | --- 5 | 6 | # combinators/W 7 | 8 | `W :: Function -> Any -> Any` 9 | 10 | `W` x y duplicates the argument y. 11 | 12 | ```javascript 13 | import { W, pipe } from 'mojiscript' 14 | 15 | const func = W (v1 => pipe ([ 16 | v2 => console.log (v1 === v2) //=> true 17 | ])) 18 | 19 | func (888) 20 | ``` 21 | -------------------------------------------------------------------------------- /combinators/__tests__/I.test.js: -------------------------------------------------------------------------------- 1 | const I = require('../I') 2 | 3 | describe('combinators/I', () => { 4 | test('returns value', () => { 5 | const expected = 888 6 | const actual = I(expected) 7 | expect(actual).toBe(expected) 8 | }) 9 | }) 10 | -------------------------------------------------------------------------------- /combinators/__tests__/K.test.js: -------------------------------------------------------------------------------- 1 | const K = require('../K') 2 | 3 | describe('combinators/K', () => { 4 | test('returns value', () => { 5 | const expected = 888 6 | const actual = K(expected)(666) 7 | expect(actual).toBe(expected) 8 | }) 9 | }) 10 | -------------------------------------------------------------------------------- /combinators/__tests__/S.test.js: -------------------------------------------------------------------------------- 1 | const S = require('../S') 2 | 3 | describe('combinators/S', () => { 4 | test('returns value', () => { 5 | const expected = 30 6 | const f = a => b => a + b 7 | const g = a => a * 2 8 | const actual = S(f)(g)(10) 9 | expect(actual).toBe(expected) 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /combinators/__tests__/W.test.js: -------------------------------------------------------------------------------- 1 | const W = require('../W') 2 | 3 | describe('combinators/W', () => { 4 | test('returns value', () => { 5 | const expected = 888 6 | const f = a => b => a + b 7 | const actual = W(f)(444) 8 | expect(actual).toBe(expected) 9 | }) 10 | }) 11 | -------------------------------------------------------------------------------- /console/__tests__/error.test.js: -------------------------------------------------------------------------------- 1 | const error = require('../error') 2 | 3 | describe('console/error', () => { 4 | beforeEach(() => jest.spyOn(global.console, 'error').mockImplementation(() => {})) 5 | afterEach(() => global.console.error.mockReset()) 6 | 7 | test('calls console.error', () => { 8 | const expected = 888 9 | error(expected) 10 | const actual = global.console.error.mock.calls[0][0] 11 | expect(actual).toBe(expected) 12 | }) 13 | 14 | test('returns original value', () => { 15 | const expected = 888 16 | const actual = error(expected) 17 | expect(actual).toBe(expected) 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /console/__tests__/log.test.js: -------------------------------------------------------------------------------- 1 | const log = require('../log') 2 | 3 | describe('console/log', () => { 4 | beforeEach(() => jest.spyOn(global.console, 'log').mockImplementation(() => {})) 5 | afterEach(() => global.console.log.mockReset()) 6 | 7 | test('calls console.log', () => { 8 | const expected = 888 9 | log(expected) 10 | const actual = global.console.log.mock.calls[0][0] 11 | expect(actual).toBe(expected) 12 | }) 13 | 14 | test('returns original value', () => { 15 | const expected = 888 16 | const actual = log(expected) 17 | expect(actual).toBe(expected) 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /console/__tests__/logF.test..js: -------------------------------------------------------------------------------- 1 | const logF = require('../logF') 2 | 3 | describe('console/logF', () => { 4 | beforeEach(() => jest.spyOn(global.console, 'log').mockImplementation(() => {})) 5 | afterEach(() => global.console.log.mockReset()) 6 | 7 | const double = x => x * 2 8 | 9 | test('calls console.log', () => { 10 | const expected = 888 11 | logF(double)(444) 12 | const actual = global.console.log.mock.calls[0][0] 13 | expect(actual).toBe(expected) 14 | }) 15 | 16 | test('returns original value', () => { 17 | const expected = 888 18 | const actual = logF(double)(expected) 19 | expect(actual).toBe(expected) 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /console/error.js: -------------------------------------------------------------------------------- 1 | const signature = require('../_internal/debug/signature') 2 | const tap = require('../function/tap') 3 | 4 | // error :: a -> a 5 | const error = tap(x => console.error(x)) // eslint-disable-line no-console 6 | 7 | module.exports = error 8 | 9 | // Experimental debug code 10 | /* istanbul ignore next */ 11 | if (process.env.MOJI_DEBUG === 'true') { 12 | module.exports = signature('error :: a -> a')(error) 13 | } 14 | -------------------------------------------------------------------------------- /console/error.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: error 3 | menu: console 4 | --- 5 | 6 | # api/console/error 7 | 8 | `error :: a -> a` 9 | 10 | `error` sends value to `console.error` and then returns the value. 11 | 12 | ```javascript 13 | import { error, pipe } from 'mojiscript' 14 | 15 | const main = pipe ([ 16 | error, 17 | value => console.log (value) //=> "uh oh" 18 | ]) 19 | 20 | main ('uh oh') 21 | ``` 22 | -------------------------------------------------------------------------------- /console/log.js: -------------------------------------------------------------------------------- 1 | const signature = require('../_internal/debug/signature') 2 | const tap = require('../function/tap') 3 | 4 | // log :: a -> a 5 | const log = tap(x => console.log(x)) // eslint-disable-line no-console 6 | 7 | module.exports = log 8 | 9 | // Experimental debug code 10 | /* istanbul ignore next */ 11 | if (process.env.MOJI_DEBUG === 'true') { 12 | module.exports = signature('log :: a -> a')(log) 13 | } 14 | -------------------------------------------------------------------------------- /console/log.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: log 3 | menu: console 4 | --- 5 | 6 | # console/log 7 | 8 | `log :: a -> a` 9 | 10 | `log` sends value to `console.log` and then returns the value. 11 | 12 | ```javascript 13 | import { log, pipe } from 'mojiscript' 14 | 15 | const main = pipe ([ 16 | log, 17 | value => console.log (value) //=> "Hello World!" 18 | ]) 19 | 20 | main ('Hello World!') 21 | ``` 22 | -------------------------------------------------------------------------------- /console/logF.js: -------------------------------------------------------------------------------- 1 | const signature = require('../_internal/debug/signature') 2 | const tap = require('../function/tap') 3 | 4 | // logF :: Function -> a -> a 5 | const logF = func => tap(x => console.log(func(x))) // eslint-disable-line no-console 6 | 7 | module.exports = logF 8 | 9 | // Experimental debug code 10 | /* istanbul ignore next */ 11 | if (process.env.MOJI_DEBUG === 'true') { 12 | module.exports = signature('logF :: Function -> a -> a')(logF) 13 | } 14 | -------------------------------------------------------------------------------- /console/logF.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: logF 3 | menu: console 4 | --- 5 | 6 | # console/log 7 | 8 | `logF :: Function -> a -> a` 9 | 10 | `logF` transforms the value and sends it to `console.log` and then returns the pre-transformed value value. 11 | 12 | ```javascript 13 | import { logF, pipe } from 'mojiscript' 14 | 15 | const main = pipe ([ 16 | logF (x => x.toUpperCase ()), //=> "HELLO WORLD!" 17 | value => console.log (value) //=> "Hello World!" 18 | ]) 19 | 20 | main ('Hello World!') 21 | ``` 22 | -------------------------------------------------------------------------------- /core/__tests__/after.test.js: -------------------------------------------------------------------------------- 1 | const after = require('../after') 2 | 3 | describe('core/after', () => { 4 | test('value sent to f', async () => { 5 | expect.assertions(1) 6 | const expected = 888 7 | const f = jest.fn() 8 | const g = jest.fn() 9 | await after(f)(g)(expected) 10 | const actual = f.mock.calls[0][0] 11 | expect(actual).toBe(expected) 12 | }) 13 | 14 | test('value sent to g', async () => { 15 | expect.assertions(1) 16 | const expected = 888 17 | const f = jest.fn() 18 | const g = jest.fn() 19 | await after(f)(g)(expected) 20 | const actual = g.mock.calls[0][0] 21 | expect(actual).toBe(expected) 22 | }) 23 | 24 | test('returns g(x)', async () => { 25 | expect.assertions(1) 26 | const expected = 888 27 | const f = jest.fn() 28 | const g = x => x * 2 29 | const actual = after(f)(g)(444) 30 | expect(actual).resolves.toBe(expected) 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /core/__tests__/into.test.js: -------------------------------------------------------------------------------- 1 | const into = require('../into') 2 | 3 | describe('core/into', () => { 4 | test('injects into object', () => { 5 | expect.assertions(1) 6 | const expected = { one: 1, two: 2 } 7 | const actual = into('two')(obj => obj.one + 1)({ one: 1 }) 8 | return expect(actual).resolves.toMatchObject(expected) 9 | }) 10 | }) 11 | -------------------------------------------------------------------------------- /core/__tests__/pipeR.test.js: -------------------------------------------------------------------------------- 1 | const pipeR = require('../pipeR') 2 | 3 | describe('core/pipeR', () => { 4 | test('afd', () => { 5 | const expected = 10 6 | const func = pipeR(next => [ 7 | num => num < 10 ? next(num + 1) : num 8 | ]) 9 | 10 | const actual = func(0) 11 | expect(actual).resolves.toBe(expected) 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /core/__tests__/run.test.js: -------------------------------------------------------------------------------- 1 | const run = require('../run') 2 | 3 | describe('core/run', () => { 4 | test('runs main with no state', () => { 5 | const expected = undefined 6 | const main = jest.fn().mockImplementation(() => Promise.resolve()) 7 | run({ main }) 8 | const actual = main.mock.calls[0][0] 9 | return expect(actual).toBe(expected) 10 | }) 11 | 12 | test('with no state returns value', () => { 13 | expect.assertions(1) 14 | const expected = 888 15 | const main = jest.fn().mockImplementation(() => Promise.resolve(expected)) 16 | const actual = run({ main }) 17 | return expect(actual).resolves.toBe(expected) 18 | }) 19 | 20 | test('with options returns value', () => { 21 | expect.assertions(1) 22 | const expected = 888 23 | const state = expected 24 | const main = jest.fn().mockImplementation(s => Promise.resolve(s)) 25 | const actual = run({ state, main }) 26 | return expect(actual).resolves.toBe(expected) 27 | }) 28 | 29 | test('with dependencies sets dependencies', () => { 30 | expect.assertions(1) 31 | const expected = 888 32 | const dependencies = expected 33 | const main = jest.fn().mockImplementation( 34 | () => jest.fn().mockImplementation(() => Promise.resolve()) 35 | ) 36 | run({ dependencies, main }) 37 | const actual = main.mock.calls[0][0] 38 | return expect(actual).toBe(expected) 39 | }) 40 | 41 | test('with dependencies and state sets state', () => { 42 | expect.assertions(1) 43 | const expected = 888 44 | const state = expected 45 | const dependencies = 8 46 | const main = jest.fn().mockImplementation(() => Promise.resolve(expected)) 47 | const deps = jest.fn().mockImplementation(() => main) 48 | run({ dependencies, state, main: deps }) 49 | const actual = main.mock.calls[0][0] 50 | return expect(actual).toBe(expected) 51 | }) 52 | 53 | test('with dependencies and options returns value', () => { 54 | expect.assertions(1) 55 | const expected = 888 56 | const state = expected 57 | const dependencies = 8 58 | const main = jest.fn().mockImplementation(s => Promise.resolve(s)) 59 | const deps = jest.fn().mockImplementation(() => main) 60 | const actual = run({ dependencies, state, main: deps }) 61 | return expect(actual).resolves.toBe(expected) 62 | }) 63 | }) 64 | -------------------------------------------------------------------------------- /core/after.js: -------------------------------------------------------------------------------- 1 | const after = first => last => value => Promise.resolve(first(value)).then(() => last(value)) 2 | 3 | module.exports = after 4 | -------------------------------------------------------------------------------- /core/into.js: -------------------------------------------------------------------------------- 1 | // into :: String -> Function -> * -> * 2 | const into = prop => func => value => Promise.resolve(value) 3 | .then(func) 4 | .then(result => Object.assign({}, value, { [prop]: result })) 5 | 6 | module.exports = into 7 | -------------------------------------------------------------------------------- /core/into.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: into 3 | menu: core 4 | --- 5 | 6 | # core/into 7 | 8 | `into :: String -> Function -> Any` 9 | 10 | Executes `func(value)` and injects the return value into the `prop` of `value`. 11 | 12 | ## Example 13 | 14 | ```javascript 15 | import { log, into, pipe, run sleep } from 'mojiscript' 16 | 17 | const isEven = num => num % 2 === 0 18 | const isOdd = pipe ([ 19 | sleep (1000), 20 | isEven, 21 | value => !value 22 | ]) 23 | 24 | const state = { 25 | value: 5 26 | } 27 | 28 | const main = pipe ([ 29 | into ('isEven') (isEven), 30 | into ('isOdd') (isOdd), 31 | log 32 | ]) 33 | 34 | run ({ state, main }) 35 | // => { value: 5, isEven: false, isOdd: true } 36 | ``` 37 | 38 | ## Parameters 39 | 40 | | Name | Type | Description | 41 | | ---- | ---- | ----------- | 42 | | prop | `String` | Property to set in `value`. | 43 | | func | `Function(a -> b)` | A `Function` that takes `value` and returns the computed result | 44 | | value | `Object` | `Object` passed to `func` and `prop` is set into. | 45 | 46 | ## Returns 47 | 48 | Returns the input `Object` with the addition of `prop` attached set to the return value of `func(value)`. 49 | -------------------------------------------------------------------------------- /core/pipe.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: pipe 3 | menu: core 4 | --- 5 | 6 | # core/pipe 7 | 8 | `pipe :: [(a -> b)] -> Any -> Any` 9 | 10 | Pipe is an asynchronous function composer. 11 | 12 | ## Example 13 | 14 | ```javascript 15 | import { log, pipe, sleep, run } from 'mojiscript' 16 | 17 | const main = pipe ([ 18 | sleep (1000), 19 | () => log ('done') 20 | ]) 21 | 22 | run ({ main }) 23 | //=> 'done' 24 | ``` 25 | 26 | ## Parameters 27 | 28 | | Name | Type | Description | 29 | | ---- | ---- | ----------- | 30 | | funcs | `[(a -> b)]` | Array of `Function`s that takes `value` and returns the computed result | 31 | | value | `Object` | `Object` passed to `func` and `prop` is set into. | 32 | 33 | ## Returns 34 | 35 | Returns the result of the last function in the `pipe`. 36 | -------------------------------------------------------------------------------- /core/pipe/__tests__/pipe.test.js: -------------------------------------------------------------------------------- 1 | const pipe = require('../index') 2 | 3 | describe('core/pipe', () => { 4 | test('no arguments throws error', () => { 5 | expect.assertions(1) 6 | const actual = () => pipe() 7 | const expected = Error('pipe requires at least one argument') 8 | return expect(actual).toThrow(expected) 9 | }) 10 | 11 | test('async argument is primitive returns value', () => { 12 | expect.assertions(1) 13 | const actual = pipe([ Promise.resolve(888) ])(666) 14 | const expected = 888 15 | return expect(actual).resolves.toBe(expected) 16 | }) 17 | 18 | test('async argument returns last value', () => { 19 | expect.assertions(1) 20 | const actual = pipe([ Promise.resolve(666), 888 ])(-1) 21 | const expected = 888 22 | return expect(actual).resolves.toBe(expected) 23 | }) 24 | 25 | test('async executes function', () => { 26 | expect.assertions(1) 27 | const actual = pipe([ () => Promise.resolve(), () => 888 ])(-1) 28 | const expected = 888 29 | return expect(actual).resolves.toBe(expected) 30 | }) 31 | 32 | test('Promise as value', () => { 33 | expect.assertions(1) 34 | const actual = pipe([ Promise.resolve(888) ])(-1) 35 | const expected = 888 36 | return expect(actual).resolves.toBe(expected) 37 | }) 38 | 39 | test('function returns a Promise', () => { 40 | expect.assertions(1) 41 | const actual = pipe([ () => Promise.resolve(888) ])(-1) 42 | const expected = 888 43 | return expect(actual).resolves.toBe(expected) 44 | }) 45 | 46 | test('async exception throws', () => { 47 | expect.assertions(1) 48 | const actual = pipe([ 49 | () => Promise.resolve(), 50 | () => { throw Error('Catch me if you can!') } 51 | ])(-1) 52 | const expected = Error('Catch me if you can!') 53 | return expect(actual).rejects.toThrow(expected) 54 | }) 55 | 56 | test('reject value rejects', () => { 57 | expect.assertions(1) 58 | const actual = pipe([ () => Promise.reject(Error('Catch me if you can!')) ])(-1) 59 | const expected = Error('Catch me if you can!') 60 | return expect(actual).rejects.toThrow(expected) 61 | }) 62 | 63 | test('reject rejects', () => { 64 | expect.assertions(1) 65 | const actual = pipe([ () => Promise.reject(Error('Catch me if you can!')) ])(-1) 66 | const expected = Error('Catch me if you can!') 67 | return expect(actual).rejects.toThrow(expected) 68 | }) 69 | }) 70 | -------------------------------------------------------------------------------- /core/pipe/__tests__/sync.test.js: -------------------------------------------------------------------------------- 1 | const pipeSync = require('../sync') 2 | 3 | describe('core/pipe', () => { 4 | test('no arguments throws error', () => { 5 | expect.assertions(1) 6 | const actual = () => pipeSync() 7 | const expected = Error('pipe/sync requires at least one argument') 8 | return expect(actual).toThrow(expected) 9 | }) 10 | 11 | test('sync argument is primitive returns value', () => { 12 | expect.assertions(1) 13 | const actual = pipeSync([ 888 ])(666) 14 | const expected = 888 15 | return expect(actual).toBe(expected) 16 | }) 17 | 18 | test('sync argument returns last value', () => { 19 | expect.assertions(1) 20 | const actual = pipeSync([ 666, 888 ])(-1) 21 | const expected = 888 22 | return expect(actual).toBe(expected) 23 | }) 24 | 25 | test('sync executes function', () => { 26 | expect.assertions(1) 27 | const actual = pipeSync([ () => 888 ])(-1) 28 | const expected = 888 29 | return expect(actual).toBe(expected) 30 | }) 31 | 32 | test('sync exception rejects', () => { 33 | expect.assertions(1) 34 | const actual = () => pipeSync([ () => { 35 | throw Error('Catch me if you can!') 36 | } ])(-1) 37 | const expected = Error('Catch me if you can!') 38 | return expect(actual).toThrow(expected) 39 | }) 40 | }) 41 | -------------------------------------------------------------------------------- /core/pipe/index.js: -------------------------------------------------------------------------------- 1 | const maybeExec = require('../../_internal/maybeExec') 2 | 3 | const pipe = (funcs = []) => 4 | funcs.length === 0 5 | ? (() => { throw Error('pipe requires at least one argument') })() 6 | : value => 7 | funcs.reduce( 8 | (acc, func) => acc.then(maybeExec(func)), 9 | Promise.resolve(value) 10 | ) 11 | 12 | module.exports = pipe 13 | -------------------------------------------------------------------------------- /core/pipe/sync.js: -------------------------------------------------------------------------------- 1 | const maybeExec = require('../../_internal/maybeExec') 2 | 3 | const pipe = (funcs = []) => 4 | funcs.length === 0 5 | ? (() => { throw Error('pipe/sync requires at least one argument') })() 6 | : value => funcs.reduce( 7 | (acc, func) => maybeExec(func)(acc), 8 | value 9 | ) 10 | 11 | module.exports = pipe 12 | -------------------------------------------------------------------------------- /core/pipeR.js: -------------------------------------------------------------------------------- 1 | const maybeExec = require('../_internal/maybeExec') 2 | 3 | const pipeR = func => value => func(pipeR(func)).reduce( 4 | (acc, func) => acc.then(maybeExec(func)), 5 | Promise.resolve(value) 6 | ) 7 | 8 | module.exports = pipeR 9 | -------------------------------------------------------------------------------- /core/pipeR.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: pipeR 3 | menu: core 4 | --- 5 | 6 | # core/pipeR 7 | 8 | `pipeR :: [Function] -> Any -> Any` 9 | 10 | Pipe is an asynchronous function composer for recursive functions. 11 | 12 | ## Example 13 | 14 | ```javascript 15 | import { log, pipeR, run, sleep } from 'mojiscript' 16 | 17 | const state = 1 18 | 19 | const main = pipeR (next => [ 20 | log, 21 | sleep (1000), 22 | x => next (x + 1) 23 | ]) 24 | 25 | run ({ state, main }) 26 | ``` 27 | 28 | ## Parameters 29 | 30 | | Name | Type | Description | 31 | | ---- | ---- | ----------- | 32 | | funcs | `[Function(a -> b)]` | A `Function` that takes `value` and returns the computed result | 33 | | value | `Object` | `Object` passed to `func` and `prop` is set into. | 34 | 35 | ## Returns 36 | 37 | Returns the result of the last function in the `pipeR`. 38 | -------------------------------------------------------------------------------- /core/run.js: -------------------------------------------------------------------------------- 1 | const error = require('../console/error') 2 | 3 | const run = ({ dependencies, state, main }) => 4 | dependencies != null 5 | ? run({ state, main: main(dependencies) }) 6 | : main(state).catch((dependencies || {}).error || error) 7 | 8 | module.exports = run 9 | -------------------------------------------------------------------------------- /core/run.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: run 3 | menu: core 4 | --- 5 | 6 | # core/run 7 | 8 | `run :: Any -> Any` 9 | 10 | Main entrypoint into your application. Runs your `main` pipe. 11 | 12 | ## Examples 13 | 14 | ```javascript 15 | import { log, pipe, run } from 'mojiscript' 16 | 17 | const main = pipe ([ 18 | () => log ('done') 19 | ]) 20 | 21 | run ({ main }) 22 | //=> 'done' 23 | ``` 24 | 25 | ```javascript 26 | import { log, pipe, run } from 'mojiscript' 27 | 28 | const state = 'Hello World' 29 | 30 | const main = pipe ([ 31 | log 32 | ]) 33 | 34 | run ({ state, main }) 35 | //=> 'Hello World' 36 | ``` 37 | 38 | ```javascript 39 | import { log, pipe, run } from 'mojiscript' 40 | 41 | const dependencies = { 42 | log 43 | } 44 | 45 | const state = 'Hello World' 46 | 47 | const main = ({ log }) => pipe ([ 48 | log 49 | ]) 50 | 51 | run ({ dependencies, state, main }) 52 | //=> 'Hello World' 53 | ``` 54 | 55 | ## Parameters 56 | 57 | | Name | Type | Description | 58 | | ---- | ---- | ----------- | 59 | | options | `Object` | run options. | 60 | | options.dependencies | `Object` | Collection of dependencies. | 61 | | options.state | `Any` | Parameter to send to `pipe`. | 62 | | options.main | `Function` | Main `pipe` to run. | 63 | 64 | ## Returns 65 | 66 | Returns a `Promise` containing the results of `main`. 67 | -------------------------------------------------------------------------------- /doczrc.js: -------------------------------------------------------------------------------- 1 | export default { 2 | title: 'MojiScript', 3 | // dest: 'docs', 4 | base: '/', 5 | codeSandbox: false, 6 | hashRouter: true, 7 | htmlContext: { 8 | head: { 9 | raw: ` 10 | 15 | `, 16 | links: [ { 17 | rel: 'stylesheet', 18 | href: 'https://codemirror.net/theme/dracula.css' 19 | } ] 20 | }, 21 | body: { 22 | raw: ` 23 | 24 | 25 | 32 | ` 33 | } 34 | }, 35 | themeConfig: { 36 | codemirrorTheme: 'dracula' 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/async-simple/.eslintignore: -------------------------------------------------------------------------------- 1 | __tests__/ 2 | coverage/ 3 | interop/ 4 | *.test.js 5 | *.test.mjs 6 | -------------------------------------------------------------------------------- /examples/async-simple/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | extends: mojiscript 2 | root: true 3 | -------------------------------------------------------------------------------- /examples/async-simple/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": false 3 | } 4 | -------------------------------------------------------------------------------- /examples/async-simple/README.md: -------------------------------------------------------------------------------- 1 | # MojiScript Async Simple 2 | 3 | MojiScript Async Simple example. 4 | 5 | ## Install 6 | 7 | ```bash 8 | # clone repository 9 | git clone https://github.com/joelnet/MojiScript.git 10 | 11 | # enter directory 12 | cd MojiScript/examples/async-simple 13 | 14 | # install dependencies 15 | npm ci 16 | ``` 17 | 18 | ## Run 19 | 20 | ```bash 21 | npm start 22 | ``` 23 | 24 | You should see the output 25 | 26 | ``` 27 | 4 28 | (pause for 1 second) 29 | 10 30 | ``` 31 | 32 | ## Code 33 | 34 | ```javascript 35 | import log from 'mojiscript/console/log' 36 | import pipe from 'mojiscript/core/pipe' 37 | import run from 'mojiscript/core/run' 38 | import sleep from 'mojiscript/threading/sleep' 39 | 40 | const state = 4 41 | 42 | // increase :: Number -> Number 43 | const increase = x => x + 1 44 | 45 | // double :: Number -> Number 46 | const double = x => x * 2 47 | 48 | const main = pipe ([ 49 | log, 50 | sleep (1000), 51 | increase, 52 | double, 53 | log 54 | ]) 55 | 56 | run ({ state, main }) //=> 10 57 | ``` -------------------------------------------------------------------------------- /examples/async-simple/index.mjs: -------------------------------------------------------------------------------- 1 | import log from 'mojiscript/console/log' 2 | import pipe from 'mojiscript/core/pipe' 3 | import run from 'mojiscript/core/run' 4 | import sleep from 'mojiscript/threading/sleep' 5 | 6 | const state = 4 7 | 8 | // increase :: Number -> Number 9 | const increase = x => x + 1 10 | 11 | // double :: Number -> Number 12 | const double = x => x * 2 13 | 14 | const main = pipe ([ 15 | log, 16 | sleep (1000), 17 | increase, 18 | double, 19 | log 20 | ]) 21 | 22 | run ({ state, main }) //= > 10 23 | -------------------------------------------------------------------------------- /examples/async-simple/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "async-simple", 3 | "version": "1.0.0", 4 | "author": "Joel Thoms", 5 | "license": "MIT", 6 | "main": "index.mjs", 7 | "scripts": { 8 | "build": "moji build .", 9 | "start": "node --experimental-modules --no-warnings index.mjs" 10 | }, 11 | "dependencies": { 12 | "mojiscript": "file:../.." 13 | }, 14 | "devDependencies": { 15 | "eslint": "^5.16.0", 16 | "eslint-config-mojiscript": "1.2.0", 17 | "eslint-plugin-better": "0.1.5", 18 | "eslint-plugin-fp": "2.3.0", 19 | "eslint-plugin-import": "^2.18.2", 20 | "eslint-plugin-prefer-arrow": "1.1.5", 21 | "mojiscript-cli": "0.0.3" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/conditionals/.eslintignore: -------------------------------------------------------------------------------- 1 | __tests__/ 2 | coverage/ 3 | interop/ 4 | *.test.js 5 | *.test.mjs 6 | -------------------------------------------------------------------------------- /examples/conditionals/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | extends: mojiscript 2 | root: true 3 | -------------------------------------------------------------------------------- /examples/conditionals/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": false 3 | } 4 | -------------------------------------------------------------------------------- /examples/conditionals/README.md: -------------------------------------------------------------------------------- 1 | # MojiScript IfElse Simple 2 | 3 | MojiScript IfElse Simple example. 4 | 5 | ## Install 6 | 7 | ```bash 8 | # clone repository 9 | git clone https://github.com/joelnet/MojiScript.git 10 | 11 | # enter directory 12 | cd MojiScript/examples/ifElse-simple 13 | 14 | # install dependencies 15 | npm ci 16 | ``` 17 | 18 | ## Run 19 | 20 | ```bash 21 | # run if/else 22 | npm run ifelse 23 | 24 | # run conditional 1 25 | npm run cond 26 | 27 | # run conditional 2 28 | npm run cond2 29 | ``` 30 | -------------------------------------------------------------------------------- /examples/conditionals/cond.mjs: -------------------------------------------------------------------------------- 1 | import logF from 'mojiscript/console/logF' 2 | import pipe from 'mojiscript/core/pipe' 3 | import run from 'mojiscript/core/run' 4 | import cond from 'mojiscript/logic/cond' 5 | 6 | const dependencies = { 7 | logF 8 | } 9 | const state = new Date ().getDay () 10 | 11 | const dayName = cond ([ 12 | [ 0, 'Sunday' ], 13 | [ 1, 'Monday' ], 14 | [ 2, 'Tuesday' ], 15 | [ 3, 'Wednesday' ], 16 | [ 4, 'Thursday' ], 17 | [ 5, 'Friday' ], 18 | [ 6, 'Saturday' ] 19 | ]) 20 | 21 | const main = ({ logF }) => pipe ([ 22 | dayName, 23 | logF (day => `Today is ${day}.`) 24 | ]) 25 | 26 | run ({ dependencies, state, main }) 27 | -------------------------------------------------------------------------------- /examples/conditionals/cond2.mjs: -------------------------------------------------------------------------------- 1 | import log from 'mojiscript/console/log' 2 | import pipe from 'mojiscript/core/pipe' 3 | import run from 'mojiscript/core/run' 4 | import cond from 'mojiscript/logic/cond' 5 | import $ from 'mojiscript/string/template' 6 | 7 | const dependencies = { 8 | log 9 | } 10 | const state = 100 11 | 12 | // getTempInfo :: Number -> String 13 | const getTempInfo = cond ([ 14 | [ 0, 'water freezes at 0°C' ], 15 | [ 100, 'water boils at 100°C' ], 16 | [ () => true, $`nothing special happens at ${0}°C` ] 17 | ]) 18 | 19 | const main = ({ log }) => pipe ([ 20 | getTempInfo, 21 | log 22 | ]) 23 | 24 | run ({ dependencies, state, main }) 25 | -------------------------------------------------------------------------------- /examples/conditionals/ifElse.mjs: -------------------------------------------------------------------------------- 1 | import log from 'mojiscript/console/log' 2 | import pipe from 'mojiscript/core/pipe' 3 | import run from 'mojiscript/core/run' 4 | import ifElse from 'mojiscript/logic/ifElse' 5 | import $ from 'mojiscript/string/template' 6 | 7 | const dependencies = { 8 | log 9 | } 10 | const state = [ 1, 2, 3 ] 11 | 12 | // hasOrders :: Array -> Boolean 13 | const hasOrders = ({ length }) => length > 0 14 | 15 | // orderCountText :: Array -> OrdersString 16 | const orderCountText = $`${'length'} orders` 17 | 18 | // noOrderCountText :: * -> OrdersString 19 | const noOrderCountText = $`No Orders` 20 | 21 | // ifHasOrders :: Function -> Function 22 | const ifHasOrders = ifElse (hasOrders) 23 | 24 | // getOrdersText :: (a -> b) -> (a -> b) -> OrdersString 25 | const getOrdersText = ifHasOrders (orderCountText) (noOrderCountText) 26 | 27 | // main :: Number -> String 28 | const main = ({ log }) => pipe ([ 29 | getOrdersText, 30 | log 31 | ]) 32 | 33 | run ({ dependencies, state, main }) //= > 'NO' 34 | -------------------------------------------------------------------------------- /examples/conditionals/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "conditionals", 3 | "version": "1.0.0", 4 | "author": "Joel Thoms", 5 | "license": "MIT", 6 | "main": "index.mjs", 7 | "scripts": { 8 | "build": "moji build .", 9 | "ifelse": "node --experimental-modules --no-warnings ifElse.mjs", 10 | "cond": "node --experimental-modules --no-warnings cond.mjs", 11 | "cond2": "node --experimental-modules --no-warnings cond2.mjs" 12 | }, 13 | "dependencies": { 14 | "mojiscript": "file:../.." 15 | }, 16 | "devDependencies": { 17 | "eslint": "^5.16.0", 18 | "eslint-config-mojiscript": "1.2.0", 19 | "eslint-plugin-better": "0.1.5", 20 | "eslint-plugin-fp": "2.3.0", 21 | "eslint-plugin-import": "^2.18.2", 22 | "eslint-plugin-prefer-arrow": "1.1.5", 23 | "mojiscript-cli": "0.0.3" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/express-hello-world/.eslintignore: -------------------------------------------------------------------------------- 1 | __tests__/ 2 | coverage/ 3 | interop/ 4 | *.test.js 5 | *.test.mjs 6 | -------------------------------------------------------------------------------- /examples/express-hello-world/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | extends: mojiscript 2 | root: true 3 | -------------------------------------------------------------------------------- /examples/express-hello-world/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": false 3 | } 4 | -------------------------------------------------------------------------------- /examples/express-hello-world/README.md: -------------------------------------------------------------------------------- 1 | # MojiScript Express Hello World 2 | 3 | App demonstrates how to interop with JavaScript libraries in a MojiScript application. In this case, we are interoping with `express` to create a simple web server that listens on port 3000. 4 | 5 | ## Install 6 | 7 | ```bash 8 | # clone repository 9 | git clone https://github.com/joelnet/MojiScript.git 10 | 11 | # enter directory 12 | cd MojiScript/examples/express-hello-world 13 | 14 | # install dependencies 15 | npm ci 16 | ``` 17 | 18 | ## Run 19 | 20 | ```bash 21 | npm start 22 | ``` 23 | 24 | You should see the output 25 | 26 | ``` 27 | Listening on port 3000. 28 | ``` 29 | 30 | ## Code 31 | 32 | [index.mjs](index.mjs) - Load dependencies and settings and start the application. 33 | 34 | [main.mjs](main.mjs) - Entrypoint of application. 35 | 36 | [interop/express.mjs](interop/express.mjs) - `express` JavaScript interop. 37 | -------------------------------------------------------------------------------- /examples/express-hello-world/index.mjs: -------------------------------------------------------------------------------- 1 | import logF from 'mojiscript/console/logF' 2 | import run from 'mojiscript/core/run' 3 | import express from './interop/express' 4 | import main from './main' 5 | 6 | const dependencies = { 7 | express: express (), 8 | logF 9 | } 10 | 11 | const state = { 12 | port: 3000 13 | } 14 | 15 | run ({ dependencies, state, main }) 16 | -------------------------------------------------------------------------------- /examples/express-hello-world/interop/express.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Interop file converts the JavaScript express library into a MojiScript compatible library. 3 | */ 4 | import express from 'express' 5 | 6 | export default () => { 7 | // wrap app in our own function so we can return MojiScript compatible `get` and `listen`. 8 | const app = express () 9 | 10 | return { 11 | // curry `get` and `handler 12 | get: path => handler => app.get (path, (req, res) => handler (req) (res)), 13 | 14 | // convert callback into a Promise. 15 | listen: port => new Promise (resolve => app.listen (port, data => resolve (data))) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/express-hello-world/main.mjs: -------------------------------------------------------------------------------- 1 | import pipe from 'mojiscript/core/pipe' 2 | import tap from 'mojiscript/function/tap' 3 | import $ from 'mojiscript/string/template' 4 | 5 | // selectors 6 | const getPort = ({ port }) => port 7 | 8 | // routes 9 | const defaultRoute = () => ({ send }) => send ('Hello World') 10 | 11 | // express 12 | const setupRoutes = ({ get }) => pipe ([ 13 | () => get ('/') (defaultRoute) 14 | ]) 15 | const startExpress = ({ listen }) => pipe ([ 16 | getPort, 17 | listen 18 | ]) 19 | 20 | const main = ({ express, logF }) => pipe ([ 21 | tap (setupRoutes (express)), 22 | tap (startExpress (express)), 23 | logF ($`Listening on port ${'port'}.`) 24 | ]) 25 | 26 | export default main 27 | -------------------------------------------------------------------------------- /examples/express-hello-world/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-hello-world", 3 | "version": "1.0.0", 4 | "author": "Joel Thoms", 5 | "license": "MIT", 6 | "main": "index.mjs", 7 | "scripts": { 8 | "build": "moji build .", 9 | "start": "node --experimental-modules --no-warnings index.mjs" 10 | }, 11 | "dependencies": { 12 | "express": "^4.17.1", 13 | "mojiscript": "file:../.." 14 | }, 15 | "devDependencies": { 16 | "eslint": "^5.16.0", 17 | "eslint-config-mojiscript": "1.2.0", 18 | "eslint-plugin-better": "0.1.5", 19 | "eslint-plugin-fp": "2.3.0", 20 | "eslint-plugin-import": "^2.18.2", 21 | "eslint-plugin-prefer-arrow": "1.1.5", 22 | "mojiscript-cli": "0.0.3" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/express-static-files/.eslintignore: -------------------------------------------------------------------------------- 1 | __tests__/ 2 | coverage/ 3 | interop/ 4 | *.test.js 5 | *.test.mjs 6 | -------------------------------------------------------------------------------- /examples/express-static-files/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | extends: mojiscript 2 | root: true 3 | -------------------------------------------------------------------------------- /examples/express-static-files/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": false 3 | } 4 | -------------------------------------------------------------------------------- /examples/express-static-files/README.md: -------------------------------------------------------------------------------- 1 | # MojiScript Express Static File Server 2 | 3 | App demonstrates how to interop with JavaScript libraries in a MojiScript application. In this case, we are interoping with `express` to create a simple web server that listens on port 3000 and serves static files in the public directory. 4 | 5 | ## Install 6 | 7 | ```bash 8 | # clone repository 9 | git clone https://github.com/joelnet/MojiScript.git 10 | 11 | # enter directory 12 | cd MojiScript/examples/express-static-files 13 | 14 | # install dependencies 15 | npm ci 16 | ``` 17 | 18 | ## Run 19 | 20 | ```bash 21 | npm start 22 | ``` 23 | 24 | You should see the output 25 | 26 | ``` 27 | Listening on port 3000. 28 | ``` 29 | 30 | Open http://localhost:3000 in your web browser. 31 | 32 | ## Code 33 | 34 | [index.mjs](index.mjs) - Load dependencies and settings and start the application. 35 | 36 | [main.mjs](main.mjs) - Entrypoint of application. 37 | 38 | [interop/express.mjs](interop/express.mjs) - `express` JavaScript interop. 39 | -------------------------------------------------------------------------------- /examples/express-static-files/index.mjs: -------------------------------------------------------------------------------- 1 | import logF from 'mojiscript/console/logF' 2 | import run from 'mojiscript/core/run' 3 | import express from './interop/express' 4 | import main from './main' 5 | 6 | const dependencies = { 7 | express: express (), 8 | logF 9 | } 10 | 11 | const state = { 12 | port: 3000 13 | } 14 | 15 | run ({ dependencies, state, main }) 16 | -------------------------------------------------------------------------------- /examples/express-static-files/interop/express.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Interop file converts the JavaScript express library into a MojiScript compatible library. 3 | */ 4 | import express from 'express' 5 | import serveIndex from 'serve-index' 6 | 7 | export default () => { 8 | // wrap app in our own function so we can return MojiScript compatible `get` and `listen`. 9 | const app = express () 10 | 11 | return { 12 | // directory to call serveIndex 13 | directory: path => serveIndex (path), 14 | 15 | // pass through express.static 16 | static: express.static, 17 | 18 | // curry use 19 | use: route => path => app.use (route, path), 20 | 21 | // convert callback into a Promise. 22 | listen: port => new Promise (resolve => app.listen (port, data => resolve (data))) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/express-static-files/main.mjs: -------------------------------------------------------------------------------- 1 | import pipe from 'mojiscript/core/pipe' 2 | import tap from 'mojiscript/function/tap' 3 | import $ from 'mojiscript/string/template' 4 | 5 | // selectors 6 | const getPort = ({ port }) => port 7 | 8 | // express 9 | const setupRoutes = express => pipe ([ 10 | () => express.use ('/') (express.static ('public')), 11 | () => express.use ('/') (express.directory ('public')) 12 | ]) 13 | const startExpress = ({ listen }) => pipe ([ 14 | getPort, 15 | listen 16 | ]) 17 | 18 | const main = ({ express, logF }) => pipe ([ 19 | tap (setupRoutes (express)), 20 | tap (startExpress (express)), 21 | logF ($`Listening on port ${'port'}.`) 22 | ]) 23 | 24 | export default main 25 | -------------------------------------------------------------------------------- /examples/express-static-files/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-static-files", 3 | "version": "1.0.0", 4 | "author": "Joel Thoms", 5 | "license": "MIT", 6 | "main": "index.mjs", 7 | "scripts": { 8 | "build": "moji build .", 9 | "start": "node --experimental-modules --no-warnings index.mjs" 10 | }, 11 | "dependencies": { 12 | "express": "^4.17.1", 13 | "mojiscript": "file:../..", 14 | "serve-index": "^1.9.1" 15 | }, 16 | "devDependencies": { 17 | "eslint": "^5.16.0", 18 | "eslint-config-mojiscript": "1.2.0", 19 | "eslint-plugin-better": "0.1.5", 20 | "eslint-plugin-fp": "2.3.0", 21 | "eslint-plugin-import": "^2.18.2", 22 | "eslint-plugin-prefer-arrow": "1.1.5", 23 | "mojiscript-cli": "0.0.3" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/express-static-files/public/c64.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelnet/MojiScript/1bc7f43fdc105ed252aa3071e22a84676d84fbe1/examples/express-static-files/public/c64.gif -------------------------------------------------------------------------------- /examples/express-static-files/public/mojo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelnet/MojiScript/1bc7f43fdc105ed252aa3071e22a84676d84fbe1/examples/express-static-files/public/mojo.jpg -------------------------------------------------------------------------------- /examples/fizz-buzz/.eslintignore: -------------------------------------------------------------------------------- 1 | __tests__/ 2 | coverage/ 3 | interop/ 4 | *.test.js 5 | *.test.mjs 6 | -------------------------------------------------------------------------------- /examples/fizz-buzz/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | extends: mojiscript 2 | root: true 3 | -------------------------------------------------------------------------------- /examples/fizz-buzz/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": false 3 | } 4 | -------------------------------------------------------------------------------- /examples/fizz-buzz/README.md: -------------------------------------------------------------------------------- 1 | # MojiScript Fizz Buzz 2 | 3 | MojiScript Fizz Buzz 4 | 5 | ## Install 6 | 7 | ```bash 8 | # clone repository 9 | git clone https://github.com/joelnet/MojiScript.git 10 | 11 | # enter directory 12 | cd MojiScript/examples/fizz-buzz 13 | 14 | # install dependencies 15 | npm ci 16 | ``` 17 | 18 | ## Run 19 | 20 | ```bash 21 | npm start 22 | ``` 23 | ## Tutorial 24 | 25 | There is a very good tutorial that will walk you through the creation of this FizzBuzz app here: 26 | 27 | [Getting started with MojiScript: FizzBuzz (part 1) 28 | ](https://dev.to/joelnet/getting-started-with-mojiscript-fizzbuzz-part-1-2fji) 29 | 30 | [Getting started with MojiScript: Async, Infinity, Testing (part 2)](https://dev.to/joelnet/getting-started-with-mojiscript-async-infinity-testing-part-2-h1e) 31 | 32 | ## Code 33 | 34 | ```javascript 35 | import cond from 'mojiscript/logic/cond' 36 | import pipe from 'mojiscript/core/pipe' 37 | import map from 'mojiscript/list/map' 38 | import range from 'mojiscript/list/range' 39 | import allPass from 'mojiscript/logic/allPass' 40 | 41 | const isFizz = num => num % 3 === 0 42 | const isBuzz = num => num % 5 === 0 43 | const isFizzBuzz = allPass ([ isFizz, isBuzz ]) 44 | 45 | const fizziness = cond ([ 46 | [ isFizzBuzz, 'FizzBuzz' ], 47 | [ isFizz, 'Fizz' ], 48 | [ isBuzz, 'Buzz' ], 49 | [ () => true, x => x ] 50 | ]) 51 | 52 | const logFizziness = log => pipe ([ 53 | fizziness, 54 | log 55 | ]) 56 | 57 | const main = ({ log }) => pipe ([ 58 | ({ start, end }) => range (start) (end + 1), 59 | map (logFizziness (log)) 60 | ]) 61 | 62 | export default main 63 | ``` 64 | -------------------------------------------------------------------------------- /examples/fizz-buzz/index.mjs: -------------------------------------------------------------------------------- 1 | import log from 'mojiscript/console/log' 2 | import run from 'mojiscript/core/run' 3 | import main from './main' 4 | 5 | const dependencies = { 6 | log 7 | } 8 | const state = { 9 | start: 1, 10 | end: 100 11 | } 12 | 13 | run ({ dependencies, state, main }) 14 | -------------------------------------------------------------------------------- /examples/fizz-buzz/main.mjs: -------------------------------------------------------------------------------- 1 | import cond from 'mojiscript/logic/cond' 2 | import pipe from 'mojiscript/core/pipe' 3 | import map from 'mojiscript/list/map' 4 | import range from 'mojiscript/list/range' 5 | import allPass from 'mojiscript/logic/allPass' 6 | 7 | const isFizz = num => num % 3 === 0 8 | const isBuzz = num => num % 5 === 0 9 | const isFizzBuzz = allPass ([ isFizz, isBuzz ]) 10 | 11 | const fizziness = cond ([ 12 | [ isFizzBuzz, 'FizzBuzz' ], 13 | [ isFizz, 'Fizz' ], 14 | [ isBuzz, 'Buzz' ], 15 | [ () => true, x => x ] 16 | ]) 17 | 18 | const logFizziness = log => pipe ([ 19 | fizziness, 20 | log 21 | ]) 22 | 23 | const main = ({ log }) => pipe ([ 24 | ({ start, end }) => range (start) (end + 1), 25 | map (logFizziness (log)) 26 | ]) 27 | 28 | export default main 29 | -------------------------------------------------------------------------------- /examples/fizz-buzz/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fizz-buzz", 3 | "version": "1.0.0", 4 | "author": "Joel Thoms", 5 | "license": "MIT", 6 | "main": "index.mjs", 7 | "scripts": { 8 | "build": "moji build .", 9 | "start": "node --experimental-modules --no-warnings index.mjs" 10 | }, 11 | "dependencies": { 12 | "mojiscript": "file:../.." 13 | }, 14 | "devDependencies": { 15 | "eslint": "^5.16.0", 16 | "eslint-config-mojiscript": "1.2.0", 17 | "eslint-plugin-better": "0.1.5", 18 | "eslint-plugin-fp": "2.3.0", 19 | "eslint-plugin-import": "^2.18.2", 20 | "eslint-plugin-prefer-arrow": "1.1.5", 21 | "mojiscript-cli": "0.0.3" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/hello-world/.eslintignore: -------------------------------------------------------------------------------- 1 | __tests__/ 2 | coverage/ 3 | interop/ 4 | *.test.js 5 | *.test.mjs 6 | -------------------------------------------------------------------------------- /examples/hello-world/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | extends: mojiscript 2 | root: true 3 | -------------------------------------------------------------------------------- /examples/hello-world/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": false 3 | } 4 | -------------------------------------------------------------------------------- /examples/hello-world/README.md: -------------------------------------------------------------------------------- 1 | # MojiScript Hello World 2 | 3 | MojiScript Hello World example. 4 | 5 | ## Install 6 | 7 | ```bash 8 | # clone repository 9 | git clone https://github.com/joelnet/MojiScript.git 10 | 11 | # enter directory 12 | cd MojiScript/examples/hello-world 13 | 14 | # install dependencies 15 | npm ci 16 | ``` 17 | 18 | ## Run 19 | 20 | ```bash 21 | npm start 22 | ``` 23 | 24 | You should see the output 25 | 26 | ``` 27 | Hello World 28 | ``` 29 | 30 | ## Code 31 | 32 | ```javascript 33 | import log from 'mojiscript/console/log' 34 | import pipe from 'mojiscript/core/pipe' 35 | import run from 'mojiscript/core/run' 36 | 37 | const state = 'Hello World' 38 | 39 | const main = pipe([ 40 | log 41 | ]) 42 | 43 | run ({ main, state }) 44 | ``` -------------------------------------------------------------------------------- /examples/hello-world/index.mjs: -------------------------------------------------------------------------------- 1 | import log from 'mojiscript/console/log' 2 | import pipe from 'mojiscript/core/pipe' 3 | import run from 'mojiscript/core/run' 4 | 5 | const state = 'Hello World' 6 | 7 | const main = pipe ([ 8 | log 9 | ]) 10 | 11 | run ({ state, main }) 12 | -------------------------------------------------------------------------------- /examples/hello-world/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello-world", 3 | "version": "1.0.0", 4 | "author": "Joel Thoms", 5 | "license": "MIT", 6 | "main": "index.mjs", 7 | "scripts": { 8 | "build": "moji build .", 9 | "start": "node --experimental-modules --no-warnings index.mjs" 10 | }, 11 | "dependencies": { 12 | "mojiscript": "file:../.." 13 | }, 14 | "devDependencies": { 15 | "eslint": "^5.16.0", 16 | "eslint-config-mojiscript": "1.2.0", 17 | "eslint-plugin-better": "0.1.5", 18 | "eslint-plugin-fp": "2.3.0", 19 | "eslint-plugin-import": "^2.18.2", 20 | "eslint-plugin-prefer-arrow": "1.1.5", 21 | "mojiscript-cli": "0.0.3" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/map-filter-reduce/.eslintignore: -------------------------------------------------------------------------------- 1 | __tests__/ 2 | coverage/ 3 | interop/ 4 | *.test.js 5 | *.test.mjs 6 | -------------------------------------------------------------------------------- /examples/map-filter-reduce/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | extends: mojiscript 2 | root: true 3 | -------------------------------------------------------------------------------- /examples/map-filter-reduce/.gitignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | node_modules/ -------------------------------------------------------------------------------- /examples/map-filter-reduce/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": false 3 | } 4 | -------------------------------------------------------------------------------- /examples/map-filter-reduce/README.md: -------------------------------------------------------------------------------- 1 | # MojiScript Map/Filter/Reduce 2 | 3 | MojiScript Map/Filter/Reduce example. 4 | 5 | You could also use `map`, `filter`, `reduce` from [Sanctuary](https://github.com/sanctuary-js/sanctuary) or [Ramda](https://ramdajs.com/) or [lodash/fp](https://github.com/lodash/lodash/wiki/FP-Guide) or any other library. 6 | 7 | ## Install 8 | 9 | ```bash 10 | # clone repository 11 | git clone https://github.com/joelnet/MojiScript.git 12 | 13 | # enter directory 14 | cd MojiScript/examples/map-filter-reduce 15 | 16 | # install dependencies 17 | npm ci 18 | ``` 19 | 20 | ## Run 21 | 22 | ```bash 23 | npm start 24 | ``` 25 | 26 | You should see the output 27 | 28 | ``` 29 | 8 30 | ``` 31 | 32 | ## Unit Tests 33 | 34 | Basic test run 35 | 36 | ```bash 37 | npm test 38 | ``` 39 | 40 | Tests with coverage 41 | 42 | ```bash 43 | npm test -- --coverage 44 | ``` 45 | 46 | Tests are written in JavaScript using Jest and should follow Jest best practices. 47 | 48 | # Source 49 | 50 | [index.js](index.js) 51 | ```javascript 52 | import log from 'mojiscript/console/log' 53 | import run from 'mojiscript/core/run' 54 | import main from './main' 55 | 56 | const dependencies = { 57 | log 58 | } 59 | const state = [ 1, 2, 3 ] 60 | 61 | run ({ dependencies, state, main }) // => 8 62 | ``` 63 | 64 | [main.js](main.js) 65 | ```javascript 66 | import pipe from 'mojiscript/core/pipe' 67 | import filter from 'mojiscript/list/filter' 68 | import map from 'mojiscript/list/map' 69 | import reduce from 'mojiscript/list/reduce' 70 | import { add, double, isOdd } from './lib/math' 71 | 72 | // main :: Dependencies -> [Number] -> Number 73 | const main = ({ log }) => pipe ([ 74 | filter (isOdd), // [1, 2, 3] => [1, 3] 75 | map (double), // [1, 3] => [2, 6] 76 | reduce (add) (0), // [2, 6] => 8, 77 | log 78 | ]) 79 | 80 | export default main 81 | ``` -------------------------------------------------------------------------------- /examples/map-filter-reduce/__tests__/main.test.mjs: -------------------------------------------------------------------------------- 1 | import log from 'mojiscript/console/log' 2 | import main from '../main' 3 | 4 | describe ('main.js', () => { 5 | const dependencies = { log } 6 | 7 | beforeEach (() => jest.spyOn (global.console, 'log').mockImplementation (() => {})) 8 | 9 | afterEach (() => jest.clearAllMocks ()) 10 | 11 | test ('main computes values', () => { 12 | expect.assertions (1) 13 | const expected = 8 14 | const state = [ 1, 2, 3 ] 15 | const actual = main (dependencies) (state) 16 | return expect (actual).resolves.toBe (expected) 17 | }) 18 | 19 | test ('main logs to console', async () => { 20 | expect.assertions (1) 21 | const expected = 8 22 | const state = [ 1, 2, 3 ] 23 | await main (dependencies) (state) 24 | const actual = global.console.log.mock.calls[0][0] 25 | expect (actual).toBe (expected) 26 | }) 27 | }) 28 | -------------------------------------------------------------------------------- /examples/map-filter-reduce/index.mjs: -------------------------------------------------------------------------------- 1 | import log from 'mojiscript/console/log' 2 | import run from 'mojiscript/core/run' 3 | import main from './main' 4 | 5 | const dependencies = { 6 | log 7 | } 8 | const state = [ 1, 2, 3 ] 9 | 10 | run ({ dependencies, state, main }) // => 8 11 | -------------------------------------------------------------------------------- /examples/map-filter-reduce/lib/__tests__/math.test.mjs: -------------------------------------------------------------------------------- 1 | import { isOdd, double, add } from '../math' 2 | 3 | describe ('lib/math', () => { 4 | describe ('isOdd', () => { 5 | test ('3 is odd', () => { 6 | const expected = true 7 | const actual = isOdd (3) 8 | expect (actual).toBe (expected) 9 | }) 10 | 11 | test ('4 is not odd', () => { 12 | const expected = false 13 | const actual = isOdd (4) 14 | expect (actual).toBe (expected) 15 | }) 16 | }) 17 | 18 | describe ('double', () => { 19 | test ('number is doubled', () => { 20 | const expected = 32 21 | const actual = double (16) 22 | expect (actual).toBe (expected) 23 | }) 24 | }) 25 | 26 | describe ('add', () => { 27 | test ('numbers are added', () => { 28 | const expected = 7 29 | const actual = add (3) (4) 30 | expect (actual).toBe (expected) 31 | }) 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /examples/map-filter-reduce/lib/math.mjs: -------------------------------------------------------------------------------- 1 | // isOdd :: Number -> Boolean 2 | export const isOdd = x => x % 2 !== 0 3 | 4 | // double :: Number -> Number 5 | export const double = x => x * 2 6 | 7 | // add :: Number -> Number -> Number 8 | export const add = x => y => x + y 9 | -------------------------------------------------------------------------------- /examples/map-filter-reduce/main.mjs: -------------------------------------------------------------------------------- 1 | import pipe from 'mojiscript/core/pipe' 2 | import filter from 'mojiscript/list/filter' 3 | import map from 'mojiscript/list/map' 4 | import reduce from 'mojiscript/list/reduce' 5 | import { add, double, isOdd } from './lib/math' 6 | 7 | // main :: Dependencies -> [Number] -> Number 8 | const main = ({ log }) => pipe ([ 9 | filter (isOdd), // [1, 2, 3] => [1, 3] 10 | map (double), // [1, 3] => [2, 6] 11 | reduce (add) (0), // [2, 6] => 8, 12 | log 13 | ]) 14 | 15 | export default main 16 | -------------------------------------------------------------------------------- /examples/map-filter-reduce/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "map-filter-reduce", 3 | "version": "1.0.0", 4 | "author": "Joel Thoms", 5 | "license": "MIT", 6 | "main": "index.mjs", 7 | "scripts": { 8 | "build": "moji build .", 9 | "start": "node --experimental-modules --no-warnings index.mjs", 10 | "test": "jest" 11 | }, 12 | "dependencies": { 13 | "mojiscript": "file:../.." 14 | }, 15 | "devDependencies": { 16 | "babel-core": "^6.26.3", 17 | "babel-jest": "^24.8.0", 18 | "babel-preset-env": "^1.7.0", 19 | "eslint": "^5.16.0", 20 | "eslint-config-mojiscript": "1.2.0", 21 | "eslint-plugin-better": "0.1.5", 22 | "eslint-plugin-fp": "2.3.0", 23 | "eslint-plugin-import": "^2.18.2", 24 | "eslint-plugin-prefer-arrow": "1.1.5", 25 | "jest": "^23.6.0", 26 | "mojiscript-cli": "0.0.3" 27 | }, 28 | "babel": { 29 | "presets": [ 30 | "env" 31 | ] 32 | }, 33 | "jest": { 34 | "testMatch": [ 35 | "**/__tests__/**/*.?(m)js?(x)", 36 | "**/?(*.)(spec|test).?(m)js?(x)" 37 | ], 38 | "moduleFileExtensions": [ 39 | "js", 40 | "mjs" 41 | ], 42 | "transform": { 43 | "^.+\\.m?js$": "babel-jest" 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /examples/parcel-hello-world/.eslintignore: -------------------------------------------------------------------------------- 1 | __tests__/ 2 | coverage/ 3 | dist/ 4 | interop/ 5 | *.test.js 6 | *.test.mjs 7 | -------------------------------------------------------------------------------- /examples/parcel-hello-world/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | extends: mojiscript 2 | root: true 3 | -------------------------------------------------------------------------------- /examples/parcel-hello-world/.gitignore: -------------------------------------------------------------------------------- 1 | .cache/ 2 | dist/ 3 | node_nodules/ 4 | -------------------------------------------------------------------------------- /examples/parcel-hello-world/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": false, 3 | "eslint.autoFixOnSave": true 4 | } 5 | -------------------------------------------------------------------------------- /examples/parcel-hello-world/README.md: -------------------------------------------------------------------------------- 1 | # MojiScript Hello World 2 | 3 | Parcel Hello World example. 4 | 5 | ## Install 6 | 7 | ```bash 8 | # clone repository 9 | git clone https://github.com/joelnet/MojiScript.git 10 | 11 | # enter directory 12 | cd MojiScript/examples/parcel-hello-world 13 | 14 | # install dependencies 15 | npm ci 16 | ``` 17 | 18 | ## Run 19 | 20 | ```bash 21 | npm run start 22 | ``` 23 | 24 | Go to [http://localhost:1234/](http://localhost:1234/) 25 | You should see the output 26 | 27 | ``` 28 | Hello World 29 | ``` 30 | 31 | ## Code 32 | 33 | ### index.js 34 | 35 | The id of the app is set as the `state`. `document` is also passed in as a dependency. 36 | 37 | ```javascript 38 | import run from 'mojiscript/core/run' 39 | import main from './main' 40 | 41 | const state = 'app' 42 | 43 | const dependencies = { 44 | document: global.document 45 | } 46 | 47 | run ({ dependencies, state, main }) 48 | ``` 49 | 50 | ### main.js 51 | 52 | `main`, receives the id of the app from `index` and executes `getElementById` first. Then it sets the text to `'Hello World!'`. 53 | 54 | ```javascript 55 | import pipe from 'mojiscript/core/pipe' 56 | import { setInnerText, getElementById } from './interop/dom.js' 57 | 58 | const main = ({ document }) => pipe ([ 59 | getElementById (document), 60 | setInnerText ('Hello World!') 61 | ]) 62 | 63 | export default main 64 | ``` 65 | 66 | ### interop/dom.js 67 | 68 | Basic DOM interactions are compartmentalized off into an interop directory. This is where MojiScript can interact with classic JavaScript. 69 | 70 | ```javascript 71 | export const setInnerText = text => element => 72 | element.innerText = text 73 | 74 | export const getElementById = document => id => 75 | document.getElementById(id) 76 | ``` 77 | -------------------------------------------------------------------------------- /examples/parcel-hello-world/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Parcel Demo 01 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /examples/parcel-hello-world/index.js: -------------------------------------------------------------------------------- 1 | import run from 'mojiscript/core/run' 2 | import main from './main' 3 | 4 | const state = 'app' 5 | 6 | const dependencies = { 7 | document: global.document 8 | } 9 | 10 | run ({ dependencies, state, main }) 11 | -------------------------------------------------------------------------------- /examples/parcel-hello-world/interop/dom.js: -------------------------------------------------------------------------------- 1 | export const setInnerText = text => element => 2 | element.innerText = text 3 | 4 | export const getElementById = document => id => 5 | document.getElementById(id) 6 | -------------------------------------------------------------------------------- /examples/parcel-hello-world/main.js: -------------------------------------------------------------------------------- 1 | import pipe from 'mojiscript/core/pipe' 2 | import { setInnerText, getElementById } from './interop/dom.js' 3 | 4 | const main = ({ document }) => pipe ([ 5 | getElementById (document), 6 | setInnerText ('Hello World!') 7 | ]) 8 | 9 | export default main 10 | -------------------------------------------------------------------------------- /examples/parcel-hello-world/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Edgar Mejía main (x + 1) 51 | ]) 52 | 53 | run ({ state, main }) 54 | ``` -------------------------------------------------------------------------------- /examples/recursion/index.mjs: -------------------------------------------------------------------------------- 1 | import log from 'mojiscript/console/log' 2 | import pipe from 'mojiscript/core/pipe' 3 | import run from 'mojiscript/core/run' 4 | import wait from 'mojiscript/threading/sleep' 5 | 6 | const state = 1 7 | 8 | const main = pipe ([ 9 | log, 10 | wait (1000), 11 | x => main (x + 1) 12 | ]) 13 | 14 | run ({ state, main }) 15 | -------------------------------------------------------------------------------- /examples/recursion/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "recursion", 3 | "version": "1.0.0", 4 | "author": "Joel Thoms", 5 | "license": "MIT", 6 | "main": "index.mjs", 7 | "scripts": { 8 | "build": "moji build .", 9 | "start": "node --experimental-modules --no-warnings index.mjs" 10 | }, 11 | "dependencies": { 12 | "mojiscript": "file:../.." 13 | }, 14 | "devDependencies": { 15 | "eslint": "^5.16.0", 16 | "eslint-config-mojiscript": "1.2.0", 17 | "eslint-plugin-better": "0.1.5", 18 | "eslint-plugin-fp": "2.3.0", 19 | "eslint-plugin-import": "^2.18.2", 20 | "eslint-plugin-prefer-arrow": "1.1.5", 21 | "mojiscript-cli": "0.0.3" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/star-wars-console/.eslintignore: -------------------------------------------------------------------------------- 1 | __tests__/ 2 | coverage/ 3 | interop/ 4 | *.test.js 5 | *.test.mjs 6 | -------------------------------------------------------------------------------- /examples/star-wars-console/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | extends: mojiscript 2 | root: true 3 | -------------------------------------------------------------------------------- /examples/star-wars-console/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": false 3 | } 4 | -------------------------------------------------------------------------------- /examples/star-wars-console/README.md: -------------------------------------------------------------------------------- 1 | # MojiScript Star Wars Console 2 | 3 | The Star Wars Console is a good example to show how easy it is to create asynchronous code (`axios`, `readline`). It is also easy to interop with JavaScript libraries (in this case node's `readline`). 4 | 5 | Look in [`index.mjs`](index.mjs) for examples on how to inject dependencies into your application. This is the entry point to your application. 6 | 7 | [`main.mjs`](main.mjs) is the meat of your application. You should find some good branching examples with `ifElse` in there. 8 | 9 | Star Wars API provided by https://swapi.co/ 10 | 11 | ## Install 12 | 13 | ```bash 14 | # clone repository 15 | git clone https://github.com/joelnet/MojiScript.git 16 | 17 | # enter directory 18 | cd MojiScript/examples/star-wars-console 19 | 20 | # install dependencies 21 | npm ci 22 | ``` 23 | 24 | ## Run 25 | 26 | ```bash 27 | # start quietly 28 | npm start --silent 29 | ``` 30 | 31 | Answer the prompt `Search for Star Wars Character:` and you should one of the three outputs depending on your search: 32 | 33 | No Search: 34 | 35 | ``` 36 | Search for Star Wars Character: 37 | No search was performed. 38 | ``` 39 | 40 | No Results: 41 | 42 | ``` 43 | Search for Star Wars Character: asdf 44 | 0 Results for "asdf". 45 | ``` 46 | 47 | Search Results: 48 | 49 | ``` 50 | Search for Star Wars Character: Skywalker 51 | 3 results: 52 | - Luke Skywalker (male) 53 | - Anakin Skywalker (male) 54 | - Shmi Skywalker (female) 55 | ``` 56 | 57 | ## Dependency Injection 58 | 59 | Dependencies are loaded and injected into the `main` application in [`index.mjs`](index.mjs). 60 | 61 | ```javascript 62 | const dependencies = { 63 | askQuestion, 64 | axios, 65 | log 66 | } 67 | 68 | run ({ dependencies, main }) 69 | ``` 70 | 71 | ## Async is easy 72 | 73 | Prompting the user for input and then performing an AJAX search is simple. 74 | 75 | ```javascript 76 | const main = ({ axios, askQuestion, log }) => pipe ([ 77 | askQuestion ('Search for Star Wars Character: '), 78 | ifEmpty (showNoSearch) (searchForPerson (axios)), 79 | log 80 | ]) 81 | ``` 82 | 83 | (Excerpt from [main.mjs](main.mjs)) 84 | 85 | ## Branching logic 86 | 87 | `ifElse` conditions can be reusable... 88 | 89 | ```javascript 90 | const ifEmpty = ifElse (isEmpty) 91 | ``` 92 | 93 | ...and a joy to work with. 94 | 95 | ```javascript 96 | ifEmpty (showNoSearch) (searchForPerson (axios)) 97 | 98 | ifEmpty (showNoResults (search)) (showResults) 99 | ``` 100 | 101 | (Excerpt from [main.mjs](main.mjs)) 102 | -------------------------------------------------------------------------------- /examples/star-wars-console/api.mjs: -------------------------------------------------------------------------------- 1 | import pipe from 'mojiscript/core/pipe' 2 | import ifElse from 'mojiscript/logic/ifElse' 3 | 4 | const rootUrl = 'https://swapi.co/api/people/' 5 | 6 | const STATUS_CODE = { 7 | OK: 200 8 | } 9 | 10 | const responseIsOk = ({ status }) => status === STATUS_CODE.OK 11 | const resolveResponse = ({ data }) => Promise.resolve (data.results) 12 | const rejectResponse = ({ status, statusText }) => Promise.reject (`${status}: ${statusText}`) 13 | 14 | const parseResponse = ifElse (responseIsOk) (resolveResponse) (rejectResponse) 15 | 16 | // searchStringToParams :: String -> AxiosOptions 17 | const searchStringToParams = search => ({ 18 | params: { 19 | search 20 | } 21 | }) 22 | 23 | // peopleSearch :: Axios -> String -> AxiosResponse 24 | export const peopleSearch = axios => pipe ([ 25 | searchStringToParams, 26 | axios.get (rootUrl), 27 | parseResponse 28 | ]) 29 | -------------------------------------------------------------------------------- /examples/star-wars-console/index.mjs: -------------------------------------------------------------------------------- 1 | import log from 'mojiscript/console/log' 2 | import run from 'mojiscript/core/run' 3 | import axios from 'mojiscript/net/axios' 4 | import { askQuestion } from './interop/readline' 5 | import main from './main' 6 | 7 | const dependencies = { 8 | askQuestion, 9 | axios, 10 | log 11 | } 12 | 13 | run ({ dependencies, main }) 14 | -------------------------------------------------------------------------------- /examples/star-wars-console/interop/readline.mjs: -------------------------------------------------------------------------------- 1 | import W from 'mojiscript/combinators/W' 2 | import pipe from 'mojiscript/core/pipe' 3 | import tap from 'mojiscript/function/tap' 4 | import readline_ from 'readline' 5 | 6 | export const createInterface = ({ 7 | input = process.stdin, 8 | output = process.stdout 9 | } = {}) => { 10 | const line = readline_.createInterface({ input, output }) 11 | const question = output => new Promise(resolve => 12 | line.question(output, resolve) 13 | ) 14 | const close = () => line.close() 15 | 16 | return { 17 | question, 18 | close 19 | } 20 | } 21 | 22 | export const askQuestion = ask => pipe ([ 23 | createInterface, 24 | W (({ question, close }) => pipe ([ 25 | question (ask), 26 | tap (close) 27 | ])) 28 | ]) -------------------------------------------------------------------------------- /examples/star-wars-console/main.mjs: -------------------------------------------------------------------------------- 1 | import W from 'mojiscript/combinators/W' 2 | import pipe from 'mojiscript/core/pipe' 3 | import join from 'mojiscript/list/join' 4 | import map from 'mojiscript/list/map' 5 | import ifElse from 'mojiscript/logic/ifElse' 6 | import prepend from 'mojiscript/string/prepend' 7 | import $ from 'mojiscript/string/template' 8 | import isEmpty from 'ramda/src/isEmpty' 9 | import { peopleSearch } from './api' 10 | 11 | const showNoSearch = $`No search was performed.` 12 | const showNoResults = search => () => `0 Results for "${search}".` 13 | const showResults = W (({ length }) => pipe ([ 14 | map ($`- ${'name'} (${'gender'})`), 15 | join ('\n'), 16 | prepend (`${length} results:\n`) 17 | ])) 18 | 19 | const ifEmpty = ifElse (isEmpty) 20 | 21 | const searchForPerson = axios => W (search => pipe ([ 22 | peopleSearch (axios), 23 | ifEmpty (showNoResults (search)) (showResults) 24 | ])) 25 | 26 | const main = ({ axios, askQuestion, log }) => pipe ([ 27 | askQuestion ('Search for Star Wars Character: '), 28 | ifEmpty (showNoSearch) (searchForPerson (axios)), 29 | log 30 | ]) 31 | 32 | export default main 33 | -------------------------------------------------------------------------------- /examples/star-wars-console/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "star-wars-console", 3 | "version": "1.0.0", 4 | "author": "Joel Thoms", 5 | "license": "MIT", 6 | "main": "index.mjs", 7 | "scripts": { 8 | "build": "moji build .", 9 | "start": "node --experimental-modules --no-warnings index.mjs" 10 | }, 11 | "dependencies": { 12 | "axios": "^0.21.1", 13 | "mojiscript": "file:../..", 14 | "mojiscript-cli": "0.0.3", 15 | "ramda": "^0.26.1" 16 | }, 17 | "devDependencies": { 18 | "eslint": "^5.16.0", 19 | "eslint-config-mojiscript": "1.2.0", 20 | "eslint-plugin-better": "0.1.5", 21 | "eslint-plugin-fp": "2.3.0", 22 | "eslint-plugin-import": "^2.18.2", 23 | "eslint-plugin-prefer-arrow": "1.1.5" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /function/__tests__/curry.test.js: -------------------------------------------------------------------------------- 1 | const curry = require('../curry') 2 | 3 | describe('function/curry', () => { 4 | const add = (...args) => 5 | args.reduce((a, b) => a + b) 6 | 7 | test('curry1 throws', () => { 8 | const actual = () => curry(1)(add) 9 | expect(actual).toThrow(Error('Cannot curry a function with 1 arguments.')) 10 | }) 11 | 12 | test('curry2', () => { 13 | const expected = 3 14 | const addCurried = curry(2)(add) 15 | const actual = addCurried(1)(2) 16 | expect(actual).toBe(expected) 17 | }) 18 | 19 | test('curry3', () => { 20 | const expected = 7 21 | const addCurried = curry(3)(add) 22 | const actual = addCurried(1)(2)(4) 23 | expect(actual).toBe(expected) 24 | }) 25 | 26 | test('curry4', () => { 27 | const expected = 15 28 | const addCurried = curry(4)(add) 29 | const actual = addCurried(1)(2)(4)(8) 30 | expect(actual).toBe(expected) 31 | }) 32 | 33 | test('curry5', () => { 34 | const expected = 31 35 | const addCurried = curry(5)(add) 36 | const actual = addCurried(1)(2)(4)(8)(16) 37 | expect(actual).toBe(expected) 38 | }) 39 | 40 | test('curry6', () => { 41 | const expected = 63 42 | const addCurried = curry(6)(add) 43 | const actual = addCurried(1)(2)(4)(8)(16)(32) 44 | expect(actual).toBe(expected) 45 | }) 46 | 47 | test('curry7 throws', () => { 48 | const actual = () => curry(7)(add) 49 | expect(actual).toThrow(Error('Cannot curry a function with 7 arguments.')) 50 | }) 51 | }) 52 | -------------------------------------------------------------------------------- /function/__tests__/lift.test.js: -------------------------------------------------------------------------------- 1 | const lift = require('../lift') 2 | const Just = require('../../type/Just') 3 | 4 | describe('function/lift', () => { 5 | const increase = lift(1)(x => x + 1) 6 | const add2 = lift(2)(x => y => x + y) 7 | 8 | test('Unknown Type', () => { 9 | const expected = new Error('Cannot lift unsupported type "object"') 10 | const actual = () => increase({ unknown: true }) 11 | expect(actual).toThrow(expected) 12 | }) 13 | 14 | test('Mapable', () => { 15 | const expected = Just(888) 16 | const actual = increase(Just(887)) 17 | expect(actual).toEqual(expected) 18 | }) 19 | 20 | test('Array', () => { 21 | const expected = [ 7 ] 22 | const actual = add2([ 3 ])([ 4 ]) 23 | expect(actual).toMatchObject(expected) 24 | }) 25 | 26 | test('Themable', () => { 27 | expect.assertions(1) 28 | const expected = 7 29 | const actual = add2({ then: r => r(3) })({ then: r => r(4) }) 30 | return expect(actual).resolves.toBe(expected) 31 | }) 32 | 33 | test('Promise', () => { 34 | expect.assertions(1) 35 | const expected = 7 36 | const actual = add2(Promise.resolve(3))(Promise.resolve(4)) 37 | return expect(actual).resolves.toBe(expected) 38 | }) 39 | }) 40 | -------------------------------------------------------------------------------- /function/__tests__/liftA.test.js: -------------------------------------------------------------------------------- 1 | const liftA = require('../liftA') 2 | const Just = require('../../type/Just') 3 | const Nothing = require('../../type/Nothing') 4 | 5 | describe('function/liftA', () => { 6 | const increase = liftA(1)(x => x + 1) 7 | const add2 = liftA(2)(x => y => x + y) 8 | const add3 = liftA(3)(x => y => z => x + y + z) 9 | 10 | test('0 Arity throws', () => { 11 | const expected = new Error('arity must be >= 1') 12 | const actual = () => liftA(0) 13 | expect(actual).toThrow(expected) 14 | }) 15 | 16 | test('Just + 1', () => { 17 | const expected = Just(888) 18 | const actual = increase(Just(887)) 19 | expect(actual).toEqual(expected) 20 | }) 21 | 22 | test('Nothing + 1', () => { 23 | const expected = Nothing 24 | const actual = increase(Nothing) 25 | expect(actual).toEqual(expected) 26 | }) 27 | 28 | test('Just + Just', () => { 29 | const expected = Just(888) 30 | const actual = add2(Just(333))(Just(555)) 31 | expect(actual).toEqual(expected) 32 | }) 33 | 34 | test('Just + Just + Just', () => { 35 | const expected = Just(888) 36 | const actual = add3(Just(333))(Just(333))(Just(222)) 37 | expect(actual).toEqual(expected) 38 | }) 39 | 40 | test('Nothing + Just', () => { 41 | const expected = Nothing 42 | const actual = add2(Nothing)(Just(555)) 43 | expect(actual).toEqual(expected) 44 | }) 45 | 46 | test('Just + Nothing', () => { 47 | const expected = Nothing 48 | const actual = add2(Just(333))(Nothing) 49 | expect(actual).toEqual(expected) 50 | }) 51 | }) 52 | -------------------------------------------------------------------------------- /function/__tests__/liftP.test.js: -------------------------------------------------------------------------------- 1 | const liftP = require('../liftP') 2 | 3 | describe('liftP', () => { 4 | const increase = num => num + 1 5 | const add = x => y => x + y 6 | 7 | test('throws Error with arity < 1', () => { 8 | expect.assertions(1) 9 | const expected = Error('arity must be >= 1') 10 | const actual = () => liftP(0) 11 | expect(actual).toThrowError(expected) 12 | }) 13 | 14 | test('1 arity function is lifted', () => { 15 | expect.assertions(1) 16 | const expected = 4 17 | const actual = liftP(1)(increase)(Promise.resolve(3)) 18 | return expect(actual).resolves.toBe(expected) 19 | }) 20 | 21 | test('2 arity function is lifted', () => { 22 | expect.assertions(1) 23 | const expected = 7 24 | const actual = liftP(2)(add)(Promise.resolve(3))(Promise.resolve(4)) 25 | return expect(actual).resolves.toBe(expected) 26 | }) 27 | 28 | test('liftP works without Promises too', () => { 29 | expect.assertions(1) 30 | const expected = 7 31 | const actual = liftP(2)(add)(Promise.resolve(3))(4) 32 | return expect(actual).resolves.toBe(expected) 33 | }) 34 | }) 35 | -------------------------------------------------------------------------------- /function/__tests__/swap.test.js: -------------------------------------------------------------------------------- 1 | const swap = require('../swap') 2 | 3 | describe('function/swap', () => { 4 | test('swaps arguments', () => { 5 | const func = a => b => `${a}${b}` 6 | const swapped = swap(func) 7 | const expected = 'BA' 8 | const actual = swapped('A')('B') 9 | expect(actual).toBe(expected) 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /function/__tests__/tap.test.js: -------------------------------------------------------------------------------- 1 | const tap = require('../tap') 2 | 3 | describe('function/tap', () => { 4 | test('returns original argument', () => { 5 | const func = () => 666 6 | const tapped = tap(func) 7 | const expected = 888 8 | const actual = tapped(expected) 9 | expect(actual).toBe(expected) 10 | }) 11 | 12 | test('async returns original argument', () => { 13 | const func = () => Promise.resolve(666) 14 | const tapped = tap(func) 15 | const expected = 888 16 | const actual = tapped(expected) 17 | expect(actual).resolves.toBe(expected) 18 | }) 19 | 20 | test('returns executes function with argument', () => { 21 | const func = jest.fn() 22 | const tapped = tap(func) 23 | const expected = 888 24 | 25 | tapped(expected) 26 | const actual = func.mock.calls[0][0] 27 | 28 | expect(actual).toBe(expected) 29 | }) 30 | 31 | test('exception bubbles up', () => { 32 | const expected = Error('Oops') 33 | const func = () => { throw Error('Oops') } 34 | const tapped = tap(func) 35 | const actual = () => tapped(expected) 36 | expect(actual).toThrowError(expected) 37 | }) 38 | 39 | test('async exception bubbles up', () => { 40 | const expected = Error('Oops') 41 | const func = () => Promise.reject(expected) 42 | const tapped = tap(func) 43 | const actual = tapped(888) 44 | expect(actual).rejects.toBe(expected) 45 | }) 46 | }) 47 | -------------------------------------------------------------------------------- /function/curry.js: -------------------------------------------------------------------------------- 1 | const signature = require('../_internal/debug/signature') 2 | 3 | // curry2 :: Function -> Any -> Any -> Any 4 | let curry2 = func => a => b => func(a, b) 5 | 6 | // curry3 :: Function -> Any -> Any -> Any -> Any 7 | let curry3 = func => a => b => c => func(a, b, c) 8 | 9 | // curry4 :: Function -> Any -> Any -> Any -> Any -> Any 10 | let curry4 = func => a => b => c => d => func(a, b, c, d) 11 | 12 | // curry5 :: Function -> Any -> Any -> Any -> Any -> Any -> Any 13 | let curry5 = func => a => b => c => d => e => func(a, b, c, d, e) 14 | 15 | // curry6 :: Function -> Any -> Any -> Any -> Any -> Any -> Any -> Any 16 | let curry6 = func => a => b => c => d => e => f => func(a, b, c, d, e, f) 17 | 18 | const curry = n => ( 19 | n === 2 ? curry2 20 | : n === 3 ? curry3 21 | : n === 4 ? curry4 22 | : n === 5 ? curry5 23 | : n === 6 ? curry6 24 | : (() => { throw new Error(`Cannot curry a function with ${n} arguments.`) })()) 25 | 26 | module.exports = curry 27 | 28 | // Experimental debug code 29 | /* istanbul ignore next */ 30 | if (process.env.MOJI_DEBUG === 'true') { 31 | curry2 = signature({ method: 'curry', args: [ 'Function', 'Any', 'Any' ], returnType: 'Any' })(curry2) 32 | curry3 = signature({ method: 'curry', args: [ 'Function', 'Any', 'Any', 'Any' ], returnType: 'Any' })(curry3) 33 | curry4 = signature({ method: 'curry', args: [ 'Function', 'Any', 'Any', 'Any', 'Any' ], returnType: 'Any' })(curry4) 34 | curry5 = signature({ method: 'curry', args: [ 'Function', 'Any', 'Any', 'Any', 'Any', 'Any' ], returnType: 'Any' })(curry5) 35 | curry6 = signature({ method: 'curry', args: [ 'Function', 'Any', 'Any', 'Any', 'Any', 'Any', 'Any' ], returnType: 'Any' })(curry6) 36 | } 37 | -------------------------------------------------------------------------------- /function/curry.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: curry 3 | menu: function 4 | --- 5 | 6 | # function/curry 7 | 8 | `curry :: Number -> Function -> Function` 9 | 10 | Returns a curried equivalent of the provided function, with the specified arity. This function is provided for JavaScript interop and should rarely be needed inside of MojiScript files. 11 | 12 | # Example 13 | 14 | ```javascript 15 | import { curry } from 'mojiscript' 16 | import fs from 'fs' 17 | 18 | const readFileSync = curry (2) (fs.readFileSync) 19 | 20 | const data = readFileSync ('file.txt') ('utf8') 21 | ``` 22 | -------------------------------------------------------------------------------- /function/lift.js: -------------------------------------------------------------------------------- 1 | const is = require('../type/is') 2 | const liftA = require('./liftA') 3 | const liftP = require('./liftP') 4 | 5 | const isMapable = object => object && is(Function)(object.map) 6 | const isThenable = object => object && is(Function)(object.then) 7 | 8 | const lift = arity => func => object => 9 | isMapable(object) ? liftA(arity)(func)(object) 10 | : isThenable(object) ? liftP(arity)(func)(object) 11 | : (() => { throw new Error(`Cannot lift unsupported type "${typeof object}"`) })() 12 | 13 | module.exports = lift 14 | -------------------------------------------------------------------------------- /function/lift.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: lift 3 | menu: function 4 | --- 5 | 6 | # function/lift 7 | 8 | Lift's a function into a `Thenable` (`Promise`) or `Mappable` (`Array`) type. 9 | 10 | ## Simple Array Example 11 | 12 | ```javascript 13 | import { lift } from 'mojiscript' 14 | 15 | // add :: Number -> Number -> Number 16 | const add = x => y => x + y 17 | 18 | // addP :: Promise -> Promise -> Promise 19 | const addP = lift (2) (add) 20 | 21 | const a = 3 22 | const b = 4 23 | add (3) (4) //=> 7 24 | 25 | const aP = [ 3 ] 26 | const bP = [ 4 ] 27 | addP (aP) (bP) //=> [ 7 ] 28 | ``` 29 | 30 | ## Compound Array Example 31 | 32 | ```javascript 33 | import lift from './function/lift' 34 | 35 | // combine :: Suit -> Rank -> String 36 | const combine = suit => rank => `${rank} ${suit}` 37 | 38 | // combineP :: Array -> Array -> Array 39 | const combineP = lift (2) (combine) 40 | 41 | const suits = [ '️♠️️', '♥️', '♦️', '♣️' ] 42 | const ranks = [ 'A', 'K', 'Q', 'J', 'T', '9', '8', '7', '6', '5', '4', '3', '2' ] 43 | const deck = combineP (suits) (ranks) 44 | /** 45 | * [ 'A ️♠️️','K ️♠️️','Q ️♠️️','J ️♠️️','T ️♠️️','9 ️♠️️','8 ️♠️️','7 ️♠️️','6 ️♠️️','5 ️♠️️','4 ️♠️️','3 ️♠️️','2 ️♠️️', 46 | * 'A ♥️','K ♥️','Q ♥️','J ♥️','T ♥️','9 ♥️','8 ♥️','7 ♥️','6 ♥️','5 ♥️','4 ♥️','3 ♥️','2 ♥️', 47 | * 'A ♦️','K ♦️','Q ♦️','J ♦️','T ♦️','9 ♦️','8 ♦️','7 ♦️','6 ♦️','5 ♦️','4 ♦️','3 ♦️','2 ♦️', 48 | * 'A ♣️','K ♣️','Q ♣️','J ♣️','T ♣️','9 ♣️','8 ♣️','7 ♣️','6 ♣️','5 ♣️','4 ♣️','3 ♣️','2' ] 49 | */ 50 | ``` 51 | 52 | ## Promise Example 53 | 54 | ```javascript 55 | import { lift } from 'mojiscript' 56 | 57 | // add :: Number -> Number -> Number 58 | const add = x => y => x + y 59 | 60 | // addP :: Promise -> Promise -> Promise 61 | const addP = lift (2) (add) 62 | 63 | const a = 3 64 | const b = 4 65 | add (3) (4) //=> 7 66 | 67 | const aP = Promise.resolve(3) 68 | const bP = Promise.resolve(4) 69 | addP (aP) (bP) //=> Promise (7) 70 | ``` 71 | 72 | ## Ajax Example 73 | 74 | This example makes 2 Ajax calls. The 2nd call depends on the results of the first. 75 | 76 | ```javascript 77 | import axios from 'axios' 78 | import { lift } from 'mojiscript' 79 | 80 | const fetchData = url => 81 | axios.get (url) 82 | .then (x => x.data) 83 | 84 | const createMessage = person => homeworld => 85 | `${person.name}'s homeworld is ${homeworld.name}` 86 | const createMessageP = lift (2) (createMessage) 87 | // --------- 88 | // / 89 | // lift createMessage with arity 2 into the Promise type 90 | 91 | const fetchHomeworld = lift (1) (person => fetchData (person.homeworld)) 92 | // --------- 93 | // / 94 | // lift the function into the Promise type 95 | // \ 96 | // --------- 97 | const writeLog = lift (1) (log => console.log (log)) 98 | 99 | const person = fetchData ('https://swapi.co/api/people/1') 100 | const homeworld = fetchHomeworld (person) 101 | const message = createMessageP (person) (homeworld) 102 | 103 | writeLog (message) 104 | //=> "Luke Skywalker's homeworld is Tatooine" 105 | ``` 106 | 107 | ### Parameters 108 | 109 | | Name | Type | Description | 110 | | ---- | ---- | ----------- | 111 | | arity | `Number` | Arity or number of arguments in the function. | 112 | | function | `Function` | Function to lift into the `Promise` type. | 113 | | args | `Args` | N number of arguments matching the Arity. | 114 | 115 | ### Returns 116 | 117 | Returns a promise containing the return value of the function. 118 | -------------------------------------------------------------------------------- /function/liftA.js: -------------------------------------------------------------------------------- 1 | const ap = require('../list/ap') 2 | const map = require('../list/map') 3 | 4 | const liftA = arity => { 5 | if (arity < 1) throw new Error('arity must be >= 1') 6 | 7 | return func => array => { 8 | const next = arity => apply => array => { 9 | const value = ap(apply)(array) 10 | return arity < 2 ? value : next(arity - 1)(value) 11 | } 12 | 13 | const value = map(func)(array) 14 | return arity < 2 ? value : next(arity - 1)(value) 15 | } 16 | } 17 | 18 | module.exports = liftA 19 | -------------------------------------------------------------------------------- /function/liftP.js: -------------------------------------------------------------------------------- 1 | const liftP = arity => { 2 | if (arity < 1) throw new Error('arity must be >= 1') 3 | return func => promise => { 4 | const next = Promise.resolve(promise).then(value => Promise.resolve(func).then(f => f(value))) 5 | return (arity < 2) ? next : liftP(arity - 1)(next) 6 | } 7 | } 8 | 9 | module.exports = liftP 10 | -------------------------------------------------------------------------------- /function/swap.js: -------------------------------------------------------------------------------- 1 | const signature = require('../_internal/debug/signature') 2 | 3 | // swap :: Function -> Function 4 | const swap = func => a => b => func(b)(a) 5 | 6 | module.exports = swap 7 | 8 | // Experimental debug code 9 | /* istanbul ignore next */ 10 | if (process.env.MOJI_DEBUG === 'true') { 11 | module.exports = signature('swap :: Function -> Function')(swap) 12 | } 13 | -------------------------------------------------------------------------------- /function/swap.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: swap 3 | menu: function 4 | --- 5 | 6 | # function/swap 7 | 8 | `swap :: Function -> Function` 9 | 10 | Swaps the order of the first two arguments of a `Function`. 11 | 12 | # Example 13 | 14 | ```javascript 15 | import { swap } from 'mojiscript' 16 | 17 | const append = a => b => `${a}${b}` 18 | const swapped = swap(append) 19 | 20 | swapped('A')('B') //=> 'BA' 21 | ``` 22 | 23 | ##### Parameters 24 | 25 | | Name | Type | Description | 26 | | ---- | ---- | ----------- | 27 | | function | `Function` | Function to swap arguments | 28 | 29 | ##### Returns 30 | 31 | Returns the `Function` with the first two arguments swapped. 32 | -------------------------------------------------------------------------------- /function/tap.js: -------------------------------------------------------------------------------- 1 | const signature = require('../_internal/debug/signature') 2 | const isThenable = require('../_internal/isThenable') 3 | 4 | // tap :: Function -> Any -> Any 5 | const tap = func => value => { 6 | const result = func(value) 7 | return isThenable(result) ? result.then(() => value) : value 8 | } 9 | 10 | module.exports = tap 11 | 12 | // Experimental debug code 13 | /* istanbul ignore next */ 14 | if (process.env.MOJI_DEBUG === 'true') { 15 | module.exports = signature('tap :: Function -> Any -> Any')(tap) 16 | } 17 | -------------------------------------------------------------------------------- /function/tap.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: tap 3 | menu: function 4 | --- 5 | 6 | # function/tap 7 | 8 | `tap :: Function -> Any -> Any` 9 | 10 | Runs the given function with the supplied object, then returns the object. `tap` is typically used when performing side-effects. 11 | 12 | # Example 13 | 14 | ```javascript 15 | import { log, pipe, run, tap } from 'mojiscript' 16 | import axios from 'mojiscript/net/axios' 17 | 18 | const getUser = () => ({ 19 | name: 'paul rudd', 20 | movies: ['I Love You Man', 'Role Models'] 21 | }) 22 | 23 | const main = pipe ([ 24 | getUser, 25 | 26 | // the axios response is ignored and the user is returned. 27 | tap (user => axios.post ('https://reqres.in/api/users') (user) ()), 28 | 29 | log 30 | ]) 31 | 32 | run ({ main }) 33 | //=> { name: "paul rudd", movies: ["I Love You Man", "Role Models"] } 34 | ``` 35 | 36 | ##### Parameters 37 | 38 | | Name | Type | Description | 39 | | ---- | ---- | ----------- | 40 | | function | `Function` | Function to execute | 41 | | value | `Object` | Input object for function and return value of the `tap`. | 42 | 43 | ##### Returns 44 | 45 | Returns the object originally given as an input. 46 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports.I = require('./combinators/I') 2 | module.exports.K = require('./combinators/K') 3 | module.exports.S = require('./combinators/S') 4 | module.exports.W = require('./combinators/W') 5 | module.exports.error = require('./console/error') 6 | module.exports.log = require('./console/log') 7 | module.exports.logF = require('./console/logF') 8 | module.exports.pipe = require('./core/pipe') 9 | module.exports.into = require('./core/into') 10 | module.exports.pipeR = require('./core/pipeR') 11 | module.exports.run = require('./core/run') 12 | module.exports.curry = require('./function/curry') 13 | module.exports.lift = require('./function/lift') 14 | module.exports.liftA = require('./function/liftA') 15 | module.exports.liftP = require('./function/liftP') 16 | module.exports.swap = require('./function/swap') 17 | module.exports.tap = require('./function/tap') 18 | module.exports.ap = require('./list/ap') 19 | module.exports.filter = require('./list/filter') 20 | module.exports.flatMap = require('./list/flatMap') 21 | module.exports.head = require('./list/head') 22 | module.exports.join = require('./list/join') 23 | module.exports.map = require('./list/map') 24 | module.exports.range = require('./list/range') 25 | module.exports.reduce = require('./list/reduce') 26 | module.exports.reduceWhile = require('./list/reduceWhile') 27 | module.exports.sort = require('./list/sort') 28 | module.exports.tail = require('./list/tail') 29 | module.exports.allPass = require('./logic/allPass') 30 | module.exports.anyPass = require('./logic/anyPass') 31 | module.exports.cond = require('./logic/cond') 32 | module.exports.ifElse = require('./logic/ifElse') 33 | module.exports.ifError = require('./logic/ifError') 34 | module.exports.unless = require('./logic/unless') 35 | module.exports.when = require('./logic/when') 36 | module.exports.clone = require('./object/clone') 37 | module.exports.pathOr = require('./object/pathOr') 38 | module.exports.propOr = require('./object/propOr') 39 | module.exports.append = require('./string/append') 40 | module.exports.prepend = require('./string/prepend') 41 | module.exports.replace = require('./string/replace') 42 | module.exports.$ = require('./string/template') 43 | module.exports.sleep = require('./threading/sleep') 44 | module.exports.Either = require('./type/Either') 45 | module.exports.is = require('./type/is') 46 | module.exports.Just = require('./type/Just') 47 | module.exports.Left = require('./type/Left') 48 | module.exports.Maybe = require('./type/Maybe') 49 | module.exports.Nothing = require('./type/Nothing') 50 | module.exports.Right = require('./type/Right') 51 | -------------------------------------------------------------------------------- /index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: Introduction 3 | route: '/' 4 | order: 100 5 | --- 6 | 7 | # Introduction 8 | 9 | ![MojiScript logo](https://raw.githubusercontent.com/joelnet/MojiScript/master/media/MS_logo_64.png) 10 | 11 | MojiScript is an Async First, opinionated, and functional language designed to have 100% compatibility with JavaScript engines. This will allow full access to JavaScript modules (NPM) and all tooling already available to JavaScript. This means that MojiScript language features can run in any JavaScript application and vice-versa. 12 | 13 | MojiScript's design is derived from Functional Programming concepts such as Currying, Partial Application, Function Composition, Category Theory, and Atomic Design. 14 | 15 | ## Philosophy 16 | 17 | The MojiScript philosophy is to provide a functional-style application framework, making asynchronous tasks intuitive and easy. 18 | 19 | MojiScript is heavily opinionated and prevents code considered to be async-unfriendly like `for` loops and statement blocks. 20 | 21 | ## Benefits 22 | 23 | - The Asynchronous-first design greatly simplifies writing and reasoning about Asynchronous code. Worry less about callbacks, promises, async, await, etc. 24 | - Atomic Design, function composition, and Pipes encourages maximum code re-use, testability and the ability to compose smaller functions into larger ones. 25 | - Compatibility with ECMAScript gives our applications full access to the JavaScript ecosystem. It also allows us to import elements from MojiScript into existing JavaScript applications. 26 | - A modular design allows for features to be imported on an as needed basis, keeping packages small. 27 | - Plays well with functional libraries. Check out the [Complementary Libraries](#complementary-libraries) section for libraries that can benefit your MojiScript applications. 28 | -------------------------------------------------------------------------------- /list/__tests__/ap.test.js: -------------------------------------------------------------------------------- 1 | const ap = require('../ap') 2 | 3 | describe('list/ap', () => { 4 | describe('Array', () => { 5 | const expected = [ 1, 2, 3, 4, 5, 1, 16, 81, 256, 625 ] 6 | const actual = ap([ Math.sqrt, x => x * x ])([ 1, 4, 9, 16, 25 ]) 7 | expect(actual).toEqual(expected) 8 | }) 9 | 10 | describe('Functor', () => { 11 | const monad = value => ({ 12 | value, 13 | flatMap: func => func(value), 14 | map: func => monad(func(value)) 15 | }) 16 | 17 | test('ap', () => { 18 | const expected = 888 19 | const double = num => num * 2 20 | const fDouble = monad(double) 21 | const value = monad(444) 22 | const actual = ap(fDouble)(value).value 23 | expect(actual).toBe(expected) 24 | }) 25 | }) 26 | }) 27 | -------------------------------------------------------------------------------- /list/__tests__/filter.test.js: -------------------------------------------------------------------------------- 1 | const filter = require('../filter') 2 | 3 | describe('list/filter', () => { 4 | const isOdd = num => num % 2 !== 0 5 | const asyncIsOdd = num => Promise.resolve(num).then(isOdd) 6 | const asyncWhenIsOdd = num => isOdd(num) ? true : asyncIsOdd(num) 7 | 8 | function* iterator() { 9 | yield 1 10 | yield 2 11 | yield 3 12 | } 13 | 14 | test('sync array', () => { 15 | const expected = [ 1, 3 ] 16 | const actual = filter(isOdd)([ 1, 2, 3 ]) 17 | expect(actual).toMatchObject(expected) 18 | }) 19 | 20 | test('async array', () => { 21 | expect.assertions(1) 22 | const expected = [ 1, 3 ] 23 | const actual = filter(asyncIsOdd)([ 1, 2, 3 ]) 24 | return expect(actual).resolves.toMatchObject(expected) 25 | }) 26 | 27 | test('sync iterator', () => { 28 | const expected = [ 1, 3 ] 29 | const actual = filter(isOdd)(iterator()) 30 | expect(actual).toMatchObject(expected) 31 | }) 32 | 33 | test('async iterator', () => { 34 | expect.assertions(1) 35 | const expected = [ 1, 3 ] 36 | const actual = filter(asyncIsOdd)(iterator()) 37 | return expect(actual).resolves.toMatchObject(expected) 38 | }) 39 | 40 | test('mixed array', () => { 41 | expect.assertions(1) 42 | const expected = [ 1, 3 ] 43 | const actual = filter(asyncWhenIsOdd)([ 1, 2, 3 ]) 44 | return expect(actual).resolves.toMatchObject(expected) 45 | }) 46 | 47 | test('mixed iterator', () => { 48 | expect.assertions(1) 49 | const expected = [ 1, 3 ] 50 | const actual = filter(asyncWhenIsOdd)(iterator()) 51 | return expect(actual).resolves.toMatchObject(expected) 52 | }) 53 | 54 | test('filter does not cache', () => { 55 | expect.assertions(1) 56 | const expected = [ 1, 3 ] 57 | const filterOdd = filter(isOdd) 58 | filterOdd([ 1, 2, 3 ]) 59 | const actual = filterOdd([ 1, 2, 3 ]) 60 | expect(actual).toEqual(expected) 61 | }) 62 | }) 63 | -------------------------------------------------------------------------------- /list/__tests__/flatMap.test.js: -------------------------------------------------------------------------------- 1 | const flatMap = require('../flatMap') 2 | 3 | describe('flatMap', () => { 4 | const double = x => x * 2 5 | 6 | describe('Functor', () => { 7 | test('flatMap on Functor', () => { 8 | const expected = 888 9 | const Functor = { 10 | flatMap: func => func(444) 11 | } 12 | const actual = flatMap(double)(Functor) 13 | expect(actual).toBe(expected) 14 | }) 15 | 16 | test('fantasy-land/chain on Functor', () => { 17 | const expected = 888 18 | const Functor = { 19 | 'fantasy-land/chain': func => func(444) 20 | } 21 | const actual = flatMap(double)(Functor) 22 | expect(actual).toBe(expected) 23 | }) 24 | }) 25 | 26 | describe('Array', () => { 27 | test('basic flatMap with numbers', () => { 28 | const expected = [ 2, 4, 6, 8 ] 29 | const array = [ 1, 2, 3, 4 ] 30 | const actual = flatMap(x => [ double(x) ])(array) 31 | expect(actual).toEqual(expected) 32 | }) 33 | 34 | test('basic flatMap strings', () => { 35 | const expected = [ 'it\'s', 'Sunny', 'in', '', 'California' ] 36 | const array = [ 'it\'s Sunny in', '', 'California' ] 37 | const actual = flatMap(x => x.split(' '))(array) 38 | expect(actual).toEqual(expected) 39 | }) 40 | 41 | test('async', () => { 42 | expect.assertions(1) 43 | const expected = [ 2, 4, 6, 8 ] 44 | const array = [ 1, 2, 3, 4 ] 45 | const actual = flatMap(x => Promise.resolve(x).then(x => [ double(x) ]))(array) 46 | return expect(actual).resolves.toEqual(expected) 47 | }) 48 | }) 49 | }) 50 | -------------------------------------------------------------------------------- /list/__tests__/head.test.js: -------------------------------------------------------------------------------- 1 | const head = require('../head') 2 | const range = require('../range') 3 | 4 | describe('list/head', () => { 5 | test('head of array', () => { 6 | expect.assertions(1) 7 | const expected = 1 8 | const actual = head([ 1, 2, 3 ]) 9 | 10 | expect(actual).toBe(expected) 11 | }) 12 | 13 | test('head of range', () => { 14 | expect.assertions(1) 15 | const expected = 3 16 | const actual = head(range(3)(6)) 17 | 18 | expect(actual).toBe(expected) 19 | }) 20 | 21 | test('head of null is not supported', () => { 22 | expect.assertions(1) 23 | const expected = Error('type object is not supported') 24 | const actual = () => head(null) 25 | expect(actual).toThrow(expected) 26 | }) 27 | 28 | test('head of object is not supported', () => { 29 | expect.assertions(1) 30 | const expected = Error('type object is not supported') 31 | const actual = () => head({}) 32 | expect(actual).toThrow(expected) 33 | }) 34 | }) 35 | -------------------------------------------------------------------------------- /list/__tests__/join.test.js: -------------------------------------------------------------------------------- 1 | const join = require('../join') 2 | 3 | describe('list/join', () => { 4 | test('joins', () => { 5 | const expected = '1,2,3' 6 | const actual = join(',')([ 1, 2, 3 ]) 7 | expect(actual).toBe(expected) 8 | }) 9 | }) 10 | -------------------------------------------------------------------------------- /list/__tests__/map.test.js: -------------------------------------------------------------------------------- 1 | const map = require('../map') 2 | const Just = require('../../type/Just') 3 | const Nothing = require('../../type/Nothing') 4 | 5 | describe('list/map', () => { 6 | const isOdd = num => num % 2 !== 0 7 | const double = num => num * 2 8 | const asyncDouble = num => Promise.resolve(num).then(double) 9 | const asyncWhenOddDouble = num => isOdd(num) ? double(num) : Promise.resolve(num).then(double) 10 | function* iterator() { 11 | yield 1 12 | yield 2 13 | yield 3 14 | } 15 | 16 | test('sync array', () => { 17 | expect.assertions(1) 18 | const expected = [ 2, 4, 6 ] 19 | const actual = map(double)([ 1, 2, 3 ]) 20 | expect(actual).toMatchObject(expected) 21 | }) 22 | 23 | test('async array', () => { 24 | expect.assertions(1) 25 | const expected = [ 2, 4, 6 ] 26 | const actual = map(asyncDouble)([ 1, 2, 3 ]) 27 | return expect(actual).resolves.toMatchObject(expected) 28 | }) 29 | 30 | test('sync iterable', () => { 31 | expect.assertions(1) 32 | const expected = [ 2, 4, 6 ] 33 | const actual = map(double)(iterator()) 34 | expect(actual).toMatchObject(expected) 35 | }) 36 | 37 | test('async iterable', () => { 38 | expect.assertions(1) 39 | const expected = [ 2, 4, 6 ] 40 | const actual = map(asyncDouble)(iterator()) 41 | return expect(actual).resolves.toMatchObject(expected) 42 | }) 43 | 44 | test('mixed array', () => { 45 | expect.assertions(1) 46 | const expected = [ 2, 4, 6 ] 47 | const actual = map(asyncWhenOddDouble)([ 1, 2, 3 ]) 48 | return expect(actual).resolves.toMatchObject(expected) 49 | }) 50 | 51 | test('mixed iterable', () => { 52 | expect.assertions(1) 53 | const expected = [ 2, 4, 6 ] 54 | const actual = map(asyncWhenOddDouble)(iterator()) 55 | return expect(actual).resolves.toMatchObject(expected) 56 | }) 57 | 58 | test('map does not cache', () => { 59 | expect.assertions(1) 60 | const expected = [ 2, 4 ] 61 | const mapDouble = map(double) 62 | mapDouble([ 1, 2 ]) 63 | const actual = mapDouble([ 1, 2 ]) 64 | expect(actual).toEqual(expected) 65 | }) 66 | 67 | test('Just', () => { 68 | expect.assertions(1) 69 | const expected = 888 70 | const actual = map(double)(Just(444)).value 71 | expect(actual).toBe(expected) 72 | }) 73 | 74 | test('functor', () => { 75 | expect.assertions(1) 76 | const expected = 888 77 | const actual = map(double)({ map: func => func(444) }) 78 | expect(actual).toBe(expected) 79 | }) 80 | 81 | test('Nothing', () => { 82 | expect.assertions(1) 83 | const expected = Nothing 84 | const actual = map(double)(expected) 85 | expect(actual).toBe(expected) 86 | }) 87 | 88 | test('unmappable', () => { 89 | expect.assertions(1) 90 | const expected = TypeError('Object is not mappable.') 91 | const actual = () => map(double)(() => {}) 92 | expect(actual).toThrowError(expected) 93 | }) 94 | }) 95 | -------------------------------------------------------------------------------- /list/__tests__/range.test.js: -------------------------------------------------------------------------------- 1 | const range = require('../range') 2 | 3 | describe('list/range', () => { 4 | test('creates range', () => { 5 | const expected = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ] 6 | const actual = [ ...range(0)(10) ] 7 | expect(actual).toMatchObject(expected) 8 | }) 9 | }) 10 | -------------------------------------------------------------------------------- /list/__tests__/reduce.test.js: -------------------------------------------------------------------------------- 1 | const range = require('../range') 2 | const reduce = require('../reduce') 3 | 4 | describe('list/reduce', () => { 5 | const isEven = num => num % 2 === 0 6 | const add = x => y => x + y 7 | const asyncAdd = x => y => Promise.resolve(y).then(add(x)) 8 | const asyncWhenEvenAdd = x => y => isEven(y) ? asyncAdd(x)(y) : add(x)(y) 9 | 10 | test('sync array', () => { 11 | const expected = 6 12 | const actual = reduce(add)(0)([ 1, 2, 3 ]) 13 | expect(actual).toBe(expected) 14 | }) 15 | 16 | test('async array', () => { 17 | expect.assertions(1) 18 | const expected = 6 19 | const actual = reduce(asyncAdd)(0)([ 1, 2, 3 ]) 20 | return expect(actual).resolves.toBe(expected) 21 | }) 22 | 23 | test('sync iterable', () => { 24 | const expected = 6 25 | const actual = reduce(add)(0)(range(1)(4)) 26 | expect(actual).toBe(expected) 27 | }) 28 | 29 | test('async iterable', () => { 30 | expect.assertions(1) 31 | const expected = 6 32 | const actual = reduce(asyncAdd)(0)(range(1)(4)) 33 | return expect(actual).resolves.toBe(expected) 34 | }) 35 | 36 | test('async array mixed', () => { 37 | expect.assertions(1) 38 | const expected = 6 39 | const actual = reduce(asyncWhenEvenAdd)(0)([ 1, 2, 3 ]) 40 | return expect(actual).resolves.toBe(expected) 41 | }) 42 | 43 | test('async iterable mixed', () => { 44 | expect.assertions(1) 45 | const expected = 6 46 | const actual = reduce(asyncWhenEvenAdd)(0)([ 1, 2, 3 ]) 47 | return expect(actual).resolves.toBe(expected) 48 | }) 49 | 50 | test('async iterable mixed', () => { 51 | expect.assertions(1) 52 | const expected = 6 53 | const actual = reduce(asyncWhenEvenAdd)(0)(range(0)(4)) 54 | return expect(actual).resolves.toBe(expected) 55 | }) 56 | }) 57 | -------------------------------------------------------------------------------- /list/__tests__/sort.test.js: -------------------------------------------------------------------------------- 1 | const sort = require('../sort') 2 | const range = require('../range') 3 | 4 | describe('list/sort', () => { 5 | const descending = a => b => b - a 6 | const ascending = a => b => a - b 7 | function* iterator() { 8 | yield 5 9 | yield 2 10 | yield 3 11 | } 12 | 13 | test('sort range ascending', () => { 14 | const expected = [ 5, 6, 7, 8, 9 ] 15 | const actual = sort(ascending)(range(5)(10)) 16 | 17 | expect(actual).toEqual(expected) 18 | }) 19 | 20 | test('sort array ascending', () => { 21 | const expected = [ 5, 6, 7, 8, 9 ] 22 | const actual = sort(ascending)([ 5, 6, 7, 8, 9 ]) 23 | 24 | expect(actual).toEqual(expected) 25 | }) 26 | 27 | test('sort iterable ascending', () => { 28 | const expected = [ 2, 3, 5 ] 29 | const actual = sort(ascending)(iterator()) 30 | 31 | expect(actual).toEqual(expected) 32 | }) 33 | 34 | test('sort range descending', () => { 35 | const expected = [ 9, 8, 7, 6, 5 ] 36 | const actual = sort(descending)(range(5)(10)) 37 | 38 | expect(actual).toEqual(expected) 39 | }) 40 | 41 | test('sort array descending', () => { 42 | const expected = [ 9, 8, 7, 6, 5 ] 43 | const actual = sort(descending)([ 5, 6, 7, 8, 9 ]) 44 | 45 | expect(actual).toEqual(expected) 46 | }) 47 | 48 | test('sort iterable descending', () => { 49 | const expected = [ 5, 3, 2 ] 50 | const actual = sort(descending)(iterator()) 51 | 52 | expect(actual).toEqual(expected) 53 | }) 54 | }) 55 | -------------------------------------------------------------------------------- /list/__tests__/tail.test.js: -------------------------------------------------------------------------------- 1 | const tail = require('../tail') 2 | const range = require('../range') 3 | 4 | describe('list/tail', () => { 5 | test('tail of array', () => { 6 | expect.assertions(1) 7 | const expected = [ 2, 3 ] 8 | const actual = tail([ 1, 2, 3 ]) 9 | 10 | expect(actual).toEqual(expected) 11 | }) 12 | 13 | test('tail of range', () => { 14 | expect.assertions(1) 15 | const expected = [ 4, 5, 6 ] 16 | const actual = tail(range(3)(7)) 17 | 18 | expect(actual).toEqual(expected) 19 | }) 20 | 21 | test('tail of null is not supported', () => { 22 | expect.assertions(1) 23 | const expected = Error('type object is not supported') 24 | const actual = () => tail(null) 25 | expect(actual).toThrow(expected) 26 | }) 27 | 28 | test('tail of object is not supported', () => { 29 | expect.assertions(1) 30 | const expected = Error('type object is not supported') 31 | const actual = () => tail({}) 32 | expect(actual).toThrow(expected) 33 | }) 34 | }) 35 | -------------------------------------------------------------------------------- /list/ap.js: -------------------------------------------------------------------------------- 1 | const flatMap = require('./flatMap') 2 | const map = require('./map') 3 | 4 | // ap :: Apply f => f (a -> b) -> f a -> f b 5 | const ap = func => value => flatMap(f => map(v => f(v))(value))(func) 6 | 7 | module.exports = ap 8 | -------------------------------------------------------------------------------- /list/ap.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: ap 3 | menu: list 4 | --- 5 | 6 | # list/ap 7 | 8 | `ap :: Apply f => f (a -> b) -> f a -> f b` 9 | 10 | ap applies a list of functions to a list of values. 11 | 12 | ## Array Example 13 | 14 | ```javascript 15 | import { ap } from 'mojiscript' 16 | 17 | ap ([Math.sqrt, x => x * x]) ([1, 4, 9, 16, 25]) 18 | //=> [ 1, 2, 3, 4, 5, 1, 16, 81, 256, 625 ] 19 | ``` 20 | 21 | ## Functor Example 22 | 23 | ```javascript 24 | import { ap, Just } from 'mojiscript' 25 | 26 | const sqrt = Just (Math.sqrt) 27 | const value = Just (64) 28 | 29 | ap (sqrt) (value) 30 | //=> Just (8) 31 | ``` 32 | 33 | ## Parameters 34 | 35 | | Name | Type | Description | 36 | | ---- | ---- | ----------- | 37 | | func | `Apply` | An `Apply` that contains a `Function`. | 38 | | value | `Iterable` | An `Apply` that contains a value. | 39 | 40 | ## Returns 41 | 42 | Returns an `Apply` with the function applied to the value. 43 | -------------------------------------------------------------------------------- /list/filter.js: -------------------------------------------------------------------------------- 1 | const signature = require('../_internal/debug/signature') 2 | const call = require('../_internal/call') 3 | const reduceWhile = require('./reduceWhile') 4 | 5 | const asyncFilterReducer = func => acc => x => call(val => (val && acc.push(x), acc))(func(x)) 6 | 7 | // filter :: Function -> Filterable -> Array 8 | const filter = predicate => iterable => 9 | reduceWhile (null) (asyncFilterReducer (predicate)) ([]) (iterable) // eslint-disable-line 10 | 11 | module.exports = filter 12 | 13 | // Experimental debug code 14 | /* istanbul ignore next */ 15 | if (process.env.MOJI_DEBUG === 'true') { 16 | module.exports = signature('filter :: Function -> Filterable -> Array')(filter) 17 | } 18 | -------------------------------------------------------------------------------- /list/filter.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: filter 3 | menu: list 4 | --- 5 | 6 | # list/filter 7 | 8 | `filter :: Function -> Iterable -> Array` 9 | 10 | Takes a predicate and an `Iterable` and returns an `Array` of the same type containing the members of the given filterable which satisfy the given predicate. 11 | 12 | An `Iterable` could be an `Array`, an `Iterator`, or `async Iterator`. 13 | 14 | ## Array Example 15 | 16 | ```javascript 17 | import { filter } from 'mojiscript' 18 | 19 | const isEven = num => num % 2 === 0 20 | const array = [ 1, 2, 3, 4, 5 ] 21 | 22 | filter (isEven) (array) //=> [ 2, 4 ] 23 | ``` 24 | 25 | ## Iterator Example 26 | 27 | ```javascript 28 | import { filter, range } from 'mojiscript' 29 | 30 | const isEven = num => num % 2 === 0 31 | 32 | filter (isEven) (range (1) (6)) //=> [ 2, 4 ] 33 | ``` 34 | 35 | ## Async Example 36 | 37 | ```javascript 38 | import { filter, pipe, sleep } from 'mojiscript' 39 | 40 | const isEven = num => num % 2 === 0 41 | const asyncIsEven = pipe ([ 42 | sleep (1000), 43 | isEven 44 | ]) 45 | 46 | const array = [ 1, 2, 3, 4, 5 ] 47 | 48 | filter (asyncIsEven) (array) //=> Promise ([ 2, 4, 6 ]) 49 | ``` 50 | 51 | ## Parameters 52 | 53 | | Name | Type | Description | 54 | | ---- | ---- | ----------- | 55 | | predicate | `Function` | Function takes and input and returns a `Boolean` | 56 | | iterable | `Iterable` | `Iterable` to apply the predicate to. | 57 | 58 | ## Returns 59 | 60 | Returns an `Array` filtered by the `predicate`. 61 | -------------------------------------------------------------------------------- /list/flatMap.js: -------------------------------------------------------------------------------- 1 | const signature = require('../_internal/debug/signature') 2 | const reduce = require('./reduce') 3 | const call = require('../_internal/call') 4 | 5 | const flatMapReducer = func => acc => x => call(val => acc.concat(val))(func(x)) 6 | const isFlatMapable = mapable => mapable && typeof mapable.flatMap === 'function' 7 | const isFlFlatMapable = mapable => mapable && typeof mapable['fantasy-land/chain'] === 'function' 8 | 9 | const flatMap = func => mapable => 10 | isFlatMapable(mapable) && !Array.isArray(mapable) ? mapable.flatMap(func) 11 | : isFlFlatMapable(mapable) ? mapable['fantasy-land/chain'](func) 12 | : reduce(flatMapReducer(func))([])(mapable) 13 | 14 | module.exports = flatMap 15 | 16 | // Experimental debug code 17 | /* istanbul ignore next */ 18 | if (process.env.MOJI_DEBUG === 'true') { 19 | module.exports = signature('flatMap :: Function -> FlatMapable -> Any')(flatMap) 20 | } 21 | -------------------------------------------------------------------------------- /list/flatMap.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: flatMap 3 | menu: list 4 | --- 5 | 6 | # list/flatMap 7 | 8 | `flatMap :: Function -> FlatMapable -> Any` 9 | 10 | Takes a function and a `FlatMapable` and returns an `Object`. 11 | 12 | A `FlatMapable` could be an `Array`, an `Iterator`, or `async Iterator`, or an object that implements 'flatMap'. 13 | 14 | ## Array Example 15 | 16 | ```javascript 17 | import { flatMap } from 'mojiscript' 18 | 19 | const andTimesTen = num => [ num, num * 10 ] 20 | const array = [ 1, 2, 3 ] 21 | 22 | flatMap (andTimesTen) (array) //=> [ 1, 10, 2, 20, 3, 30 ] 23 | ``` 24 | 25 | ## Iterator Example 26 | 27 | ```javascript 28 | import { flatMap } from 'mojiscript' 29 | 30 | const andTimesTen = num => [ num, num * 10 ] 31 | 32 | flatMap (andTimesTen) (range (1) (4)) //=> [ 1, 10, 2, 20, 3, 30 ] 33 | ``` 34 | 35 | ## Async Example 36 | 37 | ```javascript 38 | import { flatMap, pipe, sleep } from 'mojiscript' 39 | 40 | const andTimesTen = num => [ num, num * 10 ] 41 | const asyncAndTimesTen = pipe ([ 42 | sleep (1000), // <-- async function! 43 | andTimesTen 44 | ]) 45 | const array = [ 1, 2, 3 ] 46 | 47 | flatMap (asyncAndTimesTen) (array) //=> Promise ([ 1, 10, 2, 20, 3, 30 ]) 48 | ``` 49 | 50 | ## Parameters 51 | 52 | | Name | Type | Description | 53 | | ---- | ---- | ----------- | 54 | | function | `Function` | Function to apply to each item in the `Iterable`. | 55 | | mappable | `FlatMapable` | `FlatMapable` to apply the function to. | 56 | 57 | ## Returns 58 | 59 | Returns an `Object` with the function applied to the `FlatMapable`. 60 | -------------------------------------------------------------------------------- /list/head.js: -------------------------------------------------------------------------------- 1 | const signature = require('../_internal/debug/signature') 2 | const is = require('../type/is') 3 | 4 | const isArray = is(Array) 5 | const isFunction = is(Function) 6 | 7 | // head :: Array -> Any 8 | const head = iterable => 9 | isArray(iterable) ? iterable[0] 10 | : iterable && isFunction(iterable.next) ? iterable.next().value 11 | : (() => { throw new Error(`type ${typeof iterable} is not supported`) })() 12 | 13 | module.exports = head 14 | 15 | // Experimental debug code 16 | /* istanbul ignore next */ 17 | if (process.env.MOJI_DEBUG === 'true') { 18 | module.exports = signature('head :: Array -> Any')(head) 19 | } 20 | -------------------------------------------------------------------------------- /list/head.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: head 3 | menu: list 4 | --- 5 | 6 | # list/head 7 | 8 | `head :: Iterable -> Any` 9 | 10 | Takes an `Iterable` and returns the head of the `Iterable`. 11 | 12 | ## Example 13 | 14 | ```javascript 15 | import { log, pipe, run, head } from 'mojiscript' 16 | 17 | const main = pipe ([ 18 | [ 1, 2, 3 ], 19 | head, 20 | log 21 | ]) 22 | 23 | run ({ main }) 24 | //=> 1 25 | ``` 26 | 27 | ## Parameters 28 | 29 | | Name | Type | Description | 30 | | ---- | ---- | ----------- | 31 | | iterable | `Iterable` | `Iterable` to get the first value from. | 32 | 33 | ## Returns 34 | 35 | Returns the head of `Iterable` 36 | -------------------------------------------------------------------------------- /list/join.js: -------------------------------------------------------------------------------- 1 | const signature = require('../_internal/debug/signature') 2 | 3 | // join :: Array -> Array -> Array 4 | const join = separator => list => Array.prototype.join.call(list, separator) 5 | 6 | module.exports = join 7 | 8 | // Experimental debug code 9 | /* istanbul ignore next */ 10 | if (process.env.MOJI_DEBUG === 'true') { 11 | module.exports = signature('join :: Array -> Array -> Array')(join) 12 | } 13 | -------------------------------------------------------------------------------- /list/map.js: -------------------------------------------------------------------------------- 1 | const signature = require('../_internal/debug/signature') 2 | const call = require('../_internal/call') 3 | const reduceWhile = require('./reduceWhile') 4 | const isIterable = require('../_internal/isIterable') 5 | const isFunctor = require('../_internal/isFunctor') 6 | 7 | const asyncMapReducer = func => acc => x => call(val => (acc.push(val), acc))(func(x)) 8 | 9 | // map :: Function -> Mapable -> Array 10 | const map = func => mapable => 11 | isIterable(mapable) ? reduceWhile(null)(asyncMapReducer(func))([])(mapable) 12 | : isFunctor(mapable) ? (mapable['fantasy-land/map'] || mapable.map).call(mapable, func) 13 | : (() => { throw new TypeError('Object is not mappable.') })() 14 | 15 | module.exports = map 16 | 17 | // Experimental debug code 18 | /* istanbul ignore next */ 19 | if (process.env.MOJI_DEBUG === 'true') { 20 | module.exports = signature('map :: Function -> Mapable -> Array')(map) 21 | } 22 | -------------------------------------------------------------------------------- /list/map.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: map 3 | menu: list 4 | --- 5 | 6 | # list/map 7 | 8 | `map :: Function -> Iterable -> Array` 9 | 10 | Takes a function and an `Iterable` and returns an `Array` with the function applied to each value in the `Iterable`. 11 | 12 | An `Iterable` could be an `Array`, an `Iterator`, or `async Iterator`, or a `Functor`. 13 | 14 | ## Array Example 15 | 16 | ```javascript 17 | import { map } from 'mojiscript' 18 | 19 | const double = num => num * 2 20 | const array = [ 1, 2, 3 ] 21 | 22 | map (double) (array) //=> [ 2, 4, 6 ] 23 | ``` 24 | 25 | ## Iterator Example 26 | 27 | ```javascript 28 | import { map, range } from 'mojiscript' 29 | 30 | const double = num => num * 2 31 | 32 | const iterator = range (1) (4) 33 | 34 | map (double) (iterator) //=> [ 2, 4, 6 ] 35 | ``` 36 | 37 | ## Async Example 38 | 39 | ```javascript 40 | import { map, pipe, sleep } from 'mojiscript' 41 | 42 | const double = num => num * 2 43 | const asyncDouble = pipe ([ 44 | sleep (1000), // <-- async function! 45 | double 46 | ]) 47 | const array = [ 1, 2, 3 ] 48 | 49 | map (asyncDouble) (array) //=> Promise ([ 2, 4, 6 ]) 50 | ``` 51 | 52 | ## Parameters 53 | 54 | | Name | Type | Description | 55 | | ---- | ---- | ----------- | 56 | | function | `Function` | Function to apply to each item in the `Iterable`. | 57 | | iterable | `Iterable` | `Iterable` to apply the function to. | 58 | 59 | ## Returns 60 | 61 | Returns an `Array` with the function applied to each value in the `Iterable`. 62 | -------------------------------------------------------------------------------- /list/range.js: -------------------------------------------------------------------------------- 1 | const signature = require('../_internal/debug/signature') 2 | 3 | // range :: Number -> Number -> Iterable 4 | const range = start => function* range(end) { 5 | let current = start 6 | while (current < end) yield current++ 7 | } 8 | 9 | module.exports = range 10 | 11 | // Experimental debug code 12 | /* istanbul ignore next */ 13 | if (process.env.MOJI_DEBUG === 'true') { 14 | module.exports = signature('range :: Number -> Number -> Iterable')(range) 15 | } 16 | -------------------------------------------------------------------------------- /list/range.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: range 3 | menu: list 4 | --- 5 | 6 | # list/range 7 | 8 | `range :: Number -> Number -> Iterable` 9 | 10 | Creates an `Iterable` from `start` up to but not including the `end`. 11 | 12 | ## Example 13 | 14 | ```javascript 15 | import { log, pipe, run, map, range } from 'mojiscript' 16 | 17 | const main = pipe ([ 18 | range (0) (5), 19 | map (log) 20 | ]) 21 | 22 | run ({ main }) 23 | //=> [ 0, 1, 2, 3, 4 ] 24 | ``` 25 | 26 | ## Parameters 27 | 28 | | Name | Type | Description | 29 | | ---- | ---- | ----------- | 30 | | start | `Number` | Start `Number` of the range. | 31 | | end | `Number` | End `Number` to increment up to (but not including) | 32 | 33 | ## Returns 34 | 35 | Returns an `Iterable` starting at `start` and counting up to (but not including) `end`. 36 | -------------------------------------------------------------------------------- /list/reduce.js: -------------------------------------------------------------------------------- 1 | const signature = require('../_internal/debug/signature') 2 | const reduceWhile = require('./reduceWhile') 3 | 4 | // reduce :: Function -> Any -> Iterable -> Any 5 | const reduce = reduceWhile(null) 6 | 7 | module.exports = reduce 8 | 9 | // Experimental debug code 10 | /* istanbul ignore next */ 11 | if (process.env.MOJI_DEBUG === 'true') { 12 | module.exports = signature('reduce :: Function -> Any -> Iterable -> Any')(reduce) 13 | } 14 | -------------------------------------------------------------------------------- /list/reduce.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: reduce 3 | menu: list 4 | --- 5 | 6 | # list/reduce 7 | 8 | `reduce :: Function -> Any -> Iterable` 9 | 10 | Returns a single item by iterating through the list, successively calling the iterator function and passing it an accumulator value and the current value from the array, and then passing the result to the next call. 11 | 12 | An `Iterable` could be an `Array`, an `Iterator`, or `async Iterator`, or a `Functor`. 13 | 14 | ## Array Example 15 | 16 | ```javascript 17 | import { reduce } from 'mojiscript' 18 | 19 | const add = x => y => x + y 20 | const array = [ 1, 2, 3 ] 21 | 22 | reduce (add) (0) (array) //=> 6 23 | ``` 24 | 25 | ## Iterator Example 26 | 27 | ```javascript 28 | import { range, reduce } from 'mojiscript' 29 | 30 | const add = x => y => x + y 31 | 32 | reduce (add) (0) (range (1) (4)) //=> 6 33 | ``` 34 | 35 | # Async Example 36 | 37 | ```javascript 38 | import { pipe, reduce, sleep } from 'mojiscript' 39 | 40 | const add = x => y => x + y 41 | const asyncAdd = x => pipe ([ 42 | sleep (1000), 43 | add (x) 44 | ]) 45 | 46 | const array = [ 1, 2, 3 ] 47 | 48 | reduce (asyncAdd) (0) (array) //=> Promise (6) 49 | ``` 50 | -------------------------------------------------------------------------------- /list/reduceWhile.js: -------------------------------------------------------------------------------- 1 | const signature = require('../_internal/debug/signature') 2 | const is = require('../type/is') 3 | const isThenable = require('../_internal/isThenable') 4 | const iterableSerialReduceWhile = require('../_internal/iterableSerialReduceWhile') 5 | 6 | const isFunction = is(Function) 7 | 8 | const reduceWhileIterator = (predicate, func, initial, iterable) => { 9 | let acc = initial 10 | const iterator = iterable[Symbol.iterator]() 11 | let { value, done } = iterator.next() 12 | 13 | while (!done && (!predicate || predicate(acc)(value))) { 14 | acc = func(acc)(value) 15 | if (isThenable(acc)) { 16 | return iterableSerialReduceWhile(predicate, (a, b) => func(a)(b), null, iterator, acc) 17 | } 18 | 19 | const result = iterator.next() 20 | value = result.value // eslint-disable-line prefer-destructuring 21 | done = result.done // eslint-disable-line prefer-destructuring 22 | } 23 | 24 | return acc 25 | } 26 | 27 | // reduceWhile :: Function -> Function -> Any -> Iterable -> Any 28 | const reduceWhile = predicate => func => initial => iterable => 29 | isFunction(iterable[Symbol.asyncIterator]) 30 | ? iterableSerialReduceWhile(predicate, (a, b) => func(a)(b), initial, iterable) 31 | : reduceWhileIterator(predicate, func, initial, iterable) 32 | 33 | module.exports = reduceWhile 34 | 35 | // Experimental debug code 36 | /* istanbul ignore next */ 37 | if (process.env.MOJI_DEBUG === 'true') { 38 | module.exports = signature('reduceWhile :: Function -> Function -> Any -> Iterable -> Any')(reduceWhile) 39 | } 40 | -------------------------------------------------------------------------------- /list/reduceWhile.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: reduceWhile 3 | menu: list 4 | --- 5 | 6 | # list/reduceWhile 7 | 8 | `reduceWhile :: Predicate -> Function -> Any -> Iterable` 9 | 10 | Returns a single item by iterating through the list, successively calling the iterator function and passing it an accumulator value and the current value from the array, and then passing the result to the next call. For each iteration, check the predicate function. If the predicate is true, run the current iteration. If false, return the current accumulator. 11 | 12 | An `Iterable` could be an `Array`, an `Iterator`, or `async Iterator`, or a `Functor`. 13 | 14 | The predicate will be `predicate :: Any -> Any -> Boolean` 15 | 16 | ## Array Example 17 | 18 | ```javascript 19 | import { reduceWhile } from 'mojiscript' 20 | 21 | const add = x => y => x + y 22 | const array = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] 23 | const predicate = acc => x => x < 5 24 | 25 | reduceWhile (predicate) (add) (0) (array) //=> 10 26 | ``` 27 | 28 | ## Iterator Example 29 | 30 | ```javascript 31 | import { range, reduceWhile } from 'mojiscript' 32 | 33 | const add = x => y => x + y 34 | const predicate = acc => x => x < 5 35 | 36 | reduceWhile (predicate) (add) (0) (range (0) (Infinity)) //=> 10 37 | ``` 38 | 39 | ## Async Example 40 | 41 | ```javascript 42 | import { pipe, reduceWhile, sleep } from 'mojiscript' 43 | 44 | const add = x => y => x + y 45 | const asyncAdd = x => pipe ([ 46 | sleep (1000), 47 | add (x) 48 | ]) 49 | const array = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] 50 | const predicate = acc => x => x < 5 51 | 52 | reduceWhile (predicate) (asyncAdd) (0) (array) //=> Promise (10) 53 | ``` 54 | -------------------------------------------------------------------------------- /list/sort.js: -------------------------------------------------------------------------------- 1 | const signature = require('../_internal/debug/signature') 2 | 3 | // sort :: Function -> Iterable -> Array 4 | const sort = func => iterable => [ ...iterable ].sort((a, b) => func(a)(b)) 5 | 6 | module.exports = sort 7 | 8 | // Experimental debug code 9 | /* istanbul ignore next */ 10 | if (process.env.MOJI_DEBUG === 'true') { 11 | module.exports = signature('sort :: Function -> Iterable -> Array')(sort) 12 | } 13 | -------------------------------------------------------------------------------- /list/sort.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: sort 3 | menu: list 4 | --- 5 | 6 | # list/sort 7 | 8 | `sort :: Function -> Iterable -> Array` 9 | 10 | Takes a function and an `Iterable` and returns an `Array` with values sorted based on the function return value. 11 | 12 | ## Example 13 | 14 | ```javascript 15 | import { log, pipe, run, sort } from 'mojiscript' 16 | 17 | const asc = (a, b) => a < b ? -1 : 1 18 | 19 | const main = pipe ([ 20 | [ 1, 4, 3, 5, 2 ], 21 | sort (asc), 22 | log 23 | ]) 24 | 25 | run ({ main }) 26 | //=> [ 1, 2, 3, 4, 5 ] 27 | ``` 28 | 29 | ## Parameters 30 | 31 | | Name | Type | Description | 32 | | ---- | ---- | ----------- | 33 | | function | `Function` | Function to apply to each item in the `Iterable`. | 34 | | iterable | `Iterable` | `Iterable` to apply the function to. | 35 | 36 | ## Returns 37 | 38 | Returns an `Array` with the function applied to each value in the `Iterable`. 39 | -------------------------------------------------------------------------------- /list/tail.js: -------------------------------------------------------------------------------- 1 | const signature = require('../_internal/debug/signature') 2 | const is = require('../type/is') 3 | 4 | const isArray = is(Array) 5 | const isFunction = is(Function) 6 | 7 | // tail :: Array -> Array 8 | const tail = iterable => 9 | isArray(iterable) ? iterable.slice(1) 10 | : iterable && isFunction(iterable.next) ? [ ...iterable ].slice(1) 11 | : (() => { throw new Error(`type ${typeof iterable} is not supported`) })() 12 | 13 | module.exports = tail 14 | 15 | // Experimental debug code 16 | /* istanbul ignore next */ 17 | if (process.env.MOJI_DEBUG === 'true') { 18 | module.exports = signature('tail :: Array -> Array')(tail) 19 | } 20 | -------------------------------------------------------------------------------- /list/tail.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: tail 3 | menu: list 4 | --- 5 | 6 | # list/tail 7 | 8 | `tail :: Iterable -> Array` 9 | 10 | Takes an `Iterable` and the tail of the `Iterable` as an `Array`. 11 | 12 | ## Example 13 | 14 | ```javascript 15 | import { log, pipe, run, tail } from 'mojiscript' 16 | 17 | const main = pipe ([ 18 | [ 1, 2, 3 ], 19 | tail, 20 | log 21 | ]) 22 | 23 | run ({ main }) 24 | //=> [2, 3] 25 | ``` 26 | 27 | ## Parameters 28 | 29 | | Name | Type | Description | 30 | | ---- | ---- | ----------- | 31 | | iterable | `Iterable` | `Iterable` to get the tail from. | 32 | 33 | ## Returns 34 | 35 | Returns the tail of `Iterable` 36 | -------------------------------------------------------------------------------- /logic/__tests__/allPass.test.js: -------------------------------------------------------------------------------- 1 | const allPass = require('../allPass') 2 | 3 | describe('logic/allPass', () => { 4 | const isEven = num => num % 2 === 0 5 | const isLessThan10 = num => num < 10 6 | 7 | test('no tests returns true', () => { 8 | const expected = true 9 | const actual = allPass([])({}) 10 | expect(actual).toBe(expected) 11 | }) 12 | 13 | test('fail returns false', () => { 14 | const expected = false 15 | const actual = allPass([ isEven, isLessThan10 ])(7) 16 | expect(actual).toBe(expected) 17 | }) 18 | 19 | test('success returns true', () => { 20 | const expected = true 21 | const actual = allPass([ isEven, isLessThan10 ])(8) 22 | expect(actual).toBe(expected) 23 | }) 24 | }) 25 | -------------------------------------------------------------------------------- /logic/__tests__/anyPass.test.js: -------------------------------------------------------------------------------- 1 | const anyPass = require('../anyPass') 2 | 3 | describe('logic/anyPass', () => { 4 | const isEven = num => num % 2 === 0 5 | const isLessThan10 = num => num < 10 6 | 7 | test('no tests returns false', () => { 8 | const expected = false 9 | const actual = anyPass([])({}) 10 | expect(actual).toBe(expected) 11 | }) 12 | 13 | test('one pass returns true', () => { 14 | const expected = true 15 | const actual = anyPass([ isEven, isLessThan10 ])(7) 16 | expect(actual).toBe(expected) 17 | }) 18 | 19 | test('all fail returns true', () => { 20 | const expected = false 21 | const actual = anyPass([ isEven, isLessThan10 ])(99) 22 | expect(actual).toBe(expected) 23 | }) 24 | }) 25 | -------------------------------------------------------------------------------- /logic/__tests__/cond.test.js: -------------------------------------------------------------------------------- 1 | const cond = require('../cond') 2 | 3 | describe('logic/cond', () => { 4 | const c = cond([ 5 | [ null, 'null' ], 6 | [ undefined, 'undefined' ], 7 | [ 'abc', 'ABC' ], 8 | [ x => x === '123', 'ONE TWO THREE' ], 9 | [ x => x[0] === 'h', x => x.toUpperCase() ] 10 | ]) 11 | 12 | test('test with null returns value', () => { 13 | const expected = 'null' 14 | const actual = c(null) 15 | expect(actual).toBe(expected) 16 | }) 17 | 18 | test('test with undefined returns value', () => { 19 | const expected = 'null' 20 | const actual = c(null) 21 | expect(actual).toBe(expected) 22 | }) 23 | 24 | test('test value returns value', () => { 25 | const expected = 'ABC' 26 | const actual = c('abc') 27 | expect(actual).toBe(expected) 28 | }) 29 | 30 | test('test func returns value', () => { 31 | const expected = 'ONE TWO THREE' 32 | const actual = c('123') 33 | expect(actual).toBe(expected) 34 | }) 35 | 36 | test('result computes value', () => { 37 | const expected = 'HELLO' 38 | const actual = c('hello') 39 | expect(actual).toBe(expected) 40 | }) 41 | 42 | test('no match returns null', () => { 43 | const expected = null 44 | const actual = c('goodbye') 45 | expect(actual).toBe(expected) 46 | }) 47 | }) 48 | -------------------------------------------------------------------------------- /logic/__tests__/ifElse.test.js: -------------------------------------------------------------------------------- 1 | const ifElse = require('../ifElse') 2 | 3 | describe('logic/ifElse', () => { 4 | test('true returns executes true branch', () => { 5 | const expected = 888 6 | const actual = ifElse(() => true)(x => x + 1)(666)(887) 7 | expect(actual).toBe(expected) 8 | }) 9 | 10 | test('false returns false value', () => { 11 | const expected = 888 12 | const actual = ifElse(() => false)(666)(x => x + 1)(887) 13 | expect(actual).toBe(expected) 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /logic/__tests__/ifError.test.js: -------------------------------------------------------------------------------- 1 | const ifError = require('../ifError') 2 | 3 | describe('core/ifError', () => { 4 | describe('synchronous', () => { 5 | test('success calls success', () => { 6 | const expected = 888 7 | const mockSuccess = jest.fn() 8 | ifError(x => x)(() => null)(mockSuccess)(expected) 9 | const actual = mockSuccess.mock.calls[0][0] 10 | expect(actual).toBe(expected) 11 | }) 12 | 13 | test('fail calls error', () => { 14 | const expected = new Error('888') 15 | const mockError = jest.fn() 16 | ifError(x => { throw new Error(x) })(mockError)(() => null)(888) 17 | const actual = mockError.mock.calls[0][0] 18 | expect(actual).toMatchObject(expected) 19 | }) 20 | 21 | test('success returns success value', () => { 22 | const expected = 888 23 | const actual = ifError(x => x)(() => null)(x => x)(expected) 24 | expect(actual).toBe(expected) 25 | }) 26 | 27 | test('fails returns error', () => { 28 | const expected = new Error('888') 29 | const actual = ifError(x => { throw new Error(x) })(x => x)(() => null)(888) 30 | expect(actual).toMatchObject(expected) 31 | }) 32 | }) 33 | 34 | describe('asynchronous', () => { 35 | test('success calls success', async () => { 36 | const expected = 888 37 | const mockSuccess = jest.fn() 38 | await ifError(x => Promise.resolve(x))(() => null)(mockSuccess)(expected) 39 | const actual = mockSuccess.mock.calls[0][0] 40 | expect(actual).toBe(expected) 41 | }) 42 | 43 | test('fail calls error', async () => { 44 | const expected = 888 45 | const mockError = jest.fn() 46 | await ifError(x => Promise.reject(x))(mockError)(() => null)(expected) 47 | const actual = mockError.mock.calls[0][0] 48 | expect(actual).toBe(expected) 49 | }) 50 | 51 | test('success resolves onSuccess value', () => { 52 | const expected = 888 53 | const actual = ifError(x => Promise.resolve(x))(() => null)(x => x)(expected) 54 | return expect(actual).resolves.toBe(expected) 55 | }) 56 | 57 | test('fails resolves onError value', () => { 58 | const expected = { err: 888 } 59 | const actual = ifError(x => Promise.reject(x))(err => ({ err }))(() => null)(888) 60 | return expect(actual).resolves.toMatchObject(expected) 61 | }) 62 | }) 63 | }) 64 | -------------------------------------------------------------------------------- /logic/__tests__/unless.test.js: -------------------------------------------------------------------------------- 1 | const unless = require('../unless') 2 | 3 | describe('logic/unless', () => { 4 | const isOdd = num => num % 2 !== 0 5 | const isEven = num => num % 2 === 0 6 | const double = num => num * 2 7 | 8 | test('match calls function', () => { 9 | const expected = 888 10 | const actual = unless(isOdd)(double)(444) 11 | expect(actual).toBe(expected) 12 | }) 13 | 14 | test('no match returns value', () => { 15 | const expected = 888 16 | const actual = unless(isEven)(double)(expected) 17 | expect(actual).toBe(expected) 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /logic/__tests__/when.test.js: -------------------------------------------------------------------------------- 1 | const when = require('../when') 2 | 3 | describe('logic/when', () => { 4 | const isOdd = num => num % 2 !== 0 5 | const isEven = num => num % 2 === 0 6 | const double = num => num * 2 7 | 8 | test('match calls function', () => { 9 | const expected = 888 10 | const actual = when(isEven)(double)(444) 11 | expect(actual).toBe(expected) 12 | }) 13 | 14 | test('no match returns value', () => { 15 | const expected = 888 16 | const actual = when(isOdd)(double)(expected) 17 | expect(actual).toBe(expected) 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /logic/allPass.js: -------------------------------------------------------------------------------- 1 | const signature = require('../_internal/debug/signature') 2 | 3 | // allPass :: Function -> Any -> Boolean 4 | const allPass = predicates => value => { 5 | for (const predicate of predicates) { // eslint-disable-line no-restricted-syntax 6 | if (!predicate(value)) { 7 | return false 8 | } 9 | } 10 | 11 | return true 12 | } 13 | 14 | module.exports = allPass 15 | 16 | // Experimental debug code 17 | /* istanbul ignore next */ 18 | if (process.env.MOJI_DEBUG === 'true') { 19 | module.exports = signature('allPass :: Function -> Any -> Boolean')(allPass) 20 | } 21 | -------------------------------------------------------------------------------- /logic/allPass.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: allPass 3 | menu: logic 4 | --- 5 | 6 | # logic/allPass 7 | 8 | `allPass :: [Function] -> Any -> Boolean` 9 | 10 | Takes an `Array` of `predicates` and returns `true` if all `predicates` are `true`. 11 | 12 | ## Example 13 | 14 | ```javascript 15 | import { log, pipe, run, map, allPass } from 'mojiscript' 16 | 17 | const isFizz = num => num % 5 == 0 18 | const isBuzz = num => num % 3 == 0 19 | const isFizzBuzz = allPass ([ isFizz, isBuzz ]) 20 | 21 | const main = pipe ([ 22 | [ 5, 10, 15 ], 23 | map (isFizzBuzz), 24 | log 25 | ]) 26 | 27 | run ({ main }) 28 | //=> [ false, false, true ] 29 | ``` 30 | 31 | ## Parameters 32 | 33 | | Name | Type | Description | 34 | | ---- | ---- | ----------- | 35 | | predicates | `[Function]` | An `Array` of `predicates` to apply to each item in the `Iterable`. | 36 | | value | `Any` | Value to test. | 37 | 38 | ## Returns 39 | 40 | Returns `true` if all `predicates` are `true`, otherwise `false`. 41 | -------------------------------------------------------------------------------- /logic/anyPass.js: -------------------------------------------------------------------------------- 1 | const signature = require('../_internal/debug/signature') 2 | 3 | // anyPass :: Function -> Any -> Boolean 4 | const anyPass = predicates => value => { 5 | for (const predicate of predicates) { // eslint-disable-line no-restricted-syntax 6 | if (predicate(value)) { 7 | return true 8 | } 9 | } 10 | 11 | return false 12 | } 13 | 14 | module.exports = anyPass 15 | 16 | // Experimental debug code 17 | /* istanbul ignore next */ 18 | if (process.env.MOJI_DEBUG === 'true') { 19 | module.exports = signature('anyPass :: Function -> Any -> Boolean')(anyPass) 20 | } 21 | -------------------------------------------------------------------------------- /logic/anyPass.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: anyPass 3 | menu: logic 4 | --- 5 | 6 | # logic/anyPass 7 | 8 | `anyPass :: [Function] -> Any -> Boolean` 9 | 10 | Takes a list of `predicates` and returns `true` if one `predicates` is `true`. 11 | 12 | ## Example 13 | 14 | ```javascript 15 | import { log, pipe, run, map, anyPass } from 'mojiscript' 16 | 17 | const isFizz = num => num % 5 == 0 18 | const isBuzz = num => num % 3 == 0 19 | const isFizzBuzz = anyPass ([ isFizz, isBuzz ]) 20 | 21 | const main = pipe ([ 22 | [ 5, 10, 15 ], 23 | map (isFizzBuzz), 24 | log 25 | ]) 26 | 27 | run ({ main }) 28 | //=> [ true, false, true ] 29 | ``` 30 | 31 | ## Parameters 32 | 33 | | Name | Type | Description | 34 | | ---- | ---- | ----------- | 35 | | predicates | `[Function]` | An `Array` of `predicates` to apply to each item in the `Iterable`. | 36 | | value | `Any` | Value to test. | 37 | 38 | ## Returns 39 | 40 | Returns `true` if any `predicate` is `true`, otherwise `false`. 41 | -------------------------------------------------------------------------------- /logic/cond.js: -------------------------------------------------------------------------------- 1 | const signature = require('../_internal/debug/signature') 2 | const maybeExec = require('../_internal/maybeExec') 3 | const is = require('../type/is') 4 | 5 | const isFunction = is(Function) 6 | 7 | const isPass = (test, value) => (isFunction(test) ? test(value) : test === value) 8 | 9 | // cond :: Array -> Any -> Any 10 | const cond = pairs => value => { 11 | for (const pair of pairs) { // eslint-disable-line no-restricted-syntax 12 | const [ predicate, transformer ] = pair 13 | 14 | if (isPass(predicate, value)) { 15 | return maybeExec(transformer)(value) 16 | } 17 | } 18 | 19 | return null 20 | } 21 | 22 | module.exports = cond 23 | 24 | // Experimental debug code 25 | /* istanbul ignore next */ 26 | if (process.env.MOJI_DEBUG === 'true') { 27 | module.exports = signature('cond :: Function -> Any -> Any')(cond) 28 | } 29 | -------------------------------------------------------------------------------- /logic/cond.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: cond 3 | menu: logic 4 | --- 5 | 6 | # logic/cond 7 | 8 | `cond :: Array -> Any -> Any` 9 | 10 | Encapsulates `if/else/elseif` logic. 11 | 12 | ## Examples 13 | 14 | ```javascript 15 | import { logF, cond, pipe, run } from 'mojiscript' 16 | 17 | const dependencies = { 18 | logF 19 | } 20 | const state = 5 21 | 22 | const dayName = cond ([ 23 | [ 0, 'Sunday' ], 24 | [ 1, 'Monday' ], 25 | [ 2, 'Tuesday' ], 26 | [ 3, 'Wednesday' ], 27 | [ 4, 'Thursday' ], 28 | [ 5, 'Friday' ], 29 | [ 6, 'Saturday' ] 30 | ]) 31 | 32 | const main = ({ logF }) => pipe ([ 33 | dayName, 34 | logF(day => `Today is ${day}.`) 35 | ]) 36 | 37 | run({ dependencies, state, main }) 38 | //=> 'Friday' 39 | ``` 40 | 41 | ```javascript 42 | import { log, cond, pipe, run, $ } from 'mojiscript' 43 | 44 | const dependencies = { 45 | log 46 | } 47 | const state = 100 48 | 49 | // getTempInfo :: Number -> String 50 | const getTempInfo = cond ([ 51 | [ 0, 'water freezes at 0°C' ], 52 | [ 100, 'water boils at 100°C' ], 53 | [ () => true, $`nothing special happens at ${0}°C` ] 54 | ]) 55 | 56 | const main = ({ log }) => pipe ([ 57 | getTempInfo, 58 | log 59 | ]) 60 | 61 | run({ dependencies, state, main }) 62 | //=> 'water boils at 100°C' 63 | ``` 64 | 65 | ## Parameters 66 | 67 | | Name | Type | Description | 68 | | ---- | ---- | ----------- | 69 | | pairs | `[predicate, transformer]` | An `Array` of pairs of `predicate`s and `transformer`s. | 70 | | value | `Any` | Value to test. | 71 | 72 | ## Returns 73 | 74 | Returns the transformed value of the matching `predicate`. 75 | -------------------------------------------------------------------------------- /logic/ifElse.js: -------------------------------------------------------------------------------- 1 | const signature = require('../_internal/debug/signature') 2 | 3 | // ifElse :: Function -> Function -> Function -> Any -> Any 4 | const ifElse = condition => onTrue => onFalse => value => 5 | (condition(value) ? onTrue : onFalse)(value) 6 | 7 | module.exports = ifElse 8 | 9 | // Experimental debug code 10 | /* istanbul ignore next */ 11 | if (process.env.MOJI_DEBUG === 'true') { 12 | module.exports = signature('ifElse :: Function -> Function -> Function -> Any')(ifElse) 13 | } 14 | -------------------------------------------------------------------------------- /logic/ifElse.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: ifElse 3 | menu: logic 4 | --- 5 | 6 | # logic/ifElse 7 | 8 | `ifElse :: Function -> Function -> Function -> Any -> Any` 9 | 10 | Encapsulates `if/else` logic. 11 | 12 | ## Example 13 | 14 | ```javascript 15 | import { log, ifElse, pipe, run, $ } from 'mojiscript' 16 | 17 | const dependencies = { 18 | log 19 | } 20 | const state = 7 21 | 22 | const isEven = x => x % 2 == 0 23 | const yes = $`Yes, ${0} is even.` 24 | const no = $`NO, ${0} is not even.` 25 | 26 | const main = ({ log }) => pipe ([ 27 | ifElse (isEven) (yes) (no), 28 | log 29 | ]) 30 | 31 | run({ dependencies, state, main }) 32 | //=> 'NO, 7 is not even.' 33 | ``` 34 | 35 | ## Parameters 36 | 37 | | Name | Type | Description | 38 | | ---- | ---- | ----------- | 39 | | condition | `Function` | A predicate function. | 40 | | onTrue | `Function` | Invoked when the predicate evaluates to a truthy value. | 41 | | onFalse | `Function` | Invoked when the predicate evaluates to a falsy value. | 42 | | value | `Any` | Value to be passed to condition and either `onTrue` or `onFalse` function. | 43 | 44 | ## Returns 45 | 46 | Returns the result of either `onTrue` or `onFalse` depending on the result of the `condition`. 47 | -------------------------------------------------------------------------------- /logic/ifError.js: -------------------------------------------------------------------------------- 1 | const signature = require('../_internal/debug/signature') 2 | const isThenable = require('../_internal/isThenable') 3 | 4 | // ifError :: Function -> Function -> Function -> Any -> Any 5 | const ifError = func => onError => onSuccess => value => { 6 | try { 7 | const result = func(value) 8 | return isThenable(result) 9 | ? result.then(onSuccess).catch(onError) 10 | : onSuccess(result) 11 | } catch (err) { 12 | return onError(err) 13 | } 14 | } 15 | 16 | module.exports = ifError 17 | 18 | // Experimental debug code 19 | /* istanbul ignore next */ 20 | if (process.env.MOJI_DEBUG === 'true') { 21 | module.exports = signature('ifError :: Function -> Function -> Function -> Any -> Any')(ifError) 22 | } 23 | -------------------------------------------------------------------------------- /logic/ifError.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: ifError 3 | menu: logic 4 | --- 5 | 6 | # logic/ifError 7 | 8 | `ifError :: Function -> Function -> Function -> Any -> Any` 9 | 10 | Error handler executes `onError` if an error occurs, otherwise `onSuccess` is executed. 11 | 12 | ## Example 13 | 14 | ```javascript 15 | import { log, pipe, run, ifError, $ } from 'mojiscript' 16 | 17 | const state = 1 18 | 19 | const toUpperCase = value => value.toUpperCase () 20 | 21 | const main = pipe ([ 22 | ifError (toUpperCase) ($`OOPS: ${0}`) ($`Value: ${0}`), 23 | log 24 | ]) 25 | 26 | run ({ state, main }) 27 | // OOPS: TypeError: value.toUppercase is not a function 28 | ``` 29 | -------------------------------------------------------------------------------- /logic/unless.js: -------------------------------------------------------------------------------- 1 | const signature = require('../_internal/debug/signature') 2 | const ifElse = require('./ifElse') 3 | const I = require('../combinators/I') 4 | 5 | // unless :: Function -> Function -> Any -> Any 6 | const unless = condition => ifElse(condition)(I) 7 | 8 | module.exports = unless 9 | 10 | // Experimental debug code 11 | /* istanbul ignore next */ 12 | if (process.env.MOJI_DEBUG === 'true') { 13 | module.exports = signature('unless :: Function -> Function -> Any -> Any')(unless) 14 | } 15 | -------------------------------------------------------------------------------- /logic/unless.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: unless 3 | menu: logic 4 | --- 5 | 6 | # logic/unless 7 | 8 | `unless :: Function -> Function -> Any -> Any` 9 | 10 | Executes `onFalse` when the `condition` returns a falsy value. Otherwise `value` is returned. 11 | 12 | ## Example 13 | 14 | ```javascript 15 | import { log, pipe, run, unless, is } from 'mojiscript' 16 | 17 | const state = 1 18 | 19 | const isString = is (String) 20 | const toString = x => x.toString () 21 | 22 | const main = pipe ([ 23 | unless (isString) (toString), 24 | log 25 | ]) 26 | 27 | run ({ state, main }) 28 | //=> '1' 29 | ``` 30 | 31 | ## Parameters 32 | 33 | | Name | Type | Description | 34 | | ---- | ---- | ----------- | 35 | | condition | `Function` | A predicate function. | 36 | | onFalse | `Function` | Invoked when the predicate evaluates to a falsy value. | 37 | | value | `Any` | Value to be passed to condition and `onFalse` if condition is falsy. | 38 | 39 | ## Returns 40 | 41 | Returns the result of `onFalse` depending on the result of the `condition` or the `value`. 42 | -------------------------------------------------------------------------------- /logic/when.js: -------------------------------------------------------------------------------- 1 | const signature = require('../_internal/debug/signature') 2 | const ifElse = require('./ifElse') 3 | const I = require('../combinators/I') 4 | 5 | // when :: Function -> Function -> Any -> Any 6 | const when = condition => func => ifElse(condition)(func)(I) 7 | 8 | module.exports = when 9 | 10 | // Experimental debug code 11 | /* istanbul ignore next */ 12 | if (process.env.MOJI_DEBUG === 'true') { 13 | module.exports = signature('when :: Function -> Function -> Any -> Any')(when) 14 | } 15 | -------------------------------------------------------------------------------- /logic/when.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: when 3 | menu: logic 4 | --- 5 | 6 | # logic/when 7 | 8 | `when :: Function -> Function -> Any -> Any` 9 | 10 | Executes `onTrue` when the `condition` returns a truthy value. Otherwise `value` is returned. 11 | 12 | ## Example 13 | 14 | ```javascript 15 | import { log, pipe, run, when, is } from 'mojiscript' 16 | 17 | const state = 'mojiscript' 18 | 19 | const isString = is (String) 20 | const toUpperCase = x => x.toUpperCase () 21 | 22 | const main = pipe ([ 23 | when (isString) (toUpperCase), 24 | log 25 | ]) 26 | 27 | run ({ state, main }) 28 | // => "MOJISCRIPT" 29 | ``` 30 | 31 | ## Parameters 32 | 33 | | Name | Type | Description | 34 | | ---- | ---- | ----------- | 35 | | condition | `Function` | A predicate function. | 36 | | onTrue | `Function` | Invoked when the predicate evaluates to a truthy value. | 37 | | value | `Any` | Value to be passed to condition and `onTrue` if condition is truthy. | 38 | 39 | ## Returns 40 | 41 | Returns the result of `onTrue` depending on the result of the `condition` or the `value`. 42 | -------------------------------------------------------------------------------- /media/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelnet/MojiScript/1bc7f43fdc105ed252aa3071e22a84676d84fbe1/media/.DS_Store -------------------------------------------------------------------------------- /media/MS_logo.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelnet/MojiScript/1bc7f43fdc105ed252aa3071e22a84676d84fbe1/media/MS_logo.psd -------------------------------------------------------------------------------- /media/MS_logo_1200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelnet/MojiScript/1bc7f43fdc105ed252aa3071e22a84676d84fbe1/media/MS_logo_1200.png -------------------------------------------------------------------------------- /media/MS_logo_200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelnet/MojiScript/1bc7f43fdc105ed252aa3071e22a84676d84fbe1/media/MS_logo_200.png -------------------------------------------------------------------------------- /media/MS_logo_400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelnet/MojiScript/1bc7f43fdc105ed252aa3071e22a84676d84fbe1/media/MS_logo_400.png -------------------------------------------------------------------------------- /media/MS_logo_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelnet/MojiScript/1bc7f43fdc105ed252aa3071e22a84676d84fbe1/media/MS_logo_64.png -------------------------------------------------------------------------------- /media/MS_logo_800.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelnet/MojiScript/1bc7f43fdc105ed252aa3071e22a84676d84fbe1/media/MS_logo_800.png -------------------------------------------------------------------------------- /net/__tests__/axios.tests.js: -------------------------------------------------------------------------------- 1 | const actualAxios = require('axios') 2 | const axios = require('../axios') 3 | 4 | describe('net/axios', () => { 5 | beforeEach(() => { 6 | jest.spyOn(actualAxios, 'get').mockImplementation(() => {}) 7 | jest.spyOn(actualAxios, 'delete').mockImplementation(() => {}) 8 | jest.spyOn(actualAxios, 'head').mockImplementation(() => {}) 9 | jest.spyOn(actualAxios, 'options').mockImplementation(() => {}) 10 | jest.spyOn(actualAxios, 'post').mockImplementation(() => {}) 11 | jest.spyOn(actualAxios, 'put').mockImplementation(() => {}) 12 | jest.spyOn(actualAxios, 'patch').mockImplementation(() => {}) 13 | }) 14 | 15 | afterEach(() => { 16 | jest.clearAllMocks() 17 | }) 18 | 19 | test('get', () => { 20 | const expected = [ [ 1, 2 ] ] 21 | axios.get(1)(2) 22 | const actual = actualAxios.get.mock.calls 23 | expect(actual).toMatchObject(expected) 24 | }) 25 | 26 | test('delete', () => { 27 | const expected = [ [ 1, 2 ] ] 28 | axios.delete(1)(2) 29 | const actual = actualAxios.delete.mock.calls 30 | expect(actual).toMatchObject(expected) 31 | }) 32 | 33 | test('head', () => { 34 | const expected = [ [ 1, 2 ] ] 35 | axios.head(1)(2) 36 | const actual = actualAxios.head.mock.calls 37 | expect(actual).toMatchObject(expected) 38 | }) 39 | 40 | test('options', () => { 41 | const expected = [ [ 1, 2 ] ] 42 | axios.options(1)(2) 43 | const actual = actualAxios.options.mock.calls 44 | expect(actual).toMatchObject(expected) 45 | }) 46 | 47 | test('post', () => { 48 | const expected = [ [ 1, 2, 3 ] ] 49 | axios.post(1)(2)(3) 50 | const actual = actualAxios.post.mock.calls 51 | expect(actual).toMatchObject(expected) 52 | }) 53 | 54 | test('put', () => { 55 | const expected = [ [ 1, 2, 3 ] ] 56 | axios.put(1)(2)(3) 57 | const actual = actualAxios.put.mock.calls 58 | expect(actual).toMatchObject(expected) 59 | }) 60 | 61 | test('patch', () => { 62 | const expected = [ [ 1, 2, 3 ] ] 63 | axios.patch(1)(2)(3) 64 | const actual = actualAxios.patch.mock.calls 65 | expect(actual).toMatchObject(expected) 66 | }) 67 | }) 68 | -------------------------------------------------------------------------------- /net/axios.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios') 2 | const signature = require('../_internal/debug/signature') 3 | 4 | module.exports.get = url => options => axios.get(url, options) 5 | module.exports.delete = url => options => axios.delete(url, options) 6 | module.exports.head = url => options => axios.head(url, options) 7 | module.exports.options = url => options => axios.options(url, options) 8 | module.exports.post = url => data => options => axios.post(url, data, options) 9 | module.exports.put = url => data => options => axios.put(url, data, options) 10 | module.exports.patch = url => data => options => axios.patch(url, data, options) 11 | 12 | // Experimental debug code 13 | /* istanbul ignore next */ 14 | if (process.env.MOJI_DEBUG === 'true') { 15 | module.exports.get = signature('get :: String -> Object -> Any')(module.exports.get) 16 | module.exports.delete = signature('delete :: String -> Object -> Any')(module.exports.delete) 17 | module.exports.head = signature('head :: String -> Object -> Any')(module.exports.head) 18 | module.exports.options = signature('head :: String -> Object -> Any')(module.exports.options) 19 | module.exports.post = signature('post :: String -> Object -> Object -> Any')(module.exports.post) 20 | module.exports.put = signature('put :: String -> Object -> Object -> Any')(module.exports.put) 21 | module.exports.patch = signature('patch :: String -> Object -> Object -> Any')(module.exports.patch) 22 | } 23 | -------------------------------------------------------------------------------- /object/__tests__/clone.test.js: -------------------------------------------------------------------------------- 1 | const clone = require('../clone') 2 | 3 | describe('clone/clone', () => { 4 | test('clones an array', () => { 5 | const original = [ 1, 2, 3 ] 6 | const cloned = clone(original) 7 | expect(cloned).toEqual(original) 8 | expect(cloned).not.toBe(original) 9 | }) 10 | 11 | test('clones an object', () => { 12 | const original = { foo: 'bar' } 13 | const cloned = clone(original) 14 | expect(cloned).toEqual(original) 15 | expect(cloned).not.toBe(original) 16 | }) 17 | 18 | test('clones a function', () => { 19 | const original = x => x + 1 20 | const cloned = clone(original) 21 | expect(cloned).not.toBe(original) 22 | expect(cloned(5)).toBe(original(5)) 23 | }) 24 | 25 | test('clones a set', () => { 26 | const original = new Set([ 1, 2, 'a' ]) 27 | const cloned = clone(original) 28 | expect(cloned).toEqual(original) 29 | expect(cloned).not.toBe(original) 30 | }) 31 | 32 | test('clones a map', () => { 33 | const original = new Map([ [ 'a', 1 ], [ 'b', 2 ] ]) 34 | const cloned = clone(original) 35 | expect(cloned).toEqual(original) 36 | expect(cloned).not.toBe(original) 37 | }) 38 | 39 | test('returns a string', () => { 40 | const original = 'hello' 41 | const cloned = clone(original) 42 | expect(cloned).toBe(original) 43 | }) 44 | 45 | test('returns a number', () => { 46 | const original = 1 47 | const cloned = clone(original) 48 | expect(cloned).toBe(original) 49 | }) 50 | 51 | test('returns null when given null', () => { 52 | expect(clone(null)).toBeNull() 53 | }) 54 | 55 | test('returns undefined when given undefined', () => { 56 | expect(clone(undefined)).toBeUndefined() 57 | }) 58 | 59 | test('throws an error for any other type', () => { 60 | expect(() => clone(true)).toThrowError() 61 | }) 62 | }) 63 | -------------------------------------------------------------------------------- /object/__tests__/pathOr.test.js: -------------------------------------------------------------------------------- 1 | const pathOr = require('../pathOr') 2 | 3 | describe('object/pathOr', () => { 4 | test('empty', () => { 5 | const fallbackValue = 'fallback' 6 | expect(pathOr(fallbackValue)([])({ a: 1 })).toStrictEqual({ a: 1 }) 7 | expect(pathOr(fallbackValue)([ 'b' ])({})).toBe(fallbackValue) 8 | }) 9 | 10 | test('fallbacks', () => { 11 | const fallbackValue = 'fallback' 12 | expect(pathOr(fallbackValue)([ 'a' ])({ b: 1 })).toBe(fallbackValue) 13 | expect(pathOr(fallbackValue)([ 'a', 'b' ])({ a: 1 })).toBe(fallbackValue) 14 | expect(pathOr(fallbackValue)([ 'a', 'c' ])({ a: {} })).toBe(fallbackValue) 15 | }) 16 | 17 | test('falsy values', () => { 18 | const fallbackValue = 'fallback' 19 | expect(pathOr(fallbackValue)([ 'a' ])({ a: undefined })).toBe(undefined) 20 | expect(pathOr(fallbackValue)([ 'a' ])({ a: false })).toBe(false) 21 | expect(pathOr(fallbackValue)([ 'a' ])({ a: null })).toBe(null) 22 | expect(pathOr(fallbackValue)([ 'a' ])({ a: 0 })).toBe(0) 23 | }) 24 | 25 | test('truthy values', () => { 26 | const fallbackValue = 'fallback' 27 | expect(pathOr(fallbackValue)([ 'a' ])({ a: 1 })).toBe(1) 28 | expect(pathOr(fallbackValue)([ 'a' ])({ a: { b: 1 } })).toStrictEqual({ b: 1 }) 29 | expect(pathOr(fallbackValue)([ 'a' ])({ a: [ 1, 2, 3 ] })).toStrictEqual([ 1, 2, 3 ]) 30 | }) 31 | 32 | test('nested values', () => { 33 | const fallbackValue = 'fallback' 34 | expect(pathOr(fallbackValue)([ 'a', 'b' ])({ a: { b: 1 } })).toBe(1) 35 | expect(pathOr(fallbackValue)([ 'a', 'b' ])({ a: { b: undefined } })).toBe(undefined) 36 | expect(pathOr(fallbackValue)([ 'a', 'b' ])({ a: { b: {} } })).toStrictEqual({}) 37 | expect(pathOr(fallbackValue)([ 'a', 'b', 'c' ])({ a: { b: { c: [] } } })).toStrictEqual([]) 38 | }) 39 | }) 40 | -------------------------------------------------------------------------------- /object/__tests__/propOr.test.js: -------------------------------------------------------------------------------- 1 | const propOr = require('../propOr') 2 | 3 | describe('object/propOr', () => { 4 | test('fallbacks', () => { 5 | const fallbackValue = 'fallback' 6 | expect(propOr(fallbackValue)('')({})).toBe(fallbackValue) 7 | expect(propOr(fallbackValue)('')({ a: 1 })).toBe(fallbackValue) 8 | expect(propOr(fallbackValue)('a')({})).toBe(fallbackValue) 9 | expect(propOr(fallbackValue)('a')({ b: 1 })).toBe(fallbackValue) 10 | }) 11 | test('falsy values', () => { 12 | const fallbackValue = 'fallback' 13 | expect(propOr(fallbackValue)('a')({ a: undefined })).toBe(undefined) 14 | expect(propOr(fallbackValue)('a')({ a: false })).toBe(false) 15 | expect(propOr(fallbackValue)('a')({ a: null })).toBe(null) 16 | expect(propOr(fallbackValue)('a')({ a: 0 })).toBe(0) 17 | }) 18 | test('truthy values', () => { 19 | const fallbackValue = 'fallback' 20 | expect(propOr(fallbackValue)('a')({ a: 1 })).toBe(1) 21 | expect(propOr(fallbackValue)('a')({ a: { b: 1 } })).toStrictEqual({ b: 1 }) 22 | expect(propOr(fallbackValue)('a')({ a: [ 1, 2, 3 ] })).toStrictEqual([ 1, 2, 3 ]) 23 | }) 24 | }) 25 | -------------------------------------------------------------------------------- /object/clone.js: -------------------------------------------------------------------------------- 1 | const signature = require('../_internal/debug/signature') 2 | const is = require('../type/is') 3 | 4 | // clone :: Any -> Any 5 | const clone = x => { 6 | if (is(Array)(x)) return [ ...x ] 7 | if (is(Function)(x)) return x.bind({}) 8 | if (is(Set)(x)) return new Set(x) 9 | if (is(Map)(x)) return new Map(x) 10 | if (is(Object)(x)) return { ...x } 11 | if (is(String)(x)) return x 12 | if (is(Number)(x)) return x 13 | if (x === null) return null 14 | if (x === undefined) return undefined 15 | throw Error(`A value of type ${typeof x} cannot be cloned`) 16 | } 17 | 18 | module.exports = clone 19 | 20 | // Experimental debug code 21 | /* istanbul ignore next */ 22 | if (process.env.MOJI_DEBUG === 'true') { 23 | module.exports = signature('clone :: Any -> Any')(clone) 24 | } 25 | -------------------------------------------------------------------------------- /object/pathOr.js: -------------------------------------------------------------------------------- 1 | const signature = require('../_internal/debug/signature') 2 | 3 | // pathOr :: Any -> [String] -> Object -> Any 4 | const pathOr = fallback => path => o => { 5 | for (const key of path) { // eslint-disable-line no-restricted-syntax 6 | if (!o || !Object.prototype.hasOwnProperty.call(o, key)) { 7 | return fallback 8 | } 9 | o = o[key] // eslint-disable-line no-param-reassign 10 | } 11 | return o 12 | } 13 | 14 | module.exports = pathOr 15 | 16 | // Experimental debug code 17 | /* istanbul ignore next */ 18 | if (process.env.MOJI_DEBUG === 'true') { 19 | module.exports = signature('pathOr :: Any -> [String] -> Object -> Any')(pathOr) 20 | } 21 | -------------------------------------------------------------------------------- /object/propOr.js: -------------------------------------------------------------------------------- 1 | const signature = require('../_internal/debug/signature') 2 | const pathOr = require('./pathOr') 3 | 4 | // propOr :: Any -> String -> Object -> Any 5 | const propOr = fallback => prop => o => pathOr(fallback)([ prop ])(o) 6 | 7 | module.exports = propOr 8 | 9 | // Experimental debug code 10 | /* istanbul ignore next */ 11 | if (process.env.MOJI_DEBUG === 'true') { 12 | module.exports = signature('propOr :: Any -> String -> Object -> Any')(propOr) 13 | } 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mojiscript", 3 | "version": "0.13.13", 4 | "description": "MojiScript is an Async First, opinionated, and functional language designed to have 100% compatibility with JavaScript engines.", 5 | "author": "Joel Thoms", 6 | "license": "MIT", 7 | "homepage": "https://mojiscript.js.org", 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/joelnet/MojiScript.git" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/joelnet/MojiScript/issues" 14 | }, 15 | "main": "index.js", 16 | "scripts": { 17 | "build": "moji build .", 18 | "test": "jest", 19 | "test:coverage": "jest --colors --coverage", 20 | "test:coveralls": "npm run test:coverage -- --coverageReporters=text-lcov | coveralls", 21 | "patch-readme": "node --experimental-modules --no-warnings bin/patch-readme/index.mjs", 22 | "watch": "moji watch", 23 | "contributors:add": "all-contributors add", 24 | "contributors:check": "all-contributors check", 25 | "contributors:generate": "all-contributors generate", 26 | "docz:clean": "rm -rf .docz/ static/ assets.json index.html", 27 | "docz:dev": "docz dev", 28 | "docz:build": "npm run docz:clean && docz build && mv .docz/dist/* ." 29 | }, 30 | "dependencies": {}, 31 | "devDependencies": { 32 | "@types/jest": "^26.0.4", 33 | "all-contributors-cli": "^6.16.1", 34 | "axios": "^0.21.1", 35 | "coveralls": "^3.1.0", 36 | "docz": "^2.3.1", 37 | "docz-theme-default": "^1.2.0", 38 | "eslint": "^7.4.0", 39 | "eslint-config-airbnb-base": "^14.2.0", 40 | "eslint-config-mojiscript": "1.2.0", 41 | "eslint-plugin-better": "0.1.5", 42 | "eslint-plugin-fp": "2.3.0", 43 | "eslint-plugin-import": "^2.22.0", 44 | "eslint-plugin-prefer-arrow": "^1.2.1", 45 | "husky": "^4.2.5", 46 | "jest": "^26.1.0", 47 | "mojiscript-cli": "0.0.3", 48 | "sanctuary": "^3.0.0" 49 | }, 50 | "peerDependencies": { 51 | "axios": "^0.19.0" 52 | }, 53 | "jest": { 54 | "collectCoverageFrom": [ 55 | "**/*.{js,jsx,mjs}", 56 | "!coverage/**", 57 | "!examples/**", 58 | "!_internal/debug/**", 59 | "!.docz/**", 60 | "!static/**", 61 | "!doczrc.js", 62 | "!index.js" 63 | ], 64 | "coverageThreshold": { 65 | "global": { 66 | "statements": 100, 67 | "branches": 100, 68 | "functions": 100, 69 | "lines": 100 70 | } 71 | } 72 | }, 73 | "husky": { 74 | "hooks": { 75 | "pre-commit": "npm run build && npm run test:coverage", 76 | "pre-push": "npm run build && npm run test:coverage" 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /string/__tests__/append.test.js: -------------------------------------------------------------------------------- 1 | const append = require('../append') 2 | 3 | describe('string/append', () => { 4 | test('appends strings', () => { 5 | const expected = 'AB' 6 | const actual = append('B')('A') 7 | expect(actual).toBe(expected) 8 | }) 9 | 10 | test('appends null as empty string', () => { 11 | const expected = 'A' 12 | const actual = append('')('A') 13 | expect(actual).toBe(expected) 14 | }) 15 | 16 | test('appends to null as empty string', () => { 17 | const expected = 'B' 18 | const actual = append('B')('') 19 | expect(actual).toBe(expected) 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /string/__tests__/prepend.test.js: -------------------------------------------------------------------------------- 1 | const prepend = require('../prepend') 2 | 3 | describe('string/prepend', () => { 4 | test('prepend strings', () => { 5 | const expected = 'AB' 6 | const actual = prepend('A')('B') 7 | expect(actual).toBe(expected) 8 | }) 9 | 10 | test('prepend null as empty string', () => { 11 | const expected = 'B' 12 | const actual = prepend('')('B') 13 | expect(actual).toBe(expected) 14 | }) 15 | 16 | test('prepend to null as empty string', () => { 17 | const expected = 'A' 18 | const actual = prepend('A')('') 19 | expect(actual).toBe(expected) 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /string/__tests__/replace.test.js: -------------------------------------------------------------------------------- 1 | const replace = require('../replace') 2 | 3 | describe('string/replace', () => { 4 | test('replaces string', () => { 5 | const expected = 'A_C' 6 | const actual = replace('B')('_')('ABC') 7 | expect(actual).toBe(expected) 8 | }) 9 | 10 | test('replaces multiple', () => { 11 | const expected = 'A_C A_C A_C' 12 | const actual = replace('B')('_')('ABC ABC ABC') 13 | expect(actual).toBe(expected) 14 | }) 15 | 16 | test('replaces special characters', () => { 17 | const expected = 'A*C A*C A*C' 18 | const actual = replace('B')('*')('ABC ABC ABC') 19 | expect(actual).toBe(expected) 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /string/__tests__/template.test.js: -------------------------------------------------------------------------------- 1 | const $ = require('../template') 2 | 3 | describe('string/template', () => { 4 | const list = [ 5 | { foo: 'World' }, 6 | { foo: 'Sunshine' } 7 | ] 8 | 9 | test('no args template', () => { 10 | const expected = '!' 11 | const func = $`${0}!` 12 | const actual = func() 13 | expect(actual).toBe(expected) 14 | }) 15 | 16 | test('simple template', () => { 17 | const expected = 'YAY!' 18 | const func = $`${0}${1}${0}!` 19 | const actual = func('Y', 'A') 20 | expect(actual).toBe(expected) 21 | }) 22 | 23 | test('multiple template', () => { 24 | const expected = 'Hello World!' 25 | const func = $`${1} ${'foo'}!` 26 | const actual = func({ foo: 'World' }, 'Hello') 27 | expect(actual).toBe(expected) 28 | }) 29 | 30 | test('map template', () => { 31 | const expected = [ 32 | 'Hello World @ index 0!', 33 | 'Hello Sunshine @ index 1!' 34 | ] 35 | const tem = $`Hello ${'foo'} @ index ${1}!` 36 | const actual = list.map(tem) 37 | expect(actual).toMatchObject(expected) 38 | }) 39 | }) 40 | -------------------------------------------------------------------------------- /string/append.js: -------------------------------------------------------------------------------- 1 | const signature = require('../_internal/debug/signature') 2 | 3 | // append :: String -> String -> String 4 | const append = post => pre => `${pre}${post}` 5 | 6 | module.exports = append 7 | 8 | // Experimental debug code 9 | /* istanbul ignore next */ 10 | if (process.env.MOJI_DEBUG === 'true') { 11 | module.exports = signature('append :: String -> String -> String')(append) 12 | } 13 | -------------------------------------------------------------------------------- /string/append.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: append 3 | menu: string 4 | --- 5 | 6 | # string/append 7 | 8 | `append :: String -> String -> String` 9 | 10 | Appends a `String` to another `String` 11 | 12 | ## Example 13 | 14 | ```javascript 15 | import { log, pipe, run, append } from 'mojiscript'; 16 | 17 | const state = 'Moji' 18 | 19 | const main = pipe ([ 20 | append ('Script'), 21 | log 22 | ]) 23 | 24 | run ({ state, main }) 25 | // => "MojiScript" 26 | ``` 27 | 28 | ## Parameters 29 | 30 | | Name | Type | Description | 31 | | ---- | ---- | ----------- | 32 | | post | `String` | `String` to append to other `String` | 33 | | pre | `String` | `String` to be appended to. | 34 | 35 | ## Returns 36 | 37 | Appended `String`. 38 | -------------------------------------------------------------------------------- /string/prepend.js: -------------------------------------------------------------------------------- 1 | const signature = require('../_internal/debug/signature') 2 | 3 | // prepend :: String -> String -> String 4 | const prepend = pre => post => `${pre}${post}` 5 | 6 | module.exports = prepend 7 | 8 | // Experimental debug code 9 | /* istanbul ignore next */ 10 | if (process.env.MOJI_DEBUG === 'true') { 11 | module.exports = signature('prepend :: String -> String -> String')(prepend) 12 | } 13 | -------------------------------------------------------------------------------- /string/prepend.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: prepend 3 | menu: string 4 | --- 5 | 6 | # string/prepend 7 | 8 | `prepend :: String -> String -> String` 9 | 10 | Prepends a `String` to another `String` 11 | 12 | ## Example 13 | 14 | ```javascript 15 | import { log, pipe, run, prepend } from 'mojiscript'; 16 | 17 | const state = 'Script' 18 | 19 | const main = pipe ([ 20 | prepend ('Moji'), 21 | log 22 | ]) 23 | 24 | run ({ state, main }) 25 | // => "MojiScript" 26 | ``` 27 | 28 | ## Parameters 29 | 30 | | Name | Type | Description | 31 | | ---- | ---- | ----------- | 32 | | pre | `String` | `String` to be prepend to. | 33 | | post | `String` | `String` to prepend to other `String` | 34 | 35 | ## Returns 36 | 37 | Prepended `String`. 38 | -------------------------------------------------------------------------------- /string/replace.js: -------------------------------------------------------------------------------- 1 | const signature = require('../_internal/debug/signature') 2 | const escapeRegExp = require('../_internal/escapeRegExp') 3 | 4 | // replace :: String -> String -> String -> String 5 | const replace = pattern => replacement => string => 6 | string.replace(new RegExp(escapeRegExp(pattern), 'g'), replacement) 7 | 8 | module.exports = replace 9 | 10 | // Experimental debug code 11 | /* istanbul ignore next */ 12 | if (process.env.MOJI_DEBUG === 'true') { 13 | module.exports = signature('replace :: String -> String -> String -> String')(replace) 14 | } 15 | -------------------------------------------------------------------------------- /string/replace.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: replace 3 | menu: string 4 | --- 5 | 6 | # string/replace 7 | 8 | `replace :: String -> String -> String -> String` 9 | 10 | Replaces all instances of `pattern` with a `replacement` in a `string`. 11 | 12 | ## Example 13 | 14 | ```javascript 15 | import { log, pipe, run, replace } from 'mojiscript' 16 | 17 | const state = 'JavaScript JavaScript JavaScript!' 18 | 19 | const main = pipe ([ 20 | replace ('Java') ('Moji'), 21 | log 22 | ]) 23 | 24 | run ({ state, main }) 25 | // => "MojiScript MojiScript MojiScript!" 26 | ``` 27 | 28 | ## Parameters 29 | 30 | | Name | Type | Description | 31 | | ---- | ---- | ----------- | 32 | | pattern | `String` | Pattern to search for. | 33 | | replacement | `String` | Replacement `String`. | 34 | | string | `String` | `String` to to perform replacement. | 35 | 36 | ## Returns 37 | 38 | Replaced `String`. 39 | -------------------------------------------------------------------------------- /string/template.js: -------------------------------------------------------------------------------- 1 | /* 2 | * code adapted from: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals 3 | */ 4 | 5 | const template = (strings, ...keys) => (...values) => { 6 | const dict = values[0] || {} 7 | const result = [ strings[0] ] 8 | keys.forEach((key, i) => { 9 | const value = Number.isInteger(key) ? values[key] : dict[key] 10 | result.push(value, strings[i + 1]) 11 | }) 12 | return result.join('') 13 | } 14 | 15 | module.exports = template 16 | -------------------------------------------------------------------------------- /string/template.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: template 3 | menu: string 4 | --- 5 | 6 | # string/template 7 | 8 | `template :: Template -> Function -> String` 9 | 10 | Creates a template function that returns a `String`. 11 | 12 | ## Example 13 | 14 | ```javascript 15 | import { log, pipe, run, replace, $ } from 'mojiscript' 16 | 17 | const state = { first: 'Luke', last: 'Skywalker' } 18 | 19 | const nameTemplate = $`${'first'} ${'last'}` 20 | 21 | const main = pipe ([ 22 | nameTemplate, 23 | log 24 | ]) 25 | 26 | run ({ state, main }) 27 | // => "Luke Skywalker" 28 | ``` 29 | 30 | ## Returns 31 | 32 | Formatted string. 33 | -------------------------------------------------------------------------------- /styleguide.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: Style Guide 3 | order: 90 4 | --- 5 | 6 | # Style Guide 7 | 8 | All values must be declared with `const`. 9 | 10 | ```javascript 11 | // BAD 12 | let value = {} 13 | 14 | // GOOD 15 | const value = {} 16 | ``` 17 | 18 | Variables should be named in lower camel case. 19 | 20 | ```javascript 21 | // BAD 22 | const AddNumbers = x => y => x + y 23 | 24 | // GOOD 25 | const addNumbers = x => y => x + y 26 | ``` 27 | 28 | Expressions or Pipes and their arguments should be separated with a space. Arguments should be surrounded with parentheses. Further discussed at [#438](https://github.com/sanctuary-js/sanctuary/issues/438). 29 | 30 | ```javascript 31 | // BAD 32 | add(1)(2) 33 | 34 | // GOOD 35 | add (1) (2) 36 | ``` 37 | 38 | Prefer String Templates 39 | 40 | ```javascript 41 | // BAD 42 | const func = x => `Value: ${x}` 43 | 44 | // GOOD 45 | const func = $`Value: ${0}` 46 | 47 | // BAD 48 | const func = ({ prop }) => `Prop: ${prop}` 49 | 50 | // GOOD 51 | const func = $`Prop: ${'prop'}` 52 | ``` 53 | 54 | Following Atomic Design principles, code should be broken down into Atoms. This maximizes reusability, testability, composability, and readability. 55 | 56 | ```javascript 57 | // BAD 58 | const getOrdersText = ifElse (({ length }) => length > 0) ($`${0} orders`) ($`No Orders`) 59 | 60 | // GOOD 61 | const hasOrders = ({ length }) => length > 0 62 | const getOrdersText = ifElse (hasOrders) ($`${0} orders`) ($`No Orders`) 63 | 64 | // GOOD 65 | const hasOrders = ({ length }) => length > 0 66 | const ifHasOrders = ifElse (hasOrders) 67 | const getOrdersText = ifHasOrders ($`${0} orders`) ($`No Orders`) 68 | ``` 69 | 70 | `ifElse` and the condition should be on the same line. Longer statements can be broken out into multiple lines. If it is long, consider breaking it down further. 71 | 72 | ```javascript 73 | // BAD 74 | ifElse 75 | (lessThan0) 76 | (Math.abs) 77 | (Math.sqrt) 78 | 79 | // GOOD 80 | ifElse (lessThan0) (Math.abs) (Math.sqrt) 81 | 82 | // GOOD 83 | ifElse (lessThan0) 84 | (Math.abs) 85 | (Math.sqrt) 86 | ``` 87 | 88 | Pipes must be multi-line. 89 | 90 | ```javascript 91 | // BAD 92 | const main = pipe ([ add ]) 93 | 94 | // GOOD 95 | const main = pipe ([ 96 | add 97 | ]) 98 | ``` 99 | 100 | Arrays must have a space after the opening bracket and before the closing bracket. 101 | 102 | ```javascript 103 | // BAD 104 | const array = [1, 2, 3] 105 | 106 | // GOOD 107 | const array = [ 1, 2, 3 ] 108 | ``` 109 | 110 | No semi-colons. 111 | 112 | ```javascript 113 | // BAD 114 | const value = 888; 115 | 116 | // GOOD 117 | const value = 888 118 | ``` 119 | -------------------------------------------------------------------------------- /threading/__tests__/sleep.test.js: -------------------------------------------------------------------------------- 1 | const sleep = require('../sleep') 2 | 3 | describe('threading/sleep', () => { 4 | beforeEach(() => jest.spyOn(global, 'setTimeout').mockImplementation(func => func())) 5 | afterEach(() => global.setTimeout.mockReset()) 6 | 7 | test('sleep returns value', () => { 8 | expect.assertions(1) 9 | const expected = 888 10 | const actual = sleep(0)(expected) 11 | return expect(actual).resolves.toBe(expected) 12 | }) 13 | 14 | test('sleep(100000) calls setTimeout with 100000 milliseconds', () => { 15 | expect.assertions(1) 16 | const expected = 1000 17 | sleep(expected)() 18 | const actual = global.setTimeout.mock.calls[0][1] 19 | expect(actual).toBe(expected) 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /threading/sleep.js: -------------------------------------------------------------------------------- 1 | const signature = require('../_internal/debug/signature') 2 | 3 | // sleep :: Number -> Any -> Promise 4 | const sleep = milliseconds => value => new Promise( 5 | resolve => setTimeout(() => resolve(value), milliseconds) 6 | ) 7 | 8 | module.exports = sleep 9 | 10 | // Experimental debug code 11 | /* istanbul ignore next */ 12 | if (process.env.MOJI_DEBUG === 'true') { 13 | module.exports = signature('sleep :: Number -> Any -> Promise')(sleep) 14 | } 15 | -------------------------------------------------------------------------------- /threading/sleep.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: sleep 3 | menu: threading 4 | --- 5 | 6 | # threading/sleep 7 | 8 | `sleep :: Number -> Any -> Any` 9 | 10 | `sleep` the application for a given number of `milliseconds`. 11 | 12 | ## Example 13 | 14 | ```javascript 15 | import { log, pipe, run sleep } from 'mojiscript' 16 | 17 | const state = 4 18 | 19 | const double = x => x * 2 20 | 21 | const main = pipe ([ 22 | log, 23 | sleep (1000), 24 | double, 25 | log 26 | ]) 27 | 28 | run ({ state, main }) 29 | //=> 4 30 | //=> (pause for 1 second) 31 | //=> 8 32 | ``` 33 | 34 | ## Parameters 35 | 36 | | Name | Type | Description | 37 | | ---- | ---- | ----------- | 38 | | milliseconds | `Number` | `Number` of milliseconds to sleep. | 39 | | value | `Any` | Value returned at the end of `sleep`. | 40 | 41 | ## Returns 42 | 43 | Returns the `value` at the end of the sleep. 44 | -------------------------------------------------------------------------------- /type/Either.js: -------------------------------------------------------------------------------- 1 | const signature = require('../_internal/debug/signature') 2 | const is = require('./is') 3 | const Left = require('./Left') 4 | const Right = require('./Right') 5 | const { typeEither } = require('./_allTypes') 6 | 7 | const isRight = is(Right) 8 | 9 | // fromFalsy :: Any -> Either 10 | const fromFalsy = value => value ? Right(value) : Left(value) 11 | 12 | // fromMaybe :: Any -> Either -> Any 13 | const fromEither = def => either => isRight(either) ? either.value : def 14 | 15 | // fromNullable :: Any -> Either 16 | const fromNullable = value => value == null ? Left(value) : Right(value) 17 | 18 | // try :: Thunk -> Maybe 19 | const maybeTry = thunk => { 20 | try { 21 | return Right(thunk()) 22 | } catch (err) { 23 | return Left(err) 24 | } 25 | } 26 | 27 | module.exports['@@type'] = typeEither 28 | module.exports.fromFalsy = fromFalsy 29 | module.exports.fromEither = fromEither 30 | module.exports.fromNullable = fromNullable 31 | module.exports.try = maybeTry 32 | 33 | // Experimental debug code 34 | /* istanbul ignore next */ 35 | if (process.env.MOJI_DEBUG === 'true') { 36 | module.exports['@@type'] = typeEither 37 | module.exports.fromFalsy = signature('fromFalsy :: Any -> Either')(fromFalsy) 38 | module.exports.fromEither = signature('fromEither :: Any -> Either -> Any')(fromEither) 39 | module.exports.fromNullable = signature('fromNullable :: Any -> Either')(fromNullable) 40 | } 41 | -------------------------------------------------------------------------------- /type/Just.js: -------------------------------------------------------------------------------- 1 | const is = require('./is') 2 | const { typeJust } = require('./_allTypes') 3 | 4 | const isFunction = is(Function) 5 | 6 | // Just :: Any -> Just 7 | const Just = value => Object.create( 8 | prototype, // eslint-disable-line no-use-before-define 9 | { 10 | value: { 11 | value, 12 | writable: false, 13 | enumerable: true, 14 | configurable: false 15 | } 16 | } 17 | ) 18 | 19 | function map(func) { 20 | return Just(func(this.value)) 21 | } 22 | 23 | function tap(func) { 24 | func(this.value) 25 | return Just(this.value) 26 | } 27 | 28 | function flatMap(func) { 29 | return func(this.value) 30 | } 31 | 32 | function leftMap() { 33 | return Just(this.value) 34 | } 35 | 36 | function ap(just) { 37 | return just.map(this.value) 38 | } 39 | 40 | function getOrElse() { 41 | return this.value 42 | } 43 | 44 | function getValue(errCallback, dataCallback) { 45 | return dataCallback(this.value) 46 | } 47 | 48 | const prototype = { 49 | '@@type': typeJust, 50 | ap, 51 | map, 52 | tap, 53 | flatMap, 54 | leftMap, 55 | getOrElse, 56 | getValue, 57 | 'fantasy-land/ap': ap, 58 | 'fantasy-land/map': map, 59 | toString() { return this.value.toString() }, 60 | inspect() { return `Just (${isFunction(this.value) ? `function ${this.value.name}()` : JSON.stringify(this.value)})` }, 61 | toJSON() { return this.value } 62 | } 63 | 64 | Just['@@type'] = typeJust 65 | 66 | module.exports = Just 67 | -------------------------------------------------------------------------------- /type/Left.js: -------------------------------------------------------------------------------- 1 | const is = require('./is') 2 | const { typeLeft } = require('./_allTypes') 3 | 4 | const isError = is(Error) 5 | const isFunction = is(Function) 6 | 7 | // Left :: Any -> Left 8 | const Left = value => Object.create( 9 | prototype, // eslint-disable-line no-use-before-define 10 | { 11 | value: { 12 | value, 13 | writable: false, 14 | enumerable: true, 15 | configurable: false 16 | } 17 | } 18 | ) 19 | 20 | function map() { 21 | return this 22 | } 23 | 24 | function tap() { 25 | return this 26 | } 27 | 28 | function flatMap() { 29 | return this 30 | } 31 | 32 | function leftMap(func) { 33 | const value = func(this.value) 34 | return Left(value) 35 | } 36 | 37 | function leftFlatMap(func) { 38 | const value = func(this.value) 39 | return value 40 | } 41 | 42 | function ap() { 43 | return this 44 | } 45 | 46 | function getOrElse(defaultValue) { 47 | return defaultValue 48 | } 49 | 50 | function getValue(errCallback) { 51 | return errCallback(this.value) 52 | } 53 | 54 | const prototype = { 55 | '@@type': typeLeft, 56 | ap, 57 | map, 58 | tap, 59 | flatMap, 60 | leftMap, 61 | leftFlatMap, 62 | getOrElse, 63 | getValue, 64 | 'fantasy-land/ap': ap, 65 | 'fantasy-land/map': map, 66 | inspect() { 67 | const value = isFunction(this.value) ? `function ${this.value.name}()` 68 | : isError(this.value) ? `[ Error: ${this.value.message} ]` 69 | : JSON.stringify(this.value) 70 | return `Left (${value})` 71 | } 72 | } 73 | 74 | Left['@@type'] = typeLeft 75 | 76 | module.exports = Left 77 | -------------------------------------------------------------------------------- /type/Maybe.js: -------------------------------------------------------------------------------- 1 | const signature = require('../_internal/debug/signature') 2 | const is = require('./is') 3 | const Just = require('./Just') 4 | const Nothing = require('./Nothing') 5 | const { typeMaybe } = require('./_allTypes') 6 | 7 | const isJust = is(Just) 8 | 9 | // fromFalsy :: Any -> Maybe 10 | const fromFalsy = value => value ? Just(value) : Nothing 11 | 12 | // fromMaybe :: Any -> Maybe -> Any 13 | const fromMaybe = def => maybe => isJust(maybe) ? maybe.value : def 14 | 15 | // fromNullable :: Any -> Maybe 16 | const fromNullable = value => value == null ? Nothing : Just(value) 17 | 18 | // unlift :: Function -> Array -> Any 19 | const unlift = func => (...args) => 20 | func(...args.map(fromNullable)).value 21 | 22 | module.exports['@@type'] = typeMaybe 23 | module.exports.fromFalsy = fromFalsy 24 | module.exports.fromMaybe = fromMaybe 25 | module.exports.fromNullable = fromNullable 26 | module.exports.unlift = unlift 27 | 28 | // Experimental debug code 29 | /* istanbul ignore next */ 30 | if (process.env.MOJI_DEBUG === 'true') { 31 | module.exports['@@type'] = typeMaybe 32 | module.exports.fromFalsy = signature('fromFalsy :: Any -> Maybe')(fromFalsy) 33 | module.exports.fromMaybe = signature('fromMaybe :: Any -> Maybe -> Any')(fromMaybe) 34 | module.exports.fromNullable = signature('fromNullable :: Any -> Maybe')(fromNullable) 35 | } 36 | -------------------------------------------------------------------------------- /type/Nothing.js: -------------------------------------------------------------------------------- 1 | const { typeNothing } = require('./_allTypes') 2 | const Just = require('./Just') 3 | 4 | function getOrElse(defaultValue) { 5 | return defaultValue 6 | } 7 | 8 | function getValue(errCallback) { 9 | return errCallback() 10 | } 11 | 12 | const Nothing = Object.freeze(Object.create( 13 | { 14 | '@@type': typeNothing, 15 | toString: () => '', 16 | inspect: () => 'Nothing', 17 | ap: () => Nothing, 18 | map: () => Nothing, 19 | tap: () => Nothing, 20 | leftMap: func => Just(func()), 21 | flatMap: () => Nothing, 22 | getOrElse, 23 | getValue, 24 | 'fantasy-land/map': () => Nothing, 25 | toJSON: () => null 26 | }, 27 | { 28 | value: { 29 | value: null, 30 | writable: false, 31 | enumerable: true, 32 | configurable: false 33 | } 34 | } 35 | )) 36 | 37 | module.exports = Nothing 38 | -------------------------------------------------------------------------------- /type/Right.js: -------------------------------------------------------------------------------- 1 | const is = require('./is') 2 | const { typeRight } = require('./_allTypes') 3 | 4 | const isError = is(Error) 5 | const isFunction = is(Function) 6 | 7 | // Right :: Any -> Right 8 | const Right = value => Object.create( 9 | prototype, // eslint-disable-line no-use-before-define 10 | { 11 | value: { 12 | value, 13 | writable: false, 14 | enumerable: true, 15 | configurable: false 16 | } 17 | } 18 | ) 19 | 20 | function map(func) { 21 | const value = func(this.value) 22 | return Right(value) 23 | } 24 | 25 | function tap(func) { 26 | func(this.value) 27 | return Right(this.value) 28 | } 29 | 30 | function flatMap(func) { 31 | const value = func(this.value) 32 | return value 33 | } 34 | 35 | function leftMap() { 36 | return this 37 | } 38 | 39 | function leftFlatMap() { 40 | return this 41 | } 42 | 43 | function ap(right) { 44 | return right.map(this.value) 45 | } 46 | 47 | function getOrElse() { 48 | return this.value 49 | } 50 | 51 | function getValue(errCallback, dataCallback) { 52 | return dataCallback(this.value) 53 | } 54 | 55 | const prototype = { 56 | '@@type': typeRight, 57 | ap, 58 | map, 59 | tap, 60 | flatMap, 61 | leftMap, 62 | leftFlatMap, 63 | getOrElse, 64 | getValue, 65 | 'fantasy-land/ap': ap, 66 | 'fantasy-land/map': map, 67 | inspect() { 68 | const value = isFunction(this.value) ? `function ${this.value.name}()` 69 | : isError(this.value) ? `[ Error: ${this.value.message} ]` 70 | : JSON.stringify(this.value) 71 | 72 | return `Right (${value})` 73 | } 74 | } 75 | 76 | Right['@@type'] = typeRight 77 | 78 | module.exports = Right 79 | -------------------------------------------------------------------------------- /type/__tests__/Nothing.test.js: -------------------------------------------------------------------------------- 1 | const Nothing = require('../Nothing') 2 | 3 | describe('type/Nothing', () => { 4 | test('is immutable', () => { 5 | expect.assertions(1) 6 | const expected = null 7 | const nothing = Nothing 8 | nothing.value = 666 9 | const actual = nothing.value 10 | expect(actual).toBe(expected) 11 | }) 12 | 13 | test('map', () => { 14 | expect.assertions(1) 15 | const expected = Nothing 16 | const nothing = Nothing 17 | const actual = nothing.map(x => x * 2) 18 | expect(actual).toBe(expected) 19 | }) 20 | 21 | test('tap', () => { 22 | expect.assertions(1) 23 | const expected = Nothing 24 | const nothing = Nothing 25 | const actual = nothing.tap(x => x * 2) 26 | expect(actual).toBe(expected) 27 | }) 28 | 29 | test('flatMap', () => { 30 | expect.assertions(1) 31 | const expected = Nothing 32 | const nothing = Nothing 33 | const actual = nothing.flatMap(() => 666) 34 | expect(actual).toBe(expected) 35 | }) 36 | 37 | test('leftMap with String', () => { 38 | expect.assertions(1) 39 | const expected = 'Just ("ABC")' 40 | const nothing = Nothing 41 | const actual = nothing.leftMap(() => 'ABC').inspect() 42 | expect(actual).toBe(expected) 43 | }) 44 | 45 | test('leftMap with Nothing', () => { 46 | expect.assertions(1) 47 | const expected = Nothing 48 | const nothing = Nothing 49 | const actual = nothing.leftMap(() => Nothing).value 50 | expect(actual).toBe(expected) 51 | }) 52 | 53 | test('getOrElse with Nothing', () => { 54 | expect.assertions(1) 55 | const expected = 888 56 | const nothing = Nothing 57 | const actual = nothing.getOrElse(888) 58 | expect(actual).toBe(expected) 59 | }) 60 | 61 | test('getValue', () => { 62 | expect.assertions(1) 63 | const expected = 'abc' 64 | const actual = Nothing.getValue(() => expected, () => 123) 65 | expect(actual).toBe(expected) 66 | }) 67 | 68 | test('fantasy-land/map', () => { 69 | expect.assertions(1) 70 | const expected = Nothing 71 | const nothing = Nothing 72 | const actual = nothing['fantasy-land/map'](x => x * 2) 73 | expect(actual).toBe(expected) 74 | }) 75 | 76 | test('toString', () => { 77 | expect.assertions(1) 78 | const expected = '' 79 | const actual = Nothing.toString() 80 | expect(actual).toBe(expected) 81 | }) 82 | 83 | test('inspect', () => { 84 | expect.assertions(1) 85 | const expected = 'Nothing' 86 | const actual = Nothing.inspect() 87 | expect(actual).toEqual(expected) 88 | }) 89 | 90 | test('toJSON', () => { 91 | expect.assertions(1) 92 | const expected = 'null' 93 | const actual = JSON.stringify(Nothing) 94 | expect(actual).toEqual(expected) 95 | }) 96 | 97 | test('@@type', () => { 98 | expect.assertions(1) 99 | const expected = 'symbol' 100 | const actual = typeof Nothing['@@type'] 101 | expect(actual).toBe(expected) 102 | }) 103 | 104 | test('has value key', () => { 105 | expect.assertions(1) 106 | const expected = [ 'value' ] 107 | const actual = Object.keys(Nothing) 108 | expect(actual).toEqual(expected) 109 | }) 110 | 111 | test('Number(Nothing) returns 0', () => { 112 | expect.assertions(1) 113 | const expected = 0 114 | const actual = Number(Nothing) 115 | expect(actual).toBe(expected) 116 | }) 117 | 118 | test('Nothing.ap returns Nothing', () => { 119 | expect.assertions(1) 120 | const expected = Nothing 121 | const double = x => x * 2 122 | const actual = Nothing.ap(double) 123 | expect(actual).toBe(expected) 124 | }) 125 | }) 126 | -------------------------------------------------------------------------------- /type/_allTypes.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | typeJust: Symbol('mojiscript/type/Just'), 3 | typeMaybe: Symbol('mojiscript/type/Maybe'), 4 | typeNothing: Symbol('mojiscript/type/Nothing'), 5 | typeEither: Symbol('mojiscript/type/Either'), 6 | typeRight: Symbol('mojiscript/type/Right'), 7 | typeLeft: Symbol('mojiscript/type/Left') 8 | } 9 | -------------------------------------------------------------------------------- /type/is.js: -------------------------------------------------------------------------------- 1 | const signature = require('../_internal/debug/signature') 2 | const { 3 | typeJust, typeMaybe, typeNothing, typeEither, typeLeft, typeRight 4 | } = require('./_allTypes') 5 | 6 | const testFunction = ctor => ctor === Function 7 | const isFunction = value => typeof value === 'function' 8 | 9 | const testSymbol = ctor => typeof ctor === 'function' && typeof ctor['@@type'] === 'symbol' 10 | const isSymbol = (ctor, value) => value != null && ctor['@@type'] === value['@@type'] 11 | 12 | const testMaybe = ctor => typeof ctor === 'object' && typeof ctor['@@type'] === 'symbol' && ctor['@@type'] === typeMaybe 13 | const isMaybe = value => value != null && typeof value['@@type'] === 'symbol' && [ typeJust, typeMaybe, typeNothing ].includes(value['@@type']) 14 | 15 | const testEither = ctor => typeof ctor === 'object' && typeof ctor['@@type'] === 'symbol' && ctor['@@type'] === typeEither 16 | const isEither = value => value != null && typeof value['@@type'] === 'symbol' && [ typeLeft, typeRight, typeEither ].includes(value['@@type']) 17 | 18 | const testType = ctor => typeof ctor === 'object' && typeof ctor['@@type'] === 'symbol' 19 | const isType = (ctor, value) => value != null && ctor['@@type'] === value['@@type'] 20 | 21 | const testPromise = ctor => ctor != null && ctor === Promise 22 | const isPromise = value => value != null && isFunction(value.then) 23 | 24 | const defaultTest = (ctor, value) => 25 | value != null && (value.constructor === ctor || (value != null && value instanceof ctor)) 26 | 27 | // is :: Type -> Any -> Boolean 28 | const is = ctor => value => 29 | testFunction(ctor) ? isFunction(value) 30 | : testSymbol(ctor) ? isSymbol(ctor, value) 31 | : testMaybe(ctor) ? isMaybe(value) 32 | : testEither(ctor) ? isEither(value) 33 | : testType(ctor) ? isType(ctor, value) 34 | : testPromise(ctor) ? isPromise(value) 35 | : defaultTest(ctor, value) 36 | 37 | module.exports = is 38 | 39 | // Experimental debug code 40 | /* istanbul ignore next */ 41 | if (process.env.MOJI_DEBUG === 'true') { 42 | module.exports = signature('is :: Type -> Any -> Boolean')(is) 43 | } 44 | -------------------------------------------------------------------------------- /type/is.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: is 3 | menu: type 4 | --- 5 | 6 | # type/is 7 | 8 | `is :: Any -> Any -> Boolean` 9 | 10 | Checks the type of an `Object`. 11 | 12 | ## Example 13 | 14 | ```javascript 15 | import { log, pipe, run, is } from 'mojiscript' 16 | 17 | const main = pipe ([ 18 | () => log (is (Function) (x => x)), //=> true 19 | () => log (is (Object) ({})), //=> true 20 | () => log (is (Object) (new String (''))), //=> true 21 | () => log (is (Boolean) (true)), //=> true 22 | () => log (is (Array) ([])), //=> true 23 | () => log (is (String) ('')), //=> true 24 | () => log (is (Number) (1)), //=> true 25 | () => log (is (Set) (new Set ())), //=> true 26 | () => log (is (Map) (new Map ())), //=> true 27 | ]) 28 | 29 | run ({ main }) 30 | ``` 31 | 32 | ## Parameters 33 | 34 | | Name | Type | Description | 35 | | ---- | ---- | ----------- | 36 | | ctor | `Object` | A constructor. | 37 | | value | `Any` | Value to test. | 38 | 39 | ## Returns 40 | 41 | Returns `true` when the `value` is of the same type as the `Object` or `false` when it is not. 42 | --------------------------------------------------------------------------------