Compare commits

...

8 Commits
main ... loops

8 changed files with 569 additions and 100 deletions

View File

@ -1,48 +1,164 @@
import compose from UnitJS.Fun;
import curry from UnitJS.Fun.Curry;
return {
apply: apply,
pure: pure,
wrap: pure, // kept for backwards compatibility
tuple: pure, // an alias to insist on the fact that pure can be used to build tuples
fmap: fmap,
liftUncurry: liftUncurry,
lift: lift,
liftA2: liftA2,
mApply: mApply,
bind: bind,
fail: fail,
http: http,
apply: apply,
map: map,
parallel: parallel,
run: run,
sequence: sequence,
parallel: parallel,
loopWhile: loopWhile,
loopUntil: loopUntil,
loopForEach: loopForEach,
wait: wait,
wrap: wrap
http: http,
run: run
};
function apply(f, x) {
return function(g) {
g(f(x));
/*
* The Async monad obtained by curryfying Continuation Passing Style
*
* Async a = (a -> void) -> void
*
*/
/*
* pure :: a -> Async a
*
* Since all values only exist as arguments to the next computation, this allows to represent tuples and indeed, pure is variadic and allows to build tuples by passing as many arguments as you want
*
* tuple :: (a0, a1, , an) -> Async (a0, a1, , an)
*/
function pure() {
var args = arguments;
return function(f) {
f.apply(null, args);
};
}
/*
* Async is a functor:
*
* fmap :: (a -> b) -> Async a -> Async b
*
*/
function fmap(f) {
return function(mA) {
return function(g) {
return mA(compose(g, f));
};
};
}
/*
* Async is also an Applicative:
*
* When considered for tuples, fmap is almost liftAN for all N for uncurryied functions, all we need to do is to collect the various Async arguments with `parallel`
*
* liftUncurry :: ((a0, a1, , an) -> b) -> (Async a0, Async a1, , Async an) -> Async b
*/
function liftUncurry(f) {
return function() {
return fmap(f)(parallel.apply(null, arguments));
}
}
/*
* A version for curryied functions (still taking the Async parameters in an uncurryied way)
*
* lift :: (a0 -> a1 -> -> an -> b) -> (Async a0, Async a1, , Async an) -> Async b
*/
function lift(f) {
return function() {
return Array.prototype.reduce.call(arguments, function(acc, v) {
return mApply(acc)(v);
}, pure(f));
};
}
/*
* The fully curryied version of lift for each arity can then be easily derived following this example for arity 2
*
* liftA2 :: (a -> b -> c) -> Async a -> Async b -> Async c
*/
function liftA2(f) {
return curry(lift(f), 2);
}
/*
* Since we defined `lift` in term of mApply, we must implement this part of Applicative:
*
* mApply :: Async (a -> b) -> Async a -> Async b
*/
function mApply(mF) {
return function(mA) {
return function(g) {
mF(function(f) {fmap(f)(mA)(g)});
};
};
}
/*
* The bind operator needed to use the monad as such
*
* bind :: Async a -> (a -> Async b) -> Async b
*
* Actually, this has been generalized to allow an arbitrarily long call chain
*
* bind :: Async a0 -> (a0 -> Async a1) -> (a1 -> Async a2) -> -> Async an
*/
function bind() {
var m, steps;
if(arguments.length < 1) {
return wrap();
return pure();
}
m = arguments[0];
steps = arguments;
return function(f) {
var i = 1;
var step = function(x) {
var step = function() {
if(i < steps.length) {
steps[i++](x)(step);
steps[i++].apply(null, arguments)(step);
} else {
return f(x);
return f.apply(null, arguments);
}
}
m(step);
};
}
/*
* A primitive to stop a computation in Async and display a log message in the console
*
* fail :: string -> Async a
*/
function fail(message) {
return function(f) {
console.log(message);
}
}
/*
* apply :: (a -> b, a) -> Async b
*/
function apply(f, x) {
return function(g) {
g(f(x));
};
}
/*
* map :: (a -> b) -> a -> Async b
*/
function map(mapper) {
return function(x) {
return function(f) {
@ -51,12 +167,37 @@ function map(mapper) {
};
}
/*
* A sequence of void computations
*
* sequence :: (Async void, Async void, , Async a) -> Async a
*/
function sequence() {
var steps = arguments;
return function(f) {
var i = 0;
var step = function() {
if(i < steps.length) {
steps[i++](step);
} else {
f.apply(null, arguments);
}
}
step();
};
}
/*
* Parallel computations which results are collected and passed on to the next computation as a tuple
*
* parallel :: (Async a0, Async a1, , Async an) -> Async (a0, a1, , an)
*/
function parallel() {
var threads = arguments;
var pending = threads.length;
var results = [];
var returned = [];
return function(f) {
var pending = threads.length;
var results = [];
var returned = [];
var useResult = function(i) {
return function(x) {
if(!returned[i]) {
@ -64,7 +205,7 @@ function parallel() {
returned[i] = true;
pending--;
if(pending < 1) {
f(results);
f.apply(null, results);
}
}
};
@ -75,43 +216,63 @@ function parallel() {
};
}
function run() {
var m;
if(arguments.length == 1) {
m = arguments[0];
} else {
m = sequence.apply(null, arguments);
}
m(function() {});
}
function sequence() {
var steps = arguments;
return function(f) {
var i = 0;
var step = function(x) {
if(i < steps.length) {
steps[i++](step);
} else {
f(x);
}
/*
* Three async loops:
*
* loopWhile performs the test before entering the loop and ends when the condition becomes false
*
* loopWhile :: (Async boolean, Async void) -> Async void
*/
function loopWhile(predicate, body) {
return bind(predicate, function(keepOn) {
if(keepOn) {
return sequence.apply(null, [].concat(body, loopWhile(predicate, body)));
} else {
return pure();
}
step();
};
});
}
/*
* A variant that check the condition at the end of the loop that, hence, always runs at least once. Also note that this loop stops when the condition becomes true
*
* loopUntil :: (Async void, Async boolean) -> Async void
*/
function loopUntil(body, predicate) {
return sequence.apply(null, [].concat(body, bind(predicate, function(atEnd) {
if(atEnd) {
return pure();
} else {
return loopUntil(body, predicate);
}
})));
}
/*
* A loop that iterates over an array; the body is a function that accesses the iteration context as its arguments just like Array.prototype.forEach: current element, its index in the array, the whole array
*
* loopForEach :: ([a], ((a, number, [a]) -> Async void)) -> Async void
*/
function loopForEach(array, bodyF) {
return sequence.apply(null, array.reduce(function(acc, v, k, a) {
return acc.concat(bodyF(v, k, a));
}, []));
}
/*
* wait :: number -> Async void
*/
function wait(delay) {
return function(f) {
setTimeout(f, delay);
};
}
function wrap(x) {
return function(f) {
f(x);
};
}
/*
* Perform an Async HTTP(S) query
*
* http :: query -> Async XMLHttpRequest
*/
function http(query) {
return function(f) {
var xhr = new XMLHttpRequest();
@ -123,6 +284,20 @@ function http(query) {
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
}
xhr.send(query.body);
};
}
/*
* The only «way-out» of Async: running the computation from without
*
* run :: Async a -> void
*/
function run() {
var m;
if(arguments.length == 1) {
m = arguments[0];
} else {
m = sequence.apply(null, arguments);
}
m(function() {});
}

42
src/UnitJS/Async/Box.js Normal file
View File

@ -0,0 +1,42 @@
import * as Async from UnitJS.Async;
function Box(initValue) {
this.value = initValue;
}
Box.prototype.get = function(f) {
f(this.value);
};
Box.prototype.set = function(newValue) {
return function(f) {
this.value = newValue;
f();
}.bind(this);
};
Box.prototype.bind = function(f) {
return function(g) {
f(this.value)(g);
}.bind(this);
};
Box.prototype.update = function(modifier) {
return function(f) {
modifier(this.value)(function (newValue) {this.value = newValue; f()}.bind(this));
}.bind(this);
};
return {
make: make
};
function make(initValue) {
var box = new Box(initValue);
return {
get: box.get.bind(box),
set: box.set.bind(box),
bind: box.bind.bind(box),
update: box.update.bind(box),
};
}

52
src/UnitJS/Async/Cache.js Normal file
View File

@ -0,0 +1,52 @@
function Cache(loader) {
this.loader = loader;
this.loaded = {};
this.loading = {};
}
Cache.prototype.get = function(key) {
return function(f) {
if(this.loaded[key] != undefined) {
f(this.loaded[key]);
} else {
this.startLoading(key);
this.loading[key].push(f);
this.loader(key)(this.store(key));
}
}.bind(this);
};
Cache.prototype.warmUp = function(key) {
if(this.loaded[key] == undefined) {
this.startLoading(key);
this.loader(key)(this.store(key));
}
};
Cache.prototype.startLoading = function(key) {
if(this.loading[key] == undefined) {
this.loading[key] = [];
}
};
Cache.prototype.store = function(key) {
return function(value) {
this.loaded[key] = value;
for(var i = 0; i < this.loading[key].length; i++) {
this.loading[key][i](value);
}
this.loading[key] = null;
}.bind(this);
};
return {
make: make
};
function make(loader) {
var cache = new Cache(loader);
return {
get: cache.get.bind(cache),
warmUp: cache.warmUp.bind(cache)
};
}

View File

@ -1,52 +1,3 @@
function Cache(loader) {
this.loader = loader;
this.loaded = {};
this.loading = {};
}
import * as Cache from UnitJS.Async.Cache;
Cache.prototype.get = function(key) {
return function(f) {
if(this.loaded[key] != undefined) {
f(this.loaded[key]);
} else {
this.startLoading(key);
this.loading[key].push(f);
this.loader(key)(this.store(key));
}
}.bind(this);
};
Cache.prototype.warmUp = function(key) {
if(this.loaded[key] == undefined) {
this.startLoading(key);
this.loader(key)(this.store(key));
}
};
Cache.prototype.startLoading = function(key) {
if(this.loading[key] == undefined) {
this.loading[key] = [];
}
};
Cache.prototype.store = function(key) {
return function(value) {
this.loaded[key] = value;
for(var i = 0; i < this.loading[key].length; i++) {
this.loading[key][i](value);
}
this.loading[key] = null;
}.bind(this);
};
return {
make: make
};
function make(loader) {
var cache = new Cache(loader);
return {
get: cache.get.bind(cache),
warmUp: cache.warmUp.bind(cache)
};
}
return Cache; //Re-export for compatibility reasons

View File

@ -22,11 +22,13 @@ function make(tag, properties, children) {
case "maxlength":
e.setAttribute("maxlength", value);
break;
case "onClick":
e.addEventListener("click", value);
break;;
default:
e[key] = value;
var matched = key.match(/on([A-Z]\w+)/);
if(matched) {
e.addEventListener(matched[1].toLowerCase(), value);
} else {
e[key] = value;
}
}
}
for(var i = 0; i < children.length; i++) {

View File

@ -3,6 +3,7 @@ return {
compose: compose,
defined: defined,
id: id,
not: not,
insert: insert,
map: map,
mapFilter: mapFilter,
@ -32,6 +33,10 @@ function id(x) {
return x;
}
function not(x) {
return !x;
}
function insert(obj, t, comparer, min, max) {
min = defined(min) ? min : 0;
max = defined(max) ? max : t.length;

137
src/UnitJS/Fun/Curry.js Normal file
View File

@ -0,0 +1,137 @@
return {
curry: curry,
flip: flip,
plus: plus,
minus: minus,
substractTo: substractTo,
times: times,
dividedBy: dividedBy,
divide: divide,
gt: gt,
ge: ge,
eq: eq,
neq: neq,
teq: teq,
tneq: tneq,
lt: lt,
le: le,
and: and,
or: or
};
function curry(f, n, t) {
n = n == undefined ? f.length : n;
t = t == undefined ? [] : t;
if(n < 1) {
return f.apply(null, t);
} else {
return function(x) {
var args = Array.prototype.slice.call(arguments);
return curry(f, n - args.length, t.concat(args));
};
}
}
function flip(f) {
return function(a) {
return function(b) {
return f(b)(a);
}
}
}
function plus(a) {
return function(b) {
return a+b;
}
}
function minus(a) {
return function(b) {
return b-a; // reversed to match the expected infix feel : «minus(4)» should substract 4 to whatever's passed to it, not the other way round — which is implemented by substractTo
}
}
function substractTo(a) {
return function(b) {
return a-b;
}
}
function times(a) {
return function(b) {
return a*b;
}
}
function dividedBy(a) {
return function(b) {
return b/a; // same natural order as for minus — the other way round equivalent is divide
}
}
function divide(a) {
return function(b) {
return a/b;
}
}
function gt(a) {
return function(b) {
return b > a;
};
}
function ge(a) {
return function(b) {
return b >= a;
};
}
function eq(a) {
return function(b) {
return b == a;
};
}
function neq(a) {
return function(b) {
return b != a;
};
}
function teq(a) {
return function(b) {
return b === a;
};
}
function tneq(a) {
return function(b) {
return b !== a;
};
}
function lt(a) {
return function(b) {
return b < a;
};
}
function le(a) {
return function(b) {
return b <= a;
};
}
function and(a) {
return function(b) {
return a && b;
};
}
function or(a) {
return function(b) {
return a || b;
};
}

105
src/UnitJS/Fun/Uncurry.js Normal file
View File

@ -0,0 +1,105 @@
return {
uncurry: uncurry,
flip: flip,
plus: plus,
minus: minus,
times: times,
divide: divide,
gt: gt,
ge: ge,
eq: eq,
neq: neq,
teq: teq,
tneq: tneq,
lt: lt,
le: le,
and: and,
or: or
};
function uncurry(f) {
return function() {
var args = Array.prototype.slice.call(arguments);
var result = f;
while(args.length > 0 && typeof result == 'function') {
result = result.apply(null, args.splice(0, result.length));
}
return result;
}
}
function flip(f) {
return function(a, b) {
return f(b)(a);
}
}
function plus(a, b) { // keeping two arguments so that it can be curryied to Curry.plus by default
return Array.prototype.reduce.call(arguments, function(acc, v) {
return acc+v;
}, 0);
}
function minus(a, b) {
return a-b;
}
function times(a, b) {
return Array.prototype.reduce.call(arguments, function(acc, v) {
return acc*v;
}, 1);
}
function divide(a, b) {
return a/b;
}
function gt(a, b) {
return a > b;
}
function ge(a, b) {
return a >= b;
}
function eq(a, b) {
return a == b;
}
function neq(a, b) {
return a != b;
}
function teq(a, b) {
return a === b;
}
function tneq(a, b) {
return a !== b;
}
function lt(a, b) {
return a < b;
}
function le(a, b) {
return a <= b;
}
function and(a, b) {
for(var i = 0; i < arguments.length; i++) {
if(arguments[i] === false) {
return false;
}
}
return true;
}
function or(a, b) {
for(var i = 0; i < arguments.length; i++) {
if(arguments[i] === true) {
return true;
}
}
return false;
}