Y combinator

Y combinator
You are encouraged to solve this task according to the task description, using any language you may know.

In strict functional programming and the lambda calculus, functions (lambda expressions) don't have state and are only allowed to refer to arguments of enclosing functions.
This rules out the usual definition of a recursive function wherein a function is associated with the state of a variable and this variable's state is used in the body of the function.

The Y combinator is itself a stateless function that, when applied to another stateless function, returns a recursive version of the function. The Y combinator is the simplest of the class of such functions, called fixed-point combinators.

Task

Define the stateless Y combinator and use it to compute factorials and Fibonacci numbers from other stateless functions or lambda expressions.

AppleScript is not terribly "functional" friendly. It can, however, support the Y combinator.

AppleScript does not have anonymous functions, but it does have anonymous objects. The code below implements the latter with the former (using a handler (i.e. function) named 'lambda' in each anonymous object).

Unfortunately, an anonymous object can only be created in its own statement ('script'...'end script' can not be in an expression). Thus, we have to apply Y to the automatic 'result' variable that holds the value of the previous statement.

The identifier used for Y uses "pipe quoting" to make it obviously distinct from the y used inside the definition.

C doesn't have first class functions, so we demote everything to second class to match.

#include <stdio.h>#include <stdlib.h>

/* func: our one and only data type; it holds either a pointer to a function call, or an integer. Also carry a func pointer to a potential parameter, to simulate closure */typedefstruct func_t *func;typedefstruct func_t { func (*func)(func, func), _;int num;} func_t;

This version was translated from the one in The Little Schemer by Friedman and Felleisen. The use of the variable name le is due to the fact that the authors derive Y from an ordinary recursive length function.

In J, functions cannot take functions of the same type as arguments. In other words, verbs cannot take verbs and adverbs or conjunctions cannot take adverbs or conjunctions. However, the Y combinator can be implemented indirectly using, for example, the linear representations of verbs. (Y becomes a wrapper which takes a verb as an argument and serializes it, and the underlying self referring system interprets the serialized representation of a verb as the corresponding verb):

