UNPKG

fpes

Version:

Functional Programming for EcmaScript(Javascript)

334 lines (296 loc) 7.79 kB
class Pattern { constructor(matches, effect) { this.matches = matches; this.effect = effect; } } class Matchable { matches(...values) { throw new Error('No implementation'); } } function isNotNumber(v) { return isNaN(v) || v.toString().trim() === ''; } function inCaseOfEqual (value, effect) { return new Pattern((v)=>value === v, effect); } function inCaseOfNumber (effect) { return new Pattern((v)=>!isNotNumber(v), (v)=>effect(+v)); } function inCaseOfNaN (effect) { return new Pattern(isNotNumber, (v)=>effect(+v)); } function inCaseOfString (effect) { return new Pattern((v)=> typeof v === 'string', (v)=>effect(+v)); } function inCaseOfObject (effect) { return new Pattern((v)=> v && typeof v === "object" && (!Array.isArray(v)), effect); } function inCaseOfArray (effect) { return new Pattern((v)=> v && Array.isArray(v), effect); } function inCaseOfClass (theClass, effect) { return new Pattern((v)=> v instanceof theClass, effect); } function inCaseOfNull (effect) { return new Pattern((v)=> v === null || v === undefined, effect); } function inCaseOfFunction (effect) { return inCaseOfClass(Function, effect); } function inCaseOfPattern (effect) { return inCaseOfClass(Pattern, effect); } function inCaseOfPatternMatching (effect) { return inCaseOfClass(PatternMatching, effect); } function inCaseOfCompType (effect) { return inCaseOfClass(CompType, effect); } function inCaseOfCompTypeMatchesWithSpread (theCompType, effect) { return new Pattern((v)=> (TypeArray.matches(v) && theCompType.matches(...v)) || theCompType.matches(v), effect); } function inCaseOfRegex (regex, effect) { return new Pattern((v)=> { if (typeof regex === 'string') { regex = new RegExp(regex); } return regex.test(v); }, effect); } function TypeInCaseOf (matches) { return new Pattern(matches, ()=>true); } function TypeMatchesAllPatterns (...patterns) { return TypeInCaseOf((v)=>{ let matched = true; for (let pattern of patterns) { matched = matched && pattern.matches(v); } return matched; }); } function TypeADT(adtDef) { let patterns = []; let primaryTypesMapping = [ // Primary (adt) => { let theType = TypeString; return adt === theType || adt === String || theType.matches(adt) ? theType : undefined; }, (adt) => { let theType = TypeNumber; return adt === theType || adt === Number || theType.matches(adt) ? theType : undefined; }, (adt) => { let theType = TypeFunction; return adt === theType || adt === Function ? theType : undefined; }, // Rule (adt) => { let theType = TypeFunction; return theType.matches(adt) ? TypeInCaseOf(adt) : undefined; }, (adt) => { let theType = TypePattern; return theType.matches(adt) ? adt : undefined; }, (adt) => { let theType = TypePatternMatching; return theType.matches(adt) ? TypeInCaseOf((v)=>adt.matchFor(v)) : undefined; }, (adt) => { let theType = TypeCompType; return theType.matches(adt) ? adt : undefined; }, // Null/Nan (adt) => { let theType = TypeNull; return adt === theType || theType.matches(adt) ? theType : undefined; }, (adt) => { let theType = TypeInCaseOf((v) => (! (TypeObject.matches(v) || TypeArray.matches(v))) && TypeNaN.matches(v)); return adt === TypeNaN || theType.matches(adt) ? theType : undefined; }, ]; for (let theTypeMapping of primaryTypesMapping) { let theType = theTypeMapping(adtDef); if (theType) { return theType; } } if (TypeArray.matches(adtDef)) { let subPatternsForOr = adtDef.map((subAdt) => { return TypeADT(subAdt); }); subPatternsForOr.push(otherwise(()=>false)); let TypeSubVFromAdt = TypeInCaseOf((subV)=>{ return either(subV, ...subPatternsForOr); }); patterns.push(TypeArray); patterns.push(TypeInCaseOf((v)=>{ return v.map((item)=>{ return TypeSubVFromAdt.matches(item); }).reduce((prevResult, x) => x && prevResult, true); })); } else if (TypeObject.matches(adtDef)) { let patternsForAnd = []; for (let key in adtDef) { if (adtDef.hasOwnProperty(key)) { let fieldName = key; patternsForAnd.push(TypeInCaseOf((v)=>{ return TypeADT(adtDef[fieldName]).matches(v[fieldName]); })); } } patterns.push(TypeObject); patterns.push(TypeMatchesAllPatterns(...patternsForAnd)); } return TypeMatchesAllPatterns(...patterns); } function otherwise (effect) { return new Pattern(()=>true, effect); } function either(value, ...patterns) { for (let pattern of patterns) { // console.log(pattern.matches(value)); if (pattern.matches(value)) { return pattern.effect(value); } } throw new Error(`Cannot match ${JSON.stringify(value)}`); } class PatternMatching extends Matchable { constructor(...patterns) { super(); this.patterns = patterns; } matches(value) { return either(value, ...this.patterns); } matchFor(value) { return this.matches(value); } } class CompData { constructor(type, ...values) { this.type = type; this.values = values; } } class CompType extends Matchable { effect(...values) { if (this.matches(...values)) { return new CompData(this, values); } } apply(...values) { return this.effect(...values); } } class SumType extends CompType { constructor(...types) { super(); this.types = types; } matches(...values) { var type = this.innerMatches(...values); if (type) { return true; } return false; } effect(...values) { var type = this.innerMatches(...values); if (type) { return type.effect(...values); } } innerMatches(...values) { for (let type of this.types) { if (type.matches(...values)) { return type; } } } } class ProductType extends CompType { constructor(...types) { super(); this.types = types; } matches(...values) { if (values.length != this.types.length) { return false; } for (var i = 0; i < values.length; i++) { if (! this.types[i].matches(values[i])) { return false; } } return true; } } var TypeNumber = inCaseOfNumber(()=>true); var TypeString = inCaseOfString(()=>true); var TypeNaN = inCaseOfNaN(()=>true); var TypeObject = inCaseOfObject(()=>true); var TypeArray = inCaseOfArray(()=>true); var TypeNull = inCaseOfNull(()=>true); var TypeFunction = inCaseOfFunction(()=>true); var TypePattern = inCaseOfPattern(()=>true); var TypePatternMatching = inCaseOfPatternMatching(()=>true); var TypeCompType = inCaseOfCompType(()=>true); function TypeEqualTo(value) { return inCaseOfEqual(value, ()=>true); } function TypeClassOf(theClass) { return inCaseOfClass(theClass, ()=>true); } function TypeRegexMatches(regex) { return inCaseOfRegex(regex, ()=>true); } function TypeCompTypeMatchesWithSpread(theCompType) { return inCaseOfCompTypeMatchesWithSpread(theCompType, ()=>true); } module.exports = { either, Pattern, PatternMatching, inCaseOfEqual, inCaseOfNumber, inCaseOfString, inCaseOfNaN, inCaseOfObject, inCaseOfArray, inCaseOfClass, inCaseOfNull, inCaseOfFunction, inCaseOfPattern, inCaseOfPatternMatching, inCaseOfCompType, inCaseOfCompTypeMatchesWithSpread, inCaseOfRegex, otherwise, SumType, ProductType, CompType, TypeNumber, TypeString, TypeNaN, TypeObject, TypeArray, TypeNull, TypeFunction, TypePattern, TypePatternMatching, TypeCompType, TypeCompTypeMatchesWithSpread, TypeEqualTo, TypeClassOf, TypeRegexMatches, TypeInCaseOf, TypeMatchesAllPatterns, TypeADT, };