### Functions

[
  Datatype functions
  Funcon   function
  Funcon   apply
  Funcon   supply
  Funcon   compose
  Funcon   uncurry
  Funcon   curry
  Funcon   partial-apply
]


Meta-variables
  T, T', T1, T2 <: values


Datatype
  functions(T,T') ::= function(A:abstractions(T=>T'))
/*
  `functions(T, T')` consists of abstractions whose bodies may depend on
  a given value of type `T`, and whose executions normally compute values 
  of type `T'`.
  `function(abstraction(X))` evaluates to a function with dynamic bindings,
  `function(closure(X))` computes a function with static bindings.
*/


Funcon
  apply(_:functions(T, T'), _:T) : =>T'
/*
  `apply(F, V)` applies the function `F` to the argument value `V`.
  This corresponds to call by value; using thunks as argument values
  corresponds to call by name. Moreover, using tuples as argument values 
  corresponds to application to multiple arguments.
*/
Rule
  apply(function(abstraction(X)), V:T) ~> give(V, X)


Funcon
  supply(_:functions(T, T'), _:T) : =>thunks(T')
/*
  `supply(F, V)` determines the argument value of a function application,
  but returns a thunk that defers executing the body of the function.
*/
Rule
  supply(function(abstraction(X)), V:T) ~> thunk(abstraction(give(V, X)))


Funcon
  compose(_:functions(T2, T'), _:functions(T1, T2)) : =>functions(T1, T')
/*
  `compose(F2, F1)` returns the function that applies `F1` to its argument,
  then applies `F2` to the result of `F1`.
*/
Rule
  compose(function(abstraction(Y)), function(abstraction(X)))
    ~> function(abstraction(give(X, Y)))


Funcon
  uncurry(F:functions(T1, functions(T2, T'))) : 
    =>functions(tuples(T1, T2), T')
   ~> function(abstraction(
         apply(
           apply(F, checked index(1, tuple-elements given)),
           checked index(2, tuple-elements given))))
/*
  `uncurry(F)` takes a curried function `F` and returns a function that takes
  a pair of arguments..
*/


Funcon
  curry(F:functions(tuples(T1, T2), T')) : =>functions(T1, functions(T2, T'))
    ~> function(abstraction(partial-apply(F, given)))
/*
  `curry(F)` takes a function `F` that takes a pair of arguments, and returns
  the corresponding 'curried' function.
*/


Funcon
  partial-apply(F:functions(tuples(T1, T2), T'), V:T1) : =>functions(T2, T')
    ~> function(abstraction(apply(F,tuple(V,given))))
/*
  `partial-apply(F, V)` takes a function `F` that takes a pair of arguments, 
  and determines the first argument, returning a function of the second 
  argument.
*/