Another approach uses a J gerund as a "lambda" which can accept a single argument, and `:6 to mark a value which would correspond to the first element of an evaluated list in a lisp-like language.

(Multiple argument lambdas are handled by generating and evaluating an appropriate sequence of these lambdas -- in other words, (lambda (x y z) ...) is implemented as (lambda (x) (lambda (y) (lambda (z) ...))) and that particular example would be used as (((example X) Y) Z)) -- or, using J's syntax, that particular example would be used as: ((example`:6 X)`:6 Y)`:6 Z -- but we can also define a word with the value `:6 for a hypothetical slight increase in clarity.

The following code modifies the Function interface such that multiple parameters (via varargs) are supported, simplifies the y function considerably, and the Ackermann function has been included in this implementation (mostly because both D and PicoLisp include it in their own implementations).

The standard version of the Y combinator does not use lexically bound local variables (or any local variables at all), which necessitates adding a wrapper function and some code duplication - the remaining locale variables are only there to make the relationship to the previous implementation more explicit:

Since ECMAScript 2015 (ES6) just reached final draft, there are new ways to encode the applicative order Y combinator.
These use the new fat arrow function expression syntax, and are made to allow functions of more than one argument through the use of new rest parameters syntax and the corresponding new spread operator syntax. Also showcases new default parameter value syntax:

let Y=// Except for the η-abstraction necessary for applicative order languages, this is the formal Y combinator. f=>((g=>(f((...x)=>g(g)(...x))))(g=>(f((...x)=>g(g)(...x))))), Y2=// Using β-abstraction to eliminate code repetition. f=>((f=>f(f))(g=>(f((...x)=>g(g)(...x))))), Y3=// Using β-abstraction to separate out the self application combinator δ.((δ=>f=>δ(g=>(f((...x)=>g(g)(...x)))))((f=>f(f)))), fix=// β/η-equivalent fix point combinator. Easier to convert to memoise than the Y combinator.(((f)=>(g)=>(h)=>(f(h)(g(h))))// The Substitute combinator out of SKI calculus((f)=>(g)=>(...x)=>(f(g(g)))(...x))// S((S(KS)K)S(S(KS)K))(KI)((f)=>(g)=>(...x)=>(f(g(g)))(...x))), fix2=// β/η-converted form of fix above into a more compact form f=>(f=>f(f))(g=>(...x)=>f(g(g))(...x)), opentailfact=// Open version of the tail call variant of the factorial function fact=>(n,m=1)=>n<2?m:fact(n-1,n*m); tailfact=// Tail call version of factorial function Y(opentailfact);

let polyfix=// A version that takes an array instead of multiple arguments would simply use l instead of (...l) for parameter(...l)=>((f=>f(f))(g=>l.map(f=>(...x)=>f(...g(g))(...x)))),[even,odd]=// The new destructive assignment syntax for arrays polyfix((even,odd)=>n=>(n===0)||odd(n-1),(even,odd)=>n=>(n!==0)&&even(n-1));

As of 2.8.0, GP cannot make general self-references in closures declared inline, so the Y combinator is required to implement these functions recursively in that environment, e.g., for use in parallel processing.

Both the op and do operators are a syntactic sugar for currying, in two different flavors. The forms within do that are symbols are evaluated in the normal Lisp-2 style and the first symbol can be an operator. Under op, any forms that are symbols are evaluated in the Lisp-2 style, and the first form is expected to evaluate to a function. The name do stems from the fact that the operator is used for currying over special forms like if in the above example, where there is evaluation control. Operators can have side effects: they can "do" something. Consider (do set a @1) which yields a function of one argument which assigns that argument to a.

The compounded @@... notation allows for inner functions to refer to outer parameters, when the notation is nested. Consider

(op foo @1 (op bar @2 @@2))

. Here the @2 refers to the second argument of the anonymous function denoted by the inner op. The @@2 refers to the second argument of the outer op.

The standard y combinator doesn't work in Ursala due to eager
evaluation, but an alternative is easily defined as shown

(r "f") "x" = "f"("f","x")my_fix "h" = r ("f","x"). ("h" r "f") "x"

or by this shorter expression for the same thing in point free form.

my_fix = //~&R+ ^|H\~&+ ; //~&R

Normally you'd like to define a function recursively by writing
f=h(f){\displaystyle f=h(f)}, where h(f){\displaystyle h(f)} is just the body of the
function with recursive calls to f{\displaystyle f} in it. With a fixed point
combinator such as my_fix as defined above, you do almost the same thing, except it's f={\displaystyle f=}my_fix
"f".h{\displaystyle h}("f"), where the dot represents lambda abstraction and the
quotes signify a dummy variable. Using this
method, the definition of the factorial function becomes

#import nat

fact = my_fix "f". ~&?\1! product^/~& "f"+ predecessor

To make it easier, the compiler has a directive to let you install
your own fixed point combinator for it to use, which looks like
this,

#fix my_fix

with your choice of function to be used in place of my_fix.
Having done that, you may express recursive functions per convention by circular definitions,
as in this example of a Fibonacci function.

fib = {0,1}?</1! sum+ fib~~+ predecessor^~/~& predecessor

Note that this way is only syntactic sugar for the for explicit way
shown above. Without a fixed point combinator given in the #fix
directive, this definition of fib
would not have compiled. (Ursala allows user defined fixed point
combinators because they're good for other things besides
functions.)
To confirm that all this works, here is a test program applying
both of the functions defined above to the numbers from 1 to 8.

#cast %nLW

examples = (fact* <1,2,3,4,5,6,7,8>,fib* <1,2,3,4,5,6,7,8>)

Output:

(
<1,2,6,24,120,720,5040,40320>,
<1,2,3,5,8,13,21,34>)

The fixed point combinator defined above is theoretically correct
but inefficient and limited to first order functions,
whereas the standard distribution includes a library (sol)
providing a hierarchy of fixed point combinators
suitable for production use and with higher order functions.
A more efficient alternative implementation of my_fix
would be general_function_fixer 0
(with 0 signifying the lowest order of fixed point combinators),
or if that's too easy, then by this definition.

#import sol

#fix general_function_fixer 1

my_fix "h" = "h" my_fix "h"

Note that this equation is solved using the next fixed point combinator in the hierarchy.

Functions don't get to look outside of their scope so data in enclosing scopes needs to be bound to a function, the fp (function application/cheap currying) method does this. 'wrap is syntactic sugar for fp.