casualjavascript/blog

Haskell in ES6: Part 1

mateogianolio opened this issue · 2 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 of f.

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 applying f until p 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 generalises zip 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!