-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Initial PR #3
Initial PR #3
Changes from 2 commits
db7dea6
1be19e0
332527e
7a93834
6251345
aa367cc
e603535
a0c9066
a454568
2bd7b57
725845c
600baca
47beacb
502eebd
ae51106
adf4e6f
3fbdf34
dd7977f
1b52787
ea3d441
afeb3aa
2910165
815349a
34ef3fd
3b1f3e5
809d2da
00387fa
dcb81e7
51f13c9
551395c
dcc2df2
7e8ce85
69a1840
5ce9ada
bbee16b
4e28128
9031b74
3f700ab
accdf45
b3ef66b
7a8e54c
e6bd349
0fb99ff
4d6dbd1
8ebd50d
5f42911
7d6df8a
4dee2e3
90f4a47
e7efb93
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
node_modules | ||
jsconfig.json | ||
.eslintrc | ||
.vscode | ||
/coverage/ | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
The MIT License (MIT) | ||
|
||
Copyright (c) 2016 Sanctuary | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we include the following as well?
If you just drew inspiration from union-type this isn't necessary, but if you used its source code as a starting point I believe Simon has copyright. Fortunately union-type is MIT-licensed. :) /cc @paldepind There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's add Copyright for Simon (unless he objects), I didn't reference the source until the final stages but the API design is his. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ⚡ |
||
|
||
Permission is hereby granted, free of charge, to any person | ||
obtaining a copy of this software and associated documentation | ||
files (the "Software"), to deal in the Software without | ||
restriction, including without limitation the rights to use, | ||
copy, modify, merge, publish, distribute, sublicense, and/or | ||
sell copies of the Software, and to permit persons to whom the | ||
Software is furnished to do so, subject to the following | ||
conditions: | ||
|
||
The above copyright notice and this permission notice shall be | ||
included in all copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES | ||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT | ||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | ||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | ||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | ||
OTHER DEALINGS IN THE SOFTWARE. | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's delete this extra line. :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ⚡ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
ESLINT = node_modules/.bin/eslint --config node_modules/sanctuary-style/eslint-es6.json --env es6 | ||
ISTANBUL = node_modules/.bin/istanbul | ||
NPM = npm | ||
|
||
SRC = $(.) | ||
TEST = $(shell find test -name '*.js' | sort) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ⚡ |
||
|
||
|
||
.PHONY: all | ||
all: LICENSE | ||
|
||
.PHONY: LICENSE | ||
LICENSE: | ||
cp -- '$@' '[email protected]' | ||
sed 's/Copyright (c) .* Sanctuary/Copyright (c) $(shell git log --date=format:%Y --pretty=format:%ad | sort -r | head -n 1) Sanctuary/' '[email protected]' >'$@' | ||
rm -- '[email protected]' | ||
|
||
|
||
.PHONY: lint | ||
lint: | ||
$(ESLINT) --env node index.js test/test.js | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not necessary in this case, but I make a habit of including $(ESLINT) --env node -- index.js test/test.js There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ⚡ |
||
|
||
|
||
.PHONY: setup | ||
setup: | ||
$(NPM) install | ||
|
||
|
||
.PHONY: test | ||
test: | ||
$(ISTANBUL) cover "test/test.js" -- --recursive | ||
$(ISTANBUL) check-coverage --branches 98 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's change the requirement from 98% to 100%. Currently sanctuary-js/sanctuary-set#1 specifies There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ⚡ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
dependencies: | ||
override: | ||
- printf '%s\n' color=false progress=false >.npmrc | ||
- make setup | ||
|
||
machine: | ||
node: | ||
version: 6.1.0 | ||
|
||
test: | ||
override: | ||
- make lint test | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ⚡ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,317 @@ | ||
/* global Symbol,module */ | ||
const T = require('sanctuary-def'); | ||
|
||
const AutoPredicate = f => | ||
T.NullaryType(`[${f.toString()}]`, f); | ||
|
||
const B = (f, g) => (...args) => f(g(...args)); | ||
|
||
const map = require('ramda/src/map'); | ||
const curryN = require('ramda/src/curryN'); | ||
|
||
const values = o => | ||
Array.isArray(o) | ||
? o | ||
: Object.keys(o).map(k => o[k]); | ||
|
||
const zipObj = ks => vs => | ||
ks.length | ||
? Object.assign( | ||
...ks.map((k, i) => ({[k]: vs[i]})) | ||
) | ||
: {}; | ||
|
||
const unapply = f => (...values) => f(values); | ||
|
||
const mapConstrToFn = constraint => | ||
|
||
constraint === String | ||
? T.String | ||
: constraint === Number | ||
? T.Number | ||
: constraint === Boolean | ||
? T.Boolean | ||
: constraint === Object | ||
? T.Object | ||
: constraint === Array | ||
? T.Array(T.Any) | ||
: constraint === Function | ||
? T.AnyFunction | ||
: constraint; | ||
|
||
const BuiltInType = function(t){ | ||
const mapped = mapConstrToFn(t); | ||
|
||
if (mapped !== t){ | ||
return mapped; | ||
} else if (t.constructor === Function) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's not rely on object identity for the reasons given in sanctuary-js/sanctuary#100. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ⚡ |
||
return AutoPredicate(t); | ||
} else { | ||
return t; | ||
} | ||
}; | ||
|
||
const a = T.TypeVariable('a'); | ||
|
||
const createIterator = function() { | ||
return { | ||
idx: 0, | ||
val: this, | ||
next() { | ||
|
||
const keys = this.val._keys; | ||
|
||
/* eslint-disable no-plusplus */ | ||
return this.idx === keys.length | ||
? {done: true} | ||
: {value: this.val[keys[this.idx++]]}; | ||
/* eslint-enable no-plusplus */ | ||
}, | ||
}; | ||
}; | ||
|
||
const staticCase = function(options, b, ...args){ | ||
const f = options[b._name]; | ||
if (f){ | ||
|
||
const values = b._keys.map(k => b[k]); | ||
return f(...[...values, ...args]); | ||
|
||
} else if (options._) { | ||
return options._(b); | ||
} else { | ||
// caseOn is untyped | ||
// so this is possible | ||
throw new TypeError( | ||
'Non exhaustive case statement' | ||
); | ||
} | ||
}; | ||
|
||
const CaseRecordType = function(keys, enums){ | ||
return T.RecordType( | ||
keys.length | ||
? Object.assign( | ||
...keys.map( | ||
k => ({ | ||
[k]: T.Function(values(enums[k]).concat(a)), | ||
}) | ||
) | ||
) | ||
: {} | ||
); | ||
}; | ||
|
||
const ObjConstructorOf = prototype => (keys, name) => r => | ||
Object.assign( | ||
Object.create(prototype), | ||
r, | ||
{ | ||
_keys: keys, | ||
_name: name, | ||
[Symbol.iterator]: createIterator, | ||
} | ||
); | ||
|
||
const RecursiveType = Type => v => typeof v === 'undefined' ? Type : v; | ||
|
||
const processRawCases = | ||
(Type, rawCases) => | ||
map( | ||
map( | ||
B( | ||
BuiltInType, | ||
RecursiveType(Type) | ||
) | ||
), | ||
rawCases | ||
); | ||
|
||
const CreateCaseConstructor = function(def, prototype, typeName, cases){ | ||
|
||
const objConstructorOf = | ||
ObjConstructorOf(prototype); | ||
|
||
return function createCaseConstructor(k){ | ||
|
||
const type = cases[k]; | ||
|
||
const isArray = | ||
Array.isArray(type); | ||
|
||
const keys = Object.keys(type); | ||
|
||
const types = | ||
isArray | ||
? type | ||
: values(type); | ||
|
||
const recordType = | ||
isArray | ||
? T.RecordType( | ||
zipObj(keys)(types) | ||
) | ||
: T.RecordType(type); | ||
|
||
return { | ||
[`${k}Of`]: | ||
def( | ||
`${typeName}.${k}Of`, | ||
{}, | ||
[recordType, recordType], | ||
objConstructorOf(keys, k) | ||
), | ||
[k]: | ||
def( | ||
`${typeName}.${k}`, | ||
{}, | ||
types.concat(recordType), | ||
B( | ||
objConstructorOf(keys, k), | ||
unapply(zipObj(keys)) | ||
) | ||
), | ||
}; | ||
}; | ||
}; | ||
|
||
|
||
const boundStaticCase = function(options){ | ||
return staticCase(options, this); | ||
}; | ||
|
||
const Setup = function({check, ENV = T.env}){ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm opposed to functions with optional arguments (and records with optional fields). I've seen this pattern used in Haskell: fooWithOpts :: Opts -> Bar -> Baz
foo :: Bar -> Baz
We could follow Sanctuary's lead here. Sanctuary provides const $ = require('sanctuary-def');
const {create} = require('sanctuary-union-type');
const UnionType = create({check: false, env: $.env}); We could, though, export const {create, env} = require('sanctuary-union-type');
const UnionType = create({check: false, env: env}); Regardless of what we decide, we should rename the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree with all of this. One note, the naming for https://github.com/paldepind/union-type#disabling-type-checking But 👍 for having a consistent API within sanctuary libraries. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, yes, I had wondered whether There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ⚡ |
||
|
||
const def = | ||
T.create({ | ||
checkTypes: check, | ||
env: ENV, | ||
}); | ||
|
||
const CreateUnionType = function(typeName, rawCases, prototype = {}){ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's avoid this optional parameter. We can ensure that const Named =
def('UnionType.Named',
{},
[T.String, T.StrMap(T.Any), T.Any],
- CreateUnionType);
+ (x, y) => CreateUnionType(x, y, {}));
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ⚡ |
||
|
||
const Type = T.NullaryType( | ||
typeName, | ||
a => a && a['@@type'] === typeName | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's make the guard explicit by replacing Let's use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ⚡ |
||
); | ||
|
||
const keys = | ||
Object.keys(rawCases); | ||
|
||
const env = | ||
ENV.concat(Type); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd prefer There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ⚡ |
||
|
||
const def = T.create({checkTypes: check, env}); | ||
|
||
const cases = | ||
processRawCases(Type, rawCases); | ||
|
||
const createCaseConstructor = | ||
CreateCaseConstructor( | ||
def, | ||
prototype, | ||
typeName, | ||
cases | ||
); | ||
|
||
const constructors = | ||
keys.map(createCaseConstructor); | ||
|
||
const caseRecordType = | ||
CaseRecordType(keys, cases); | ||
|
||
const instanceCaseDef = | ||
def( | ||
`${typeName}::case`, | ||
{}, | ||
[caseRecordType, a], | ||
boundStaticCase | ||
); | ||
|
||
const flexibleInstanceCase = function(o, ...args){ | ||
if (o._){ | ||
return boundStaticCase.apply(this, [o, ...args]); | ||
} else { | ||
return instanceCaseDef.apply(this, [o, ...args]); | ||
} | ||
}; | ||
Type.prototype = Object.assign( | ||
prototype, | ||
{ | ||
'@@type': typeName, | ||
case: flexibleInstanceCase, | ||
env, | ||
} | ||
); | ||
|
||
Type.prototype.case.toString = | ||
Type.prototype.case.inspect = | ||
instanceCaseDef.toString; | ||
|
||
const staticCaseDef = | ||
def( | ||
`${typeName}.case`, | ||
{}, | ||
[caseRecordType, Type, a], | ||
staticCase | ||
); | ||
|
||
const flexibleStaticCase = function(o, ...args){ | ||
if (o._){ | ||
return curryN(2, staticCase).apply(this, [o, ...args]); | ||
} else { | ||
return staticCaseDef.apply(this, [o, ...args]); | ||
} | ||
}; | ||
|
||
Type.case = flexibleStaticCase; | ||
|
||
Type.case.toString = | ||
Type.case.inspect = | ||
staticCaseDef.toString; | ||
|
||
// caseOn opts out of typing because I'm | ||
// not smart enough to do it efficiently | ||
Type.caseOn = curryN(3, staticCase); | ||
|
||
return Object.assign( | ||
Type | ||
, ...constructors | ||
); | ||
}; | ||
|
||
const Named = | ||
def( | ||
'UnionType.Named' | ||
, {} | ||
, [T.String, T.StrMap(T.Any), T.Any] | ||
, CreateUnionType | ||
); | ||
|
||
const Anonymous = | ||
def( | ||
'UnionType.Anonymous', | ||
{}, | ||
[T.StrMap(T.Any), T.Any], | ||
enums => CreateUnionType( | ||
`(${Object.keys(enums).join(' | ')})` | ||
, enums | ||
) | ||
); | ||
|
||
const Class = | ||
def( | ||
'UnionType.Class' | ||
, {} | ||
, [T.String, T.StrMap(T.Any), T.Object, T.Any] | ||
, CreateUnionType | ||
); | ||
|
||
return { | ||
Anonymous, | ||
Named, | ||
Class, | ||
}; | ||
}; | ||
|
||
module.exports = Setup; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These comments will reveal my obsessive nature:
\n
). You may like to configure your editor to insert this automatically./
so it's clear exactly where we expect a file or directory to reside. (There are situations in which this is not practical.)/
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great points, I'll update my global .gitignore.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
⚡