async function apply(proc, args, input, environment) {
        var result;
        result = await applyInner(proc, args, input, environment);
        while(isLambda(result) && result.thunk === true) {
            // trampoline loop - this gets invoked as a result of tail-call optimization
            // the function returned a tail-call thunk
            // unpack it, evaluate its arguments, and apply the tail call
            var next = await evaluate(result.body.procedure, result.input, result.environment);
            if(result.body.procedure.type === 'variable') {
                next.token = result.body.procedure.value;
            }
            next.position = result.body.procedure.position;
            var evaluatedArgs = [];
            for(var ii = 0; ii < result.body.arguments.length; ii++) {
                evaluatedArgs.push(await evaluate(result.body.arguments[ii], result.input, result.environment));
            }

            result = await applyInner(next, evaluatedArgs, input, environment);
        }
        return result;
    }
Apply procedure or function
@param {Object} proc - Procedure
@param {Array} args - Arguments
@param {Object} input - input
@param {Object} environment - environment
@returns {*} Result of procedure