Last active
August 29, 2015 14:26
-
-
Save alexeygolev/669fc619c93aaa535e18 to your computer and use it in GitHub Desktop.
Extra stuff for union types
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const Color = Type({RGB:[Number, Number, Number], CMYK:[Number, Number, Number, Number]}); | |
const Color_ = Type({RGB:{Red: Number, Green: Number, Blue: Number}, CMYK:{Cyan: Number, Magenta: Number, Yellow: Number, Black:Number}}); | |
const {Person} = Type({Person: {name: String, id: Number, leftEyeColor: Color_, rightEyeColor: Color}}); | |
let person = Person({name: 'John', id: 1, leftEyeColor: Color.RGB(255,100,255), rightEyeColor: Color_.CMYK({Cyan: 50, Magenta: 80, Yellow: 10, Black: 25})}); | |
let personWithoutEyes = Person('John', 2); | |
let person2 = personWithoutEyes(Color.RGB(100,100,100), Color.CMYK(30,30,30,0)); | |
console.log('person', person.value); // person ["John", 1, Object, Object] | |
console.log('person2', person2.value); // person ["John", 1, Object, Object] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import validate from './validation'; | |
import t from './union-type'; | |
const Type = t(validate); | |
//quite handy - no need to use it as Person.Person... we have all the important stuff in our prototype chain anyway | |
let {Person} = Type({Person:{name: String, id: Number}}, { | |
//we can pass additional methods that will be available for our type | |
isPerson(){ | |
return true; | |
}, | |
//a bit of a contrived example to illustrate the point | |
// and ramda is doing a dynamic dyspatch so we can do R.map(fn, Person("John", 1) | |
map(f) { | |
return this[this._ctor](f(this._value.name), this._value.id) | |
} | |
}); | |
//this was Person.Person before which was wrong because I used destructuring above | |
let person = Person("John", 2); | |
//or using record syntax. same — it was Person.Person before | |
let person2 = Person({id: 2, name: "Johnny"}); | |
console.log('person', person.value); //["John", 2] | |
console.log('person', person2.value);//["Johnny",2] | |
console.log('person', person2.isPerson());//true | |
//to implement Maybe we need a method to lift a value into a Maybe | |
// typeApi will come handy | |
let Maybe = Type({Just:[R.T], Nothing:[]}, { | |
map(f) { | |
if(this._ctor === 'Nothing') return this.Nothing(); | |
return this.Just(f(this._value[0])) | |
} | |
}, { | |
of(x) { | |
if(!x) return this.Nothing(); | |
return this.Just(x); | |
} | |
}); | |
let nothing = Maybe.of(); | |
console.log('nothing', R.map((x)=> x + 1, nothing).toString()); // Nothing() | |
let something = Maybe.of(2); | |
console.log('nothing', R.map((x)=> x + 1, nothing).toString()); // Just(3) | |
//some crazy stuff | |
let {High, Medium, Low} = Type({High:[], Medium:[], Low:[]}, { | |
louderThan(x) { | |
return this.caseOn({ | |
High: R.T, | |
Medium: this.caseOf({ | |
High: R.T, | |
_: R.F | |
}), | |
_: R.F | |
}, this, x); | |
}, | |
quiterThan(x) { | |
return !this.louderThan(x); | |
} | |
}); | |
let vol = High(); | |
console.log(vol.louderThan(Medium())); // true | |
vol = Medium() | |
console.log(vol.quiterThan(Low())); // false |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let {Person} = Type({Person:{name: String, id: Number}}, { | |
//we can pass additional methods that will be available for our type | |
rValue(){ | |
return _value; | |
} | |
}); | |
let person = Person('John', 1); | |
console.log(person.rValue); // {name: 'John', id: 1} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {curryN, values} from 'ramda'; | |
var _validate = function(){}; | |
function isObject(value) { | |
var type = typeof value; | |
return !!value && (type == 'object' || type == 'function') && Object.prototype.toString.call(value) !== '[object Array]'; | |
} | |
function getValues(group, name, args, validators, valIsObj, cons) { | |
_validate(group, name, args, validators, valIsObj); | |
let keys = Object.keys(validators); | |
if (valIsObj) { | |
let _args = {}; | |
if (!isObject(args[0])) { | |
for (var i = 0; i < keys.length; i++) { | |
_args[keys[i]] = args[i]; | |
} | |
args = _args; | |
} else { | |
args = args[0]; | |
} | |
} | |
let v = Object.create(cons); | |
v.value = args; | |
return v; | |
} | |
function Constructor(group, name, validators) { | |
var cons = Object.create(group); | |
Object.defineProperties(cons, { | |
_ctor: { | |
value: name | |
}, | |
_value: { | |
writable: true | |
}, | |
toString: { | |
value: function(){ | |
return `${this._ctor}(${this.value.toString()})` | |
} | |
}, | |
value: { | |
get: function() { | |
return Object.keys(validators).map((key) => this._value[key]); | |
}, | |
set: function(...vals) { | |
this._value = vals[0] | |
} | |
} | |
}); | |
var valIsObj = isObject(validators); | |
return function(...args) { | |
if (args.length < validators.length && !valIsObj) { | |
return curryN(validators.length - args.length, (...rest) => { | |
return getValues(group, name, [...args, ...rest], validators, valIsObj, cons); | |
}) | |
} | |
if (valIsObj) { | |
let vLength = Object.keys(validators).length; | |
if(args.length < vLength && !isObject(args[0])) { | |
return curryN(vLength - args.length, (...rest) => { | |
return getValues(group, name, [...args, ...rest], validators, valIsObj, cons); | |
}) | |
} | |
} | |
return getValues(group, name, args, validators, valIsObj, cons); | |
}; | |
} | |
function rawCase(type, cases, action, arg) { | |
if (!type.isPrototypeOf(action)) throw new TypeError(`Data constructor ${action._ctor} is not in scope`); | |
var _case = cases[action._ctor] || cases['_']; | |
if (!_case) { | |
throw new Error('Non-exhaustive patterns in a function'); | |
} | |
return _case.apply(undefined, arg !== undefined ? action.value.concat([arg]) : action.value); | |
} | |
var typeCase = curryN(3, rawCase); | |
var caseOn = curryN(4, rawCase); | |
function Type(desc, api, typeApi) { | |
let obj = api ? {...api} : {}; | |
Object.keys(desc).forEach( key => { | |
obj[key] = Constructor(obj, key, desc[key]); | |
}); | |
obj.caseOf = typeCase(obj); | |
obj.caseOn = caseOn(obj); | |
return obj; | |
} | |
module.exports = (validate) => { | |
_validate = validate || _validate; | |
return Type; | |
}; | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {curryN, values, difference} from 'ramda'; | |
function isString(s) { | |
return typeof s === 'string'; | |
} | |
function isNumber(n) { | |
return typeof n === 'number'; | |
} | |
function isBoolean(b) { | |
return typeof b === 'boolean'; | |
} | |
function isObject(value) { | |
var type = typeof value; | |
return !!value && (type == 'object' || type == 'function') && Object.prototype.toString.call(value) !== '[object Array]'; | |
} | |
function isFunction(f) { | |
return typeof f === 'function'; | |
} | |
var isArray = Array.isArray || function (a) { | |
return 'length' in a; | |
}; | |
var mapConstrToFn = function (group, constr) { | |
return constr === String ? isString | |
: constr === Number ? isNumber | |
: constr === Boolean ? isBoolean | |
: constr === Object ? isObject | |
: constr === Array ? isArray | |
: constr === Function ? isFunction | |
: constr === undefined ? group | |
: constr; | |
}; | |
function numToStr(n) { | |
switch (n) { | |
case 1: | |
return 'first'; | |
case 2: | |
return 'second'; | |
case 3: | |
return 'third'; | |
case 4: | |
return 'fourth'; | |
case 5: | |
return 'fifth'; | |
case 6: | |
return 'sixth'; | |
case 7: | |
return 'seventh'; | |
case 8: | |
return 'eighth'; | |
case 9: | |
return 'ninth'; | |
case 10: | |
return 'tenth'; | |
} | |
} | |
export default function validate(group, name, args, validators, valIsObj) { | |
var val = [], v, validator; | |
var validateArgs = args; | |
var validatorsValues = valIsObj ? values(validators) : validators; | |
var validatorsKeys = Object.keys(validators); | |
if(validators.length === 0) { | |
if(args.length > 0) { | |
throw new TypeError(`${name} is applied to ${args.length} argument${args.length > 1 ? 's' : ''} but it's type has none`); | |
} | |
return val; | |
} else if (args.length < validatorsValues.length && isObject(args[0])){ | |
let argsKeys = Object.keys(args[0]); | |
if(argsKeys.length < validatorsKeys.length) { | |
let fields = difference(validatorsKeys, argsKeys); | |
throw new TypeError(`Fileds of ${name} not initialised: ${fields}`); | |
} | |
validateArgs = validatorsKeys.map(key => args[0][key]); | |
args = args[0] | |
} | |
for (var i = 0; i < validateArgs.length; ++i) { | |
v = validateArgs[i]; | |
validator = mapConstrToFn(group, validatorsValues[i]); | |
if ((typeof validator === 'function' && validator(v)) || | |
(v !== undefined && v !== null && v._ctor in validator)) { | |
val[i] = v; | |
} else { | |
throw new TypeError(`Couldn't match expected type ${validator.name || validator} with actual type ${typeof v}. In the ${numToStr(i + 1)} argument of ${name} namely "${v}"`); | |
} | |
} | |
return val; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment