Skip to content

Commit

Permalink
feat(init): initial version
Browse files Browse the repository at this point in the history
  • Loading branch information
safareli committed Oct 7, 2016
1 parent 46e0c35 commit 0ad1495
Show file tree
Hide file tree
Showing 14 changed files with 585 additions and 0 deletions.
19 changes: 19 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/* eslint-disable no-unused-vars */
const OFF = 0
const WARNING = 1
const ERROR = 2
/* eslint-enable no-unused-vars */

module.exports = {
parser: 'babel-eslint',

extends: 'standard',

rules: {
'comma-dangle': [ERROR, 'always-multiline'],
'space-before-function-paren': [
ERROR,
{anonymous: 'never', named: 'never'},
],
},
}
39 changes: 39 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
dist

# Logs
logs
*.log
npm-debug.log*

# Runtime data
pids
*.pid
*.seed

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# nyc test coverage
.nyc_output

# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# node-waf configuration
.lock-wscript

# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules
jspm_packages

# Optional npm cache directory
.npm

# Optional REPL history
.node_repl_history
22 changes: 22 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
sudo: false
language: node_js
cache:
directories:
- node_modules
notifications:
email: false
node_js:
- '6'
before_install:
- npm i -g npm@^3.0.0
before_script:
- npm prune
script:
- npm run check
- npm run coverage
- npm run build
after_success:
- npm run semantic-release
branches:
except:
- /^v\d+\.\d+\.\d+$/
91 changes: 91 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
{
"name": "@safareli/quasi",
"version": "0.0.0-placeholder",
"description": "Combination of a quasi applicative functor and quasi monad",
"main": "dist/quasi.js",
"scripts": {
"lint": "eslint --no-ignore .eslintrc.js --ext js src test tools *.js",
"watch": "nodemon -q -x 'tap test/*.js'",
"test": "tap test/*.js --coverage",
"coverage": "tap --coverage-report=lcov --no-browser && codecov",
"coverage:open": "tap --coverage-report=lcov",
"coverage:check": "tap --check-coverage --branches 95 --functions 95 --lines 95 --statements 95",
"check": "npm run lint && npm run test -- --no-coverage-report && npm run coverage:check",
"commit": "git-cz",
"prebuild": "rimraf dist",
"build": "run-p build:*",
"build:main": "babel -s --out-dir dist src",
"build:umd": "webpack --output-filename quasi.umd.js",
"build:umd.min": "webpack --output-filename quasi.umd.min.js -p",
"semantic-release": "semantic-release pre && npm publish && semantic-release post"
},
"repository": {
"type": "git",
"url": "https://github.com/safareli/quasi.git"
},
"keywords": [
"Functor",
"Apply",
"Applicative",
"Chain",
"ChainRec",
"Monad",
"Semigroup",
"Monoid",
"Setoid",
"fantasy-land"
],
"files": [
"dist",
"LICENSE",
"README.md"
],
"author": "Irakli Safareli <i.safareli@gmail.com>",
"license": "MIT",
"bugs": {
"url": "https://github.com/safareli/quasi/issues"
},
"homepage": "https://github.com/safareli/quasi#readme",
"devDependencies": {
"babel-cli": "6.10.1",
"babel-core": "6.10.4",
"babel-eslint": "6.1.0",
"babel-loader": "6.2.4",
"babel-preset-es2015": "6.9.0",
"babel-preset-stage-2": "6.11.0",
"codecov": "1.0.1",
"commitizen": "2.8.2",
"conventional-recommended-bump": "0.2.1",
"cz-conventional-changelog": "1.1.6",
"daggy": "0.0.1",
"eslint": "2.13.1",
"eslint-config-standard": "5.3.1",
"eslint-plugin-promise": "1.3.2",
"eslint-plugin-standard": "1.3.2",
"fantasy-combinators": "0.0.1",
"ghooks": "1.3.0",
"nodemon": "1.9.2",
"npm-run-all": "2.3.0",
"rimraf": "2.5.3",
"semantic-release": "^4.3.5",
"tap": "6.1.1",
"webpack": "1.13.1"
},
"babel": {
"presets": [
"es2015",
"stage-2"
]
},
"release": {
"analyzeCommits": "./tools/releaseAnalyzeCommits"
},
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
},
"ghooks": {
"pre-commit": "npm run check"
}
}
}
16 changes: 16 additions & 0 deletions src/fl.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module.exports = {
equals: 'fantasy-land/equals',
concat: 'fantasy-land/concat',
empty: 'fantasy-land/empty',
map: 'fantasy-land/map',
ap: 'fantasy-land/ap',
of: 'fantasy-land/of',
reduce: 'fantasy-land/reduce',
traverse: 'fantasy-land/traverse',
chain: 'fantasy-land/chain',
chainRec: 'fantasy-land/chainRec',
extend: 'fantasy-land/extend',
extract: 'fantasy-land/extract',
bimap: 'fantasy-land/bimap',
promap: 'fantasy-land/promap',
}
82 changes: 82 additions & 0 deletions src/quasi.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
const fl = require('./fl.js')
const key$of = '@functional/of'
const key$empty = '@functional/empty'
const isEmpty = m => m[key$empty] === true
const isOf = a => a[key$of] === true

// `of` createas container for a value which currently has no type.
// result could conforms to specifications: Functor, Apply,
// Applicative, Chain, ChainRec, Monad, Semigroup, Monoid and Setoid
// of :: a => m a
const of = (a) => ({
[key$of]: true,
value: a,
constructor: of,
toString: () => `<of>(${a})`,
[fl.map]: (f) => of(f(a)),
[fl.ap]: (f) => isOf(f) ? of(f.value(a)) : f.constructor[fl.of](a)[fl.ap](f),
[fl.chain]: (f) => f(a),
[fl.equals]: (b) => {
if (isOf(b)) {
return a === b.value || (typeof a[fl.equals] === 'function' && a[fl.equals](b.value))
} else if (isEmpty(a)) {
return isEmpty(b)
} else if (typeof b.constructor[fl.of] === 'function' ) {
return b.constructor[fl.of](a)[fl.equals](b)
} else {
return false
}
},
[fl.concat]: (b) => {
if (isOf(b)) {
return of(a[fl.concat](b.value))
} else if (isEmpty(b)) {
return of(a)
} else {
return b.constructor[fl.of](a)[fl.concat](b)
}
},
})

const methodNeedsValueErrorTpl = (methodName) =>
`can't call '${methodName}' method as current instance does not contain a value`

// it represents `empty` value of some Monoid type is not know yet like result of `of`
const empty = {
[key$empty]: true,
toString: () => `<empty>`,
constructor: of,
[fl.map]: (_) => { throw new TypeError(methodNeedsValueErrorTpl('map')) },
[fl.ap]: (_) => { throw new TypeError(methodNeedsValueErrorTpl('ap')) },
[fl.chain]: (_) => { throw new TypeError(methodNeedsValueErrorTpl('chain')) },
[fl.concat]: a => a,
[fl.equals]: a => isEmpty(a) || (isOf(a) && isEmpty(a.value)),
}

of[fl.empty] = empty
of[fl.of] = of

const chainRecNext = value => ({ isNext: true, value })
const chainRecDone = value => ({ isNext: false, value })
of[fl.chainRec] = (f, i) => {
let step = f(chainRecNext, chainRecDone, i)
while (isOf(step) && step.value.isNext) {
step = f(chainRecNext, chainRecDone, step.value.value)
}
if (isOf(step)) {
return of(step.value.value)
}
return step[fl.chain](({ isNext, value }) =>
isNext ? step.constructor[fl.chainRec](f, value) : step.constructor[fl.of](value)
)
}

const foldIfIsOf = (f, a) => isOf(a) ? f(a.value) : a

module.exports = {
empty,
isEmpty,
of,
isOf,
foldIfIsOf,
}
35 changes: 35 additions & 0 deletions test/common/Func.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
const daggy = require('daggy')

const Q = require('../../src/quasi.js')
const fl = require('../../src/fl.js')

const Func = daggy.tagged('run')

Func[fl.of] = (a) => Func((_) => a)

Func.prototype[fl.ap] = function(g) {
const f = this
return Func((x) => Q.foldIfIsOf(Func[fl.of], g).run(x)(f.run(x)))
}

Func[fl.empty] = Func[fl.of](Q.empty)

Func.prototype[fl.concat] = function(g) {
if (Q.isEmpty(g)) {
return this
}
const f = this
return Func((x) => {
const a = this.run(x)
const b = Q.foldIfIsOf(Func[fl.of], g).run(x)
if (Q.isEmpty(a)) {
return b
} else if (Q.isEmpty(b)) {
return a
} else {
return a[fl.concat](b)
}
})
}

module.exports = Func
52 changes: 52 additions & 0 deletions test/common/Identity.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
const daggy = require('daggy')

const Q = require('../../src/quasi.js')
const fl = require('../../src/fl.js')

const Identity = daggy.tagged('value')

Identity[fl.of] = (a) => Identity(a)

Identity[fl.empty] = Identity[fl.of](Q.empty)

Identity.prototype[fl.equals] = function(b) {
return this.value === b.value || (typeof this.value[fl.equals] == 'function' && this.value[fl.equals](b.value))
}

Identity.prototype[fl.chain] = function(f) {
return f(this.value)
}


const chainRecNext = value => ({ isNext: true, value })
const chainRecDone = value => ({ isNext: false, value })

Identity[fl.chainRec] = function(f, i) {
var state = chainRecNext(i);
while (state.isNext) {
state = f(chainRecNext, chainRecDone, state.value).value;
}
return Identity(state.value);

}

Identity.prototype[fl.concat] = function(b) {
b = Q.foldIfIsOf(Identity[fl.of], b)
if (Q.isEmpty(this.value)) {
return b
} else if (Q.isEmpty(b) || Q.isEmpty(b.value)) {
return this
} else {
return Identity(this.value[fl.concat](b.value))
}
}

Identity.prototype[fl.ap] = function(f) {
if (Q.isOf(f)) {
return this.map(f.value)
} else {
return Identity(f.value(this.value))
}
}

module.exports = Identity
Loading

0 comments on commit 0ad1495

Please sign in to comment.