module.exports = {
  mergeInto: mergeInto,
  pairs: pairs,
  foldl: foldl,
  lift2: lift2,
  append: append,
  ap: ap,
  map: map,
  flatMap: flatMap,
  curry: curry,
  comp: comp,
  concatMap: concatMap
}

function mergeInto(base, obj) {
  Object.keys(obj).forEach(function (key) {
    base[key] = obj[key]
  })
  return base
}

function pairs(coll) {
  return Object.keys(coll).map(function (key) {
    return [key, coll[key]]
  })
}

function foldl(arr, fn, accumulator) {
  var item, index, value
  var length = arr.length

  if (accumulator === undefined) {
    index = 0
    value = arr[0]
  } else {
    index = -1
    value = accumulator
  }

  while (++index < length) {
    value = fn(value, arr[index])
  }

  return value
}

function lift2(f, a, b) {
  return ap(b, map(a, function(a) {
    return function(b) {
      return f(a, b)
    }
  }))
}

function append(a, b) {
  return a.concat(b)
}

function map(m, f) {
  if(m.map) return m.map(f)
  return flatMap(m, function(a) {
    return point(m, f(a))
  })
}

function ap(a, f) {
  if(a.ap) return a.ap(f)
  return flatMap(f, function(f) {
    return map(a, f)
  })
}

function flatMap(m, f) {
  return m.chain ? m.chain(f) : m.then(f)
}

function curry(f, a) {
  return f.bind.apply(f, [null, a])
}

function comp(f, g) {
  return function (x) {
    return f(g(x))
  }
}

function concatMap(xs, fn) {
  var res = []
  xs.forEach(function (x) {
    var val = fn(x)
    if (Array.isArray(val)) {
      res.push.apply(res, val)
    } else {
      res.push(val)
    }
  })
  return res
}
