Haskell in ES6: Part 1
mateogianolio opened this issue · comments
Originally posted 2015-11-12
.
This post is the first in a series that will be dedicated to implementating native versions of Haskell functions according to JavaScript ES6 standards. Full source can be found in this GitHub repo. You are more than welcome to contribute!
ƒ.comp
Function composition.
(.) :: (b -> c) -> (a -> b) -> a -> c
/**
* Function composition
* @param ...fs functions to compose
* @return composed function
**/
export function comp (...fs) {
return (v, ...args) =>
fs.reduceRight(
(g, f) => f(g, ...args), v
);
}
Examples
var add = x => x + x,
pow = x => x * x,
inv = x => 1 / x;
var comp = ƒ.comp(add, pow, inv);
comp(1); // => 2
/**
* Explained:
* 1) inv 1 / 1 => 1
* 2) pow 1 * 1 => 1
* 3) add 1 + 1 => 2
**/
comp(4); // => 1/8
ƒ.flip
flip f
takes its (first) two arguments in the reverse order off
.
flip :: (a -> b -> c) -> b -> a -> c
/**
* Flip function arguments
* @param f function to flip
* @return f applied with args in reverse order
**/
export function flip (f) {
return (a, b, ...args) =>
f(b, a, ...args);
}
Examples
var add = (a, b) => a / b,
three = (a, b, c) => [a, b, c],
flip = ƒ.flip(add);
flip(10, 5); // => 1/2
flip(1, 10); // => 10
flip = ƒ.flip(three);
flip(1, 2, 3); // => [2, 1, 3]
ƒ.until
until p f
yields the result of applyingf
untilp
holds.
until :: (a -> Bool) -> (a -> a) -> a -> a
/**
* Applies a function which is passed as the second argument to
* the third argument and it comapares the result with the condition,
* if the condition evaluates to true, it prints the result, if not,
* it passes the result to the function and repeats the cycle as long
* as the condition is matched
* @param condition condition to be applied to f
* @param f function to match against
* @return result if condition is true else repeat cycle
**/
export function until (condition, f) {
return (...args) => {
var r = f(...args);
return condition(r) ? r : until(condition, f)(r);
};
}
Examples
var condition = x => x > 100,
inc = x => x + 1,
until = ƒ.until(condition, inc);
until(0); // => 101
condition = x => x === 5;
until = ƒ.until(condition, inc);
until(3); // => 5
List operations
head
extracts the first element of a list, which must be non-empty.
last
extracts the last element of a list, which must be finite and non-empty.
tail
extracts the elements after the head of a list, which must be non-empty.
init
returns all the elements of a list except the last one. The list must be non-empty.
head :: [a] -> a
last :: [a] -> a
tail :: [a] -> [a]
init :: [a] -> [a]
export function head (xs) { return xs[0]; }
export function last (xs) { return xs[xs.length - 1]; }
export function tail (xs) { return xs.slice(1); }
export function init (xs) { return xs.slice(0, -1); }
Examples
ƒ.head([5, 27, 3, 1]); // => 5
ƒ.last([5, 27, 3, 1]); // => 1
ƒ.tail([5, 27, 3, 1]); // => [27, 3, 1]
ƒ.init([5, 27, 3, 1]); // => [5, 27, 3]
Special folds
concat
yields the concatenation of all the elements of a container of lists.
concatMap
maps a function over all the elements of a container and concatenate the resulting lists.
concat :: Foldable t => t [a] -> [a]
concatMap :: Foldable t => (a -> [b]) -> t a -> [b]
export function concat (...xs) {
return xs.reduce(
(a, b) => a.concat(b)
);
}
export function concatMap (f, ...xs) {
return concat(xs.map(f));
}
Examples
ƒ.concat([5], [27], [3]); // => [5, 27, 3]
ƒ.concatMap(x => 'hi ' + x, 1, [[2]], 3); // => ['hi 1', 'hi 2', 'hi 3']
ƒ.zip and ƒ.zipWith
zip
takes two lists and returns a list of corresponding pairs. If one input list is short, excess elements of the longer list are discarded."
zipWith
generaliseszip
by zipping with the function given as the first argument, instead of a tupling function. For example,zipWith (+)
is applied to two lists to produce the list of corresponding sums."
zip :: [a] -> [b] -> [(a, b)]
zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
/**
* Zip two arrays into a list of n-ples
* @param ...xs arrays to zip
* @return a list of of n-ples
**/
export function zip (...xs) {
var r = [],
nple = [],
length = Math.min(...xs.map(x => x.length));
for (var i = 0; i < length; i++) {
xs.forEach(
x => nple.push(x[i])
);
r.push(nple);
nple = [];
}
return r;
}
/**
* Generalises zip by zipping with the function given
* as the first argument, instead of a tupling function.
* @param op function to zip with
* @param ...xs arrays to zip
* @return array zipped with the op function
**/
export function zipWith (op, ...xs) {
zip(...xs).map(
(x) => x.reduce(op)
);
}
Examples
var a = [0, 1, 2],
b = [3, 4, 5],
c = [6, 7, 8];
ƒ.zip(a, b); // => [[0, 3], [1, 4], [2, 5]]
ƒ.zipWith((a, b) => a + b, a, b, c); // => [9, 12, 15]
mistake in zip function:
export function zip (...xs) {
var r = [],
nple = [],
// should be: length = Math.min(...xs.map(x => x.length))
// or: length = Math.min.call(null, ...xs.map(x => x.length))
length = Math.min(null, ...xs.map(x => x.length));
for (var i = 0; i < length; i++) {
xs.forEach(
x => nple.push(x[i])
);
r.push(nple);
nple = [];
}
return r;
}
Thanks, fixed!