--[[

Functional programming library

Created by chrisinajar

]]


--[[
partial (fn, ...)

fn - any function reference
... - parameters for partial application

Returns a version of fn which when executed will have `...` parameters passed in to it first

```
function add (a, b)
  return a + b
end

local addToFive = partial(add, 5)

print(addToFive(3)) -- prints 8
```

]]
function partial(fn, arg1, ...)
  if select("#", ...) == 0 then
    return function (...)
      return fn(arg1, ...)
    end
  else
    return partial(partial(fn, arg1), ...)
  end
end

--[[

forEach(myArray, function (value)
  print(value)
end)

]]
-- function forEach (ar, fn)
--   for i,v in ipairs(ar) do
--     fn(v, i)
--   end
--   return ar
-- end


--[[

myArray = map(myArray, function (value)
  return value .. 'boo'
end)

]]
-- function map (ar, fn)
--   local newAr = {}
--   for i,v in ipairs(ar) do
--     newAr[i] = fn(v, i)
--   end
--   return newAr
-- end

function after (count, callback)
  local result = {}
  local function done(...)
    table.insert(result, {...})
    count = count - 1
    if count == 0 then
      callback(result)
    end
  end
  return done
end

-- Returns a function that calls methodName on any given object, passing the object
-- as the first argument along with any additional arguments given to CallMethod
function CallMethod(methodName, ...)
  local caller
  -- Since this is meant to call C++, it has to be very specific about the number of arguments.
  -- Using unpack(args) unconditionally would result in an extra nil argument
  -- if no argument was given to CallMethod
  if select('#', ...) > 0 then
    local args = {...}
    caller = function (object)
      return object[methodName](object, unpack(args))
    end
  else
    caller = function (object)
      return object[methodName](object)
    end
  end
  return caller
end

-- Takes a set of functions and returns a fn that is the composition of those fns.
-- The returned fn takes a variable number of args, applies the rightmost of fns to the args,
-- the next fn (right-to-left) to the result, etc.
function compose(f, ...)
  local function compose1(f1, g)
    return function (...)
      return f1(g(...))
    end
  end
  return reduce(compose1, f, {...})
end
