Skip to content

Instantly share code, notes, and snippets.

@lin1987www
Last active November 21, 2018 13:24
Show Gist options
  • Save lin1987www/e238f026544fe8492cc4c91b078c4b30 to your computer and use it in GitHub Desktop.
Save lin1987www/e238f026544fe8492cc4c91b078c4b30 to your computer and use it in GitHub Desktop.
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