Skip to content
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

Closed
wants to merge 50 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
db7dea6
Initial commit
JAForbes Oct 29, 2016
1be19e0
Removed comments from old eslint file
JAForbes Oct 29, 2016
332527e
add 'use strict'; pragmas
davidchambers Oct 29, 2016
7a93834
use conventional def formatting
davidchambers Oct 29, 2016
6251345
package.json: use 1.2.x format for dependency versions
davidchambers Oct 29, 2016
aa367cc
[email protected]
davidchambers Oct 29, 2016
e603535
license: remove blank line at end of file
davidchambers Oct 29, 2016
a0c9066
makefile: remove unused variable declarations
davidchambers Oct 29, 2016
a454568
use Unix line endings
davidchambers Oct 29, 2016
2bd7b57
makefile: use "--" separator
davidchambers Oct 29, 2016
725845c
makefile: increase branch coverage requirement to 100%
davidchambers Oct 29, 2016
600baca
package.json: remove "directories"
davidchambers Oct 29, 2016
47beacb
lint: remove ESLint exemptions
davidchambers Oct 29, 2016
502eebd
style: use ternary expressions
davidchambers Oct 29, 2016
ae51106
avoid "inconsistent" use of Array#concat
davidchambers Oct 29, 2016
adf4e6f
use explicit "!= null" guard
davidchambers Oct 29, 2016
3fbdf34
do not rely on constructor identity
davidchambers Oct 29, 2016
dd7977f
group and alphabetize imports
davidchambers Oct 29, 2016
1b52787
style: use more compact formatting
davidchambers Oct 29, 2016
ea3d441
style: use conventional identifier for sanctuary-def module
davidchambers Oct 29, 2016
afeb3aa
test: replace tape with mocha
davidchambers Oct 29, 2016
2910165
test: use assert.throws
davidchambers Oct 29, 2016
815349a
update .gitignore
davidchambers Oct 29, 2016
34ef3fd
test: rename file
davidchambers Oct 29, 2016
3b1f3e5
:%s/\<check\>/checkTypes/g
davidchambers Oct 29, 2016
809d2da
require "env" field
davidchambers Oct 29, 2016
00387fa
remove boundStaticCase
davidchambers Oct 29, 2016
dcb81e7
internal simplifications
davidchambers Oct 29, 2016
51f13c9
more internal simplifications
davidchambers Oct 29, 2016
551395c
remove one use of curryN
davidchambers Oct 29, 2016
dcc2df2
avoid type coercion
davidchambers Oct 29, 2016
7e8ce85
use Z.map
davidchambers Oct 29, 2016
69a1840
license: add @paldepind as a copyright holder
davidchambers Oct 29, 2016
5ce9ada
:%s/\<fn\>/f/g
davidchambers Oct 29, 2016
bbee16b
remove CaseRecordType
davidchambers Oct 29, 2016
4e28128
update package.json
davidchambers Oct 29, 2016
9031b74
add CONTRIBUTING.md
davidchambers Oct 29, 2016
3f700ab
makefile: integrate transcribe and xyz
davidchambers Oct 29, 2016
accdf45
remove BuiltInType
davidchambers Oct 29, 2016
b3ef66b
remove special case for typeof x === 'function'
davidchambers Oct 29, 2016
7a8e54c
change type of List.Nil from "() -> List a" to "List a"
davidchambers Oct 29, 2016
e6bd349
style: remove unnecessary { ... } block
davidchambers Oct 29, 2016
0fb99ff
test: tweak Maybe examples
davidchambers Oct 29, 2016
4d6dbd1
strip namespaces
davidchambers Oct 29, 2016
8ebd50d
progress towards unification with $.RecordType
davidchambers Oct 29, 2016
5f42911
remove duplication
davidchambers Oct 29, 2016
7d6df8a
test: use type variables
davidchambers Oct 29, 2016
4dee2e3
test: add namespace
davidchambers Oct 29, 2016
90f4a47
revert change to handling of nullary data constructors
davidchambers Oct 30, 2016
e7efb93
reinstate support for defining methods by mutating prototype
davidchambers Oct 30, 2016
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules
jsconfig.json
.eslintrc
.vscode
/coverage/
Copy link
Member

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:

  • The final line should be terminated by a newline character (\n). You may like to configure your editor to insert this automatically.
  • I like to begin all paths with / so it's clear exactly where we expect a file or directory to reside. (There are situations in which this is not practical.)
  • I like to end paths to directories with /.
  • I like to list directories before files, and order the paths in each of these groups alphabetically.
  • Only files created by the project should be listed. On macOS, for example, directories may contain .DS_Store files. One never wants to commit these, so they should be listed in one's global .gitignore. .vscode falls into this category.
  • I believe jsconfig.json and .eslintrc could be removed from the list.

Copy link
Member Author

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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

25 changes: 25 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
The MIT License (MIT)

Copyright (c) 2016 Sanctuary
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we include the following as well?

Copyright (c) 2015 Simon Friis Vindum

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

Copy link
Member Author

Choose a reason for hiding this comment

The 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.

Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's delete this extra line. :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

32 changes: 32 additions & 0 deletions Makefile
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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SRC and TEST aren't referenced in the makefile. Let's remove them.

Copy link
Member

Choose a reason for hiding this comment

The 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
Copy link
Member

Choose a reason for hiding this comment

The 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 -- to separate keyword arguments from positional arguments:

    $(ESLINT) --env node -- index.js test/test.js

Copy link
Member

Choose a reason for hiding this comment

The 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
Copy link
Member

Choose a reason for hiding this comment

The 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 98 because I integrated Istanbul after the fact and we haven't yet added test cases for the untested paths.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

12 changes: 12 additions & 0 deletions circle.yml
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

\n

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

317 changes: 317 additions & 0 deletions index.js
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) {
Copy link
Member

Choose a reason for hiding this comment

The 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. typeof should be fine here.

Copy link
Member

Choose a reason for hiding this comment

The 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}){
Copy link
Member

Choose a reason for hiding this comment

The 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

foo is defined simply by applying fooWithOpts to the default options.

We could follow Sanctuary's lead here. Sanctuary provides create, but the exported module is itself the result of applying create to the default options. The only downside of this approach is that the user would need to depend on sanctuary-def in order to disable type checking:

const $ = require('sanctuary-def');
const {create} = require('sanctuary-union-type');

const UnionType = create({check: false, env: $.env});

We could, though, export env (as Sanctuary does):

const {create, env} = require('sanctuary-union-type');

const UnionType = create({check: false, env: env});

Regardless of what we decide, we should rename the check field to match the name used by sanctuary-def (and Sanctuary).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with all of this. One note, the naming for check was completely informed by:

https://github.com/paldepind/union-type#disabling-type-checking

But 👍 for having a consistent API within sanctuary libraries.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yes, I had wondered whether check came from union-type. Since we'll be providing a different API, we're free to rename the property for consistency. :)

Copy link
Member

Choose a reason for hiding this comment

The 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 = {}){
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's avoid this optional parameter. We can ensure that prototype is always provided by making this change:

 const Named =
   def('UnionType.Named',
       {},
       [T.String, T.StrMap(T.Any), T.Any],
-      CreateUnionType);
+      (x, y) => CreateUnionType(x, y, {}));

x and y could be renamed, of course. :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


const Type = T.NullaryType(
typeName,
a => a && a['@@type'] === typeName
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make the guard explicit by replacing a && with a != null &&.

Let's use x as the parameter name to avoid shadowing our type variable named a.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

);

const keys =
Object.keys(rawCases);

const env =
ENV.concat(Type);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer ENV.concat([Type]).

Copy link
Member

Choose a reason for hiding this comment

The 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;
Loading