Last active
November 21, 2018 13:24
-
-
Save lin1987www/e238f026544fe8492cc4c91b078c4b30 to your computer and use it in GitHub Desktop.
This file contains hidden or 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 S from 'sanctuary' | |
import _ from 'lodash' | |
/* | |
mix 將所有 prototype 的 property 全部複製成新的 prototype ,而 prototype 的 prototype 一層一層合成複製起來,才能維持原本的階層架構 | |
mix 的 function 不能有任何帶有任何參數,因為想要從mix的先後順序呼叫 Constructors | |
Symbol.(mixPrototypes) 紀錄所以有 Constructors | |
根據 特性 實作各自繼承組合起來 | |
例如: MonoidArray 跟 MonadArray 各自的 定義 mix 起來 變成實際使用的 Array | |
*/ | |
// curry :: ((a, b, ...) -> c) -> a -> b -> ... -> c | |
function curry(fn, arity) { | |
if (typeof fn != "function") { | |
// console.log('Only curry on function.'); | |
return fn; | |
} | |
if (fn.prototype && Object.getPrototypeOf(fn.prototype) === curry.prototype) { | |
// console.log('Function is curried.'); | |
return fn; | |
} | |
arity = arity || fn.length; | |
if (arity <= 1) { | |
// console.log("Do not need to curry."); | |
return fn; | |
} | |
// Arity is a term which specifies the amount of arguments that a function takes. | |
function curried(...args) { | |
if (args.length < arity) { | |
return curried.bind(null, ...args); | |
} | |
return fn.call(null, ...args); | |
}; | |
curried.arity = arity; | |
curried.fn = fn; | |
Object.setPrototypeOf(curried.prototype, curry.prototype); | |
return curried; | |
}; | |
// compose :: ((a -> b), (b -> c), ..., (y -> z)) -> a -> z | |
// const compose = (...fns) => x => fns.reduceRight((y, f) => f(y), x); | |
const compose = (...fns) => { | |
return (...args) => { | |
// reduceRight :: Foldable f => f fns ~> callback -> initialValue -> accumulator | |
// callback :: accumulator -> currentValue -> accumulator | |
return fns.reduceRight((res, fn) => { | |
return [fn.call(null, ...res)] | |
}, args)[0]; | |
} | |
}; | |
const MIX = { | |
constructors: Symbol('MIX.constructors'), | |
f0: Object.getPrototypeOf(Function), | |
fPropertyNames: Object.getOwnPropertyNames(function () { | |
}), | |
fPrototypePropertyNames: Object.getOwnPropertyNames(function () { | |
}.prototype), | |
oPrototype: Object.prototype | |
}; | |
const mix = (name, ...fns) => { | |
let constructorArrayArray = fns.reduce((accumulator, currentValue) => { | |
function recordAllConstructors(constructorArrayArray, constructor, index) { | |
if (constructor != MIX.f0) { | |
let constructorArray; | |
if (index < constructorArrayArray.length) { | |
constructorArray = constructorArrayArray[index]; | |
} else { | |
constructorArray = []; | |
constructorArrayArray.push(constructorArray); | |
} | |
let constructors; | |
if (constructor.prototype.hasOwnProperty(MIX.constructors)) { | |
constructors = constructor.prototype[MIX.constructors]; | |
} else { | |
constructors = [constructor]; | |
} | |
constructors.forEach((constructor) => { | |
if (constructorArray.indexOf(constructor) == -1) { | |
constructorArray.push(constructor); | |
} | |
recordAllConstructors(constructorArrayArray, Object.getPrototypeOf(constructor), index + 1) | |
}); | |
} | |
}; | |
recordAllConstructors(accumulator, currentValue, 0); | |
return accumulator; | |
}, []); | |
// Removing duplicate constructor from constructorArrayArray | |
constructorArrayArray = constructorArrayArray.reduce((accumulator, currentValue, index, array) => { | |
function findLastIndex(constructor) { | |
return array.reduceRight((lastIndex, constructorArray, index) => { | |
if (lastIndex == -1 && constructorArray.indexOf(constructor) > -1) { | |
return index; | |
} | |
return lastIndex; | |
}, -1); | |
}; | |
let constructorArray = currentValue.filter(constructor => index == findLastIndex(constructor)); | |
if (constructorArray.length > 0) { | |
accumulator.push(constructorArray); | |
} | |
return accumulator; | |
}, []); | |
function mergeProperties(target, source, ignorePropertyNames) { | |
let objectProperties = source.reduce((accumulator, obj) => | |
Object.assign(accumulator, Object.getOwnPropertyDescriptors(obj)), | |
{}); | |
if (ignorePropertyNames && ignorePropertyNames.length > 0) { | |
let deletePropertyNames = Object.getOwnPropertyNames(objectProperties).filter( | |
(propertyName) => ignorePropertyNames.indexOf(propertyName) > -1 | |
); | |
deletePropertyNames.forEach((propertyName) => { | |
delete objectProperties[propertyName]; | |
}); | |
} | |
Object.defineProperties(target, objectProperties); | |
} | |
let F = function () { | |
// Pretend to execute new operator with all mixed constructors but with the same THIS. | |
let originPrototype = Object.getPrototypeOf(this); | |
Object.getPrototypeOf(this)[MIX.constructors].reduce((self, constructor) => { | |
Object.setPrototypeOf(this, constructor.prototype); | |
constructor.apply(self, []); | |
return self; | |
}, this); | |
Object.setPrototypeOf(this, originPrototype); | |
}; | |
constructorArrayArray.reduce((newClass, constructorArray, index, array) => { | |
// merging constructorArray to created new class. | |
let prototypeArray = constructorArray.map(constructor => constructor.prototype); | |
mergeProperties(newClass, constructorArray, MIX.fPropertyNames); | |
mergeProperties(newClass.prototype, prototypeArray, MIX.fPrototypePropertyNames); | |
newClass.prototype[MIX.constructors] = constructorArray.slice(); | |
let constructorNames = constructorArray.map(constructor => constructor.name); | |
let newClassName = '[' + constructorNames.join(', ') + ']'; | |
Object.defineProperty(newClass, "name", {value: newClassName}); | |
if (index < array.length - 1) { | |
let f = function () { | |
}; | |
Object.setPrototypeOf(newClass, f); | |
Object.setPrototypeOf(newClass.prototype, f.prototype); | |
return f; | |
} else { | |
return newClass; | |
} | |
}, F); | |
Object.defineProperty(F, "name", {value: name}); | |
return F; | |
}; | |
class Foldable { | |
static reduce(faba, a, fb) { | |
return fb.reduce(faba, a); | |
} | |
constructor() { | |
console.log('Foldable'); | |
} | |
reduce(faba, a) { | |
console.log('Foldable.[[Prototype]].reduce'); | |
// Foldable f => f b ~> (a -> b -> a) -> a -> a | |
} | |
} | |
class Semigroup { | |
constructor() { | |
console.log('Semigroup'); | |
} | |
concat() { | |
// mappend :: Semigroup => a -> a -> a | |
} | |
} | |
class Monoid extends Semigroup { | |
constructor() { | |
super(); | |
console.log('Semigroup'); | |
} | |
empty() { | |
// 這裡的 () 是直接沒有帶任何參數呼叫 | |
// empty :: Monoid a => () -> a | |
// 而原本的 Haskell 設計是 常數 | |
// mempty :: Monoid a => a | |
} | |
mconcat() { | |
// mconcat :: Monoid a => [a] -> a | |
} | |
} | |
class Functor { | |
static map(fab, fa) { | |
// fmap | |
// map :: (a -> b) -> f a -> f b | |
return fa.map(fab); | |
} | |
constructor() { | |
console.log('Functor'); | |
} | |
map(fab) { | |
console.log('Functor.[[Prototype]].map'); | |
// Functor f => f a ~> (a -> b) -> f b | |
} | |
} | |
class Apply extends Functor { | |
static ap(fab, fa) { | |
// 自從有 Applicative 後 lift 就不常用了 | |
// (<*>) | |
// ap :: f (a -> b) -> f a -> f b | |
return fa.ap(fab); | |
} | |
// Applicative Functor | |
constructor() { | |
super(); | |
console.log('Apply'); | |
} | |
ap(fab) { | |
console.log('Apply.[[Prototype]].ap'); | |
// 跟 Haskell 定義相反,但是結果一致! 由 fab fa 的巢狀結構 | |
// Apply f => f a ~> f (a -> b) -> f b | |
} | |
} | |
class Applicative extends Apply { | |
static of(a) { | |
console.log('Applicative.of ' + this.name); | |
}; | |
// Applicative pure | |
constructor() { | |
super(); | |
console.log('Applicative'); | |
} | |
of(a) { | |
console.log('Applicative.[[Prototype]].of ' + this.name); | |
}; | |
} | |
class Chain extends Apply { | |
static chain(ma, amb) { | |
// chain :: Chain m => m a -> (a -> m b) -> m b | |
return ma.chain(amb); | |
} | |
// Monad (>>=) do | |
constructor() { | |
super(); | |
console.log('Chain'); | |
} | |
cchain(amb) { | |
console.log('Chain.[[Prototype]].mchain'); | |
// Chain m => m a ~> (a -> m b) -> m b | |
} | |
chain(amb) { | |
try { | |
// Chain m => m a ~> (a -> m b) -> m b | |
return this.cchain(amb); | |
} | |
catch (e) { | |
return this.fail(e); | |
} | |
} | |
fail(e) { | |
console.log('Chain.[[Prototype]].fail'); | |
} | |
} | |
let Monad = mix('Monad', Applicative, Chain); | |
class MonadList extends Monad { | |
constructor() { | |
super(); | |
console.log('MonadList'); | |
this.ap = this.ap.bind(this); | |
} | |
ap(fab) { | |
// 跟 Haskell 定義相反,但是結果一致! 由 fab fa 的巢狀結構 | |
// Apply f => f a ~> f (a -> b) -> f b | |
return fab.reduce((fb, ab) => { | |
return fb.concat( | |
this.reduce((fb, a) => { | |
fb.push(ab(a)); | |
return fb; | |
}, []) | |
); | |
}, []); | |
} | |
cchain(amb) { | |
// Chain m => m a ~> (a -> m b) -> m b | |
return this.reduce((mb, a) => { | |
return mb.concat(amb(a)); | |
}, []); | |
} | |
} | |
let List = mix('List', Foldable, MonadList, Array); | |
// ---- TEST ---- | |
window.curry = curry; | |
window.Monad = Monad; | |
window.List = List; | |
let o = {}; | |
Object.setPrototypeOf(o, List.prototype); | |
List.apply(o, []); | |
// | |
console.log(Apply.ap([x => x, x => x * x], List.of(1, 2, 3))); | |
console.log(List.of(1, 2, 3).chain(x => [x, x + 10])); | |
/* | |
window._ = _; | |
window.compose = compose; | |
window.curry = curry; | |
var c2 = compose(compose, compose); | |
var f = function (x) { | |
return (x / 2); | |
}; | |
var g = curry(function (x, y) { | |
return (x / y); | |
}); | |
var c2r = g(12, 2); | |
console.log(c2r); | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment