Skip to content

Instantly share code, notes, and snippets.

@alexeygolev
Last active August 29, 2015 14:26
Show Gist options
  • Save alexeygolev/669fc619c93aaa535e18 to your computer and use it in GitHub Desktop.
Save alexeygolev/669fc619c93aaa535e18 to your computer and use it in GitHub Desktop.
Extra stuff for union types
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]
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
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}
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;
};
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