This is a Jupyter Notebook document introducing features, usage, and behaviour of calchylus
module.
Calchylus module is a Lambda calculus evaluator written with Hy by Marko Manninen, 2017.
For full documentation see: http://calchylus.readthedocs.io
First, let us see, what is the environment I'm running these examples:
In [1]:
(import hy sys)
(print (% "Hy version: %s" hy.__version__))
(print (% "Python %s" sys.version))
In [2]:
(import calchylus)
(print (% "Hy version: %s" calchylus.__version__))
Finally, import calchylus
module and initialize calchylus
system by one of the with- macros:
In [3]:
(require (calchylus.lambdas [*]))
(with-alpha-conversion-and-macros 𝜆 \.)
Out[3]:
For printing mathematical symbols nicely in the Jupyter Notebook document, I have defined some helper functions to the module. Note that these are not necessary for the Lambda calculus evaluation itself:
#§
pretty print Lambda expression as it is#¤
pretty print Lambda expression and beta-reduced form of the expressionlatex-output
print quoted expressions (useful, when working with Lambda expression as a Hy variable)Lambda expressions are formed by three components: variables, function abstractions, and function applications.
Variable is any symbol or Lambda expression that will acts as a parameter for the function. In pure untyped Lambda calculus, symbols do not have any special meaning or type by they own. Meaning is extracted from the functional application. Untyped systems have some advantages related to universality but that usually comes with a performance penalty.
Calchylus is far from being beneficial for heavy arithmetic processing. Rather, it is meant for inspecting theoretical foundations of arithmetics, programming languages, and logic.
Function abstraction consists of:
𝜆
or λ
.
or a middle dot ·
()
or sometimes []
Due to the fact, that a dot belongs to the reserved symbols in Hy language, a dot will be escaped in the following examples like this: `\.`
Traditionally, Lambda function must have one and only one argument. At first, this seems very restricting, but in reality with a technique called currying multiary functions can be constructed. In other words, any number of arguments can be transformed to the single argument style expressions, and vice versa.
The body of the function will be the first expression after the delimitter. Body expression can be a parenthezised set of symbols or just a single symbol, where parentheses or a space before and after the symbol indicates the start and the end of the body.
Function application is a function abstraction together with an applicable variable / parameter. In many programming languages this is same as defining an anonymous function and applying it to the given argument(s).
Let us start the lengthy introduction to the Lambda functions and define the function abstraction first. The following function is one of the simplest ones. It is called the identity function and has one argument x
and a simple body x
:
In [4]:
(𝜆 x \. x)
Out[4]:
In calchylus
we need to have parentheses around the expression due to LISP like language specification of Hy. In common Lambda notation the outermost parentheses are often removed and the identity function is denoted similar to this:
In [5]:
#§(𝜆 x \. x)
Out[5]:
In native Hy, the identity function is constructed by an anonymous function accordingly:
In [6]:
(fn [x] x)
Out[6]:
We realize easily, that the difference between the anonymous function in Hy and the function abstraction in Lambda calculus is rather semantical so far. Lambda function indicator is fn
(was actually the word lambda
in the earlier versions of Hy). Arguments are inside brackets, because a function definition in Hy supports more complex argument initialization compared to the pure Lambda calculus. The rest of the expression is the body of the function.
The function abstraction, or you could call it a function definition, is waiting for one argument. And then, if an argument is given, the identity function simply "returns" the very same argument value intact.
In the function application, the identity function is applied to the given argument. You could say that the function abstraction is functioning only in the function application.
There are actually two possible ways to pass parameters to the function in calchylus
module.
Let us see, how the function application looks like in the first case:
In [7]:
#§((𝜆 x \. x) y)
Out[7]:
In case 2., the expression looks like this:
In [8]:
#§(𝜆 x \. x y)
Out[8]:
Again, we can see a close resemblance to the native anonymous function in Hy. One could transform the above Lambda expression to the Hy code like this:
((fn [x] x) 'y)
Furthermore, there is a close resemblance with a Lambda calculus notation and a mathematical notation for the function definition.
For a demonstration, let us change the function identifier and the delimitter with calchylus
module initializer macro and try the function abstraction again:
In [9]:
(with-alpha-conversion-and-macros ƒ: ↦)
#§(ƒ: x ↦ x)
Out[9]:
We are pretty close to the formal mathematical representation#Notation) of the function. We could even use a special escape character as a function identifier to leave out the from the visual presentation of the Lambda expression in the Jupyter Notebook environment:
In [10]:
(with-alpha-conversion-and-macros \ ↦)
#§(\ x ↦ x)
Out[10]:
Calchylus module is not just for presenting Lambda calculus notation in different forms, but also for evaluation (beta-reduction) of the Lambda expressions. Evaluation process has some principles that are good to know:
Consequence of this is that:
These principles all together are called call-by-value strategy or applicative order which is an important concept. It leads to the lazy evaluation contra to the most of the programming languages that eagerly evaluates parameters first before sending them to the function.
The last principle we have already seen in action is:
Otherwise we have a result where all arguments and parameters of the expression has been substituted.
Let us first see, how the identity function is applied to the parameter in a native Hy way:
In [11]:
((fn [x] x) 'y)
Out[11]:
Note that there already is the identity function that you could also use in Hy: `(identity 'y)`
Now, compare that to the corresponding Lambda expression in the following example:
In [12]:
(with-alpha-conversion-and-macros λ \.)
#¤(λ x \. x y)
Out[12]:
In [13]:
#¤(IDENT y)
Out[13]:
We see that the argument name is a
in this example, but the result is same (y
) with the previous example. In Lambda calculus λ x . x
and λ a . a
are regarded as equal forms. The name of the argument can be anything because it is consistently used and substituted in the process of evaluation.
So far we have kept everything as simple as possible. The identity function is good for introducing basics elements of the Lambda calculus. Althought the identity function looks too simple for any real usage other than demonstration, or conceptual talk, it is good to remember that the predecessor function (PRED
) fully utilizes it.
Before going deeper to the arithmetics, let's introduce one exciting property of the Lambda calculus, namely, higher-order functions.
When we really start to play with functions, there comes a moment, where you probably happened to pass a function to the function, or at least you wondered what happens, if you do so.
So, what happens when you apply the identity function to the identity function? In calchylus
, we can investigate it with the next piece of code:
In [14]:
#¤(IDENT IDENT)
Out[14]:
If the identity function is given to the identity function, it returns (or beta-reduces to) the identity function. Returned function is an abstraction in this case and can't be reduced further. On the other hand, this means that we could pass a parameter to the result, which is exactly what we do next:
In [15]:
#¤((IDENT IDENT) y)
Out[15]:
With this simple maneuver we have moved to the realms of the higher-order functions. It means ability to send functions inside functions (apply functions to functions), or define nested functions, sometimes called functors.
Let us do one more thing with the identity function before moving on to the concepts of constants, scopes, and variable shadowing.
In the next example, rather than passing a function to the function, we construct the body of the function to be the identity function per se:
In [16]:
#¤((λ b \. IDENT b) y)
Out[16]:
Again the result is y
, maybe after a little bit twisting first.
This is a good place to take a breath before diving deeper to the variable substitution topic.
Variable is an object, which value is changing. Constant is a value, that should keep static and permanent. In Lambda calculus it is possible to imitate constants by function notation. We simply take one parameter for the function argument, but don't use it at all. Instead a static body is returned. Say the function has an argument foo
and the body bar
in the following way:
In [17]:
(λ foo \. bar)
Out[17]:
If any parameter, like baz
, is passed to the function, it should stubbornly return the body bar
:
In [18]:
(λ foo \. bar baz)
Out[18]:
In calchylus
module we have a CONST
macro to create constants. In practice we need to specify the argument name and the body separately because we can't have both using same symbol:
In [19]:
(CONST foo bar)
Out[19]:
If the argument anme and the body is same symbol, then result is an identity function! Let's try once more the evaluated version of the constant with parameter baz
:
In [20]:
#¤(CONST foo bar baz)
Out[20]:
LET
and LET*
We have talked about variables, arguments, parameters, and constants. These terms are sometimes used interchangeably depending on what language level they are referring. calchylus
module implementation, for example, has to protect argument names with automated variable name generation.
But if we talk in the level of calchylus
module interface rather than implementation, with the variable term we refer to the LET
and LET*
constructors. Once again, these are macro shorthands or abbreviations for constructing certain Lambda expressions, namely variables. This time I want to show, althougt awfully late, the famous "Hello World" example:
In [21]:
#¤(LET x "World" (Hello, x !))
Out[21]:
Here (Hello, x !)
is the function body. x
is the argument name and World
is the parameter. As a combination, because x
is meant for a changing value, we have defined a variable x
.
...
It has become obvious now that the main operation in Lambda calculus is a substitution of argument names in the function body with the parameters given to the function application. Our examples has been easy so far. Natural question is, how substitution works when there are many nested functions, possibly with same argument names?
Well, every function also works as a scope to the arguments inside the function. So, every argument that is inside the body will get substituted until there is another function with same argument name. In that case, a function works as a protector and prevents for further substitution.
Let us see two exampless for this.
In [22]:
#¤(λ x \. (λ y \. x) z)
Out[22]:
Note how x
in the inner Lambda expression (λ y . x)
got substituted by z
.
In [23]:
#¤(λ x \. (λ x \. x) z)
Out[23]:
In [24]:
#¤(λ x \. (λ y \. (x y)) y)
Out[24]:
So far so good. Or is it really? What happens or should happen, if you pass a second parameter to the function abstraction we have left?
We can easily investigate it with calchylus
module:
In [25]:
#¤(λ x \. (λ y \. (x y)) y z)
Out[25]:
Are you surprised, that the result is y z
?
The standard convention in Lambda calculus is that argument names should be stable when they have been bound to the variables. When the first argument x
has been substituted to the sub terms in the body with y
, value of y
should not change anymore.
Stableness is achieved by a process called alpha conversion which is happening behind the curtain in calchylus
module. Argument names are changed momentarily to generated unique names so that they cannot overlap.
In calchylus
module, however, we can switch on and off the alpha conversion phase and investigate, what happens if names get collided:
In [26]:
(with-alpha-conversion-nor-macros λ \.)
#¤(λ x \. (λ y \. (x y)) y z)
Out[26]:
This is set purely for inspection and not recommended as a real evaluation strategy. It can be shown that only with unique selection of names, real arithmetics can be achieved in Lambda calculus. Other way around the problem would be to introduce compile or run time exception system and tell the programmer, that there is a collision of variable names. That could indeed effect positively to the evaluation time of the program on the other hand, because in the final program, there would be no need for the alpha conversion!
For curious minds, this is what happens inside the module on the alpha conversion process:
In [27]:
(alpha-conversion '(λ x \. (λ y \. (x y)) y z))
Out[27]:
This is set purely for inspection and not recommended as a real evaluation strategy. It can be shown that only with unique selection of names, real arithmetics can be achieved in Lambda calculus. Other way around the problem would be to introduce compile or run time exception system and tell the programmer, that there is a collision of variable names. That could indeed effect positively to the evaluation time of the program on the other hand, because in the final program, there would be no need for the alpha conversion!
Earlier we created a variable with LET
macro. I didn't note yet that it is possible to construct several variables at once with that macro. Variable names and values must come in pairs and the last argument of the macro should be the body, where arguments are used.
Using this feature, I want to present other possible way of twisting variables. Here the first variable a
is re-used in the second variable b
:
In [28]:
(with-alpha-conversion-and-macros λ \.)
#¤(LET a c b a (a b))
Out[28]:
It seems that we are not quite getting it. LET
macro made the Lambda expression (λ a b . (a b) c a)
but someone might have expected that the value of variable b
is same as the value of variable a
. But we only got the name of the symbol a
for b
. It occures that it is not possible to use the value of arguments in the other following arguments.
But it is with other variable constructor, namely LET*
macro. It works same way than LET
but allows to use values of arguments in the other arguments, rather than only in the body of the function.
In [29]:
#¤(LET* a c b a (a b))
Out[29]:
Notice, that the Lambda form is also a bit different in structure. We have actually shown that it is possible to present variable setters purely functional way. This may become as a small surprise for many programmer. We have created something that looks like causing all hatred side effects but is rather nullipotent in its true nature.
We can even do better and tremble the foundations and concepts of two different paradigms, the functional and imperative ones.
In calchylus
there is a DO
shorthand to alter the very same functionality that we already saw in LET
and LET*
. DO
practically lets you to define variables with LET
's and leave the final function body "outside" the LET
macros.
By the next example we shall take one step closer to the next big area, where Lambda calculus is still studied and where Lambda calculus was primarily and originally intended to be used, as a universal mathematical language:
In [30]:
#¤
(DO
(LET a 2)
(LET b 3)
(a ^ b + b ^ a))
Out[30]:
If Lambda calculus is so great, why I am not showing the final result of the equation? The reason is, that symbols given, even they look like numbers (2
, 3
) and the addition (+
), have no real meaning in Lambda calculus. They are just symbols to be substituted to the places where arguments are in the body.
In Lambda calculus we have to and try to construct everything from the three basic principles and substitution convention. So how should we present numbers, or the more basic boolean values then?
Curry Howard barber tale...
In [31]:
;(with-alpha-conversion-and-macros ƒ ↦)
;(setv c '(LET* x 2 y 2 a b b c (a ^ x b ^ y)))
;(macro-print c :result True)
In [32]:
(TRUE)
Out[32]:
In [ ]:
In [33]:
;(with-alpha-conversion-and-macros L ,)
;(APP (L a , (L b , e a) 1))
;(print (APP (((L a , (L b , e)) 1) 2)))
In [ ]:
In [34]:
; to be used like: #ℕ17 / #ℕ 17 -> (NUM 17) -> (L x , (L y , (x (x ..... (x y)))))
;(defsharp ℕ [n] `(NUM ~n))
;#ℕ 17
In [35]:
(with-macros L ,)
(L a , (L b , (L c , (a b c) b) a) 1)
Out[35]:
In [36]:
(with-alpha-conversion-and-macros L ,)
(LET* a 1 b a c b (a b c))
Out[36]:
In [37]:
(APP (x ((L x , x) y)))
Out[37]:
In [38]:
(L x , (L y , (L z , (x y z))) 1 2 3)
Out[38]:
In [39]:
(L x y , (L z a , (x y z a)) 1 2 3 4)
Out[39]:
In [40]:
;(macro-print '(SUMMATION THREE) 'small)
In [41]:
;(macro-print '(DO (LET a (𝜆 a b · a)) (LET b (𝜆 a b · b)) (a b)))
In [42]:
(APP (𝜆 a b · a (𝜆 a b · b)))
Out[42]:
In [43]:
(with-alpha-conversion-and-macros 𝜆 ·)
(DO (LET a (𝜆 a b · a)) (LET b (𝜆 a b · b)) (a b))
Out[43]:
In [44]:
(𝜆 a p · (p a) (𝜆 a b · a) (𝜆 a · (𝜆 b p · (p b) (𝜆 a b · b) (𝜆 b · (a b)))))
Out[44]:
In [45]:
;(macro-print '(DO (LET a TRUE) (LET b FALSE) (a b)))
In [46]:
(DO (LET a TRUE) (LET b FALSE) (a b))
Out[46]:
In [47]:
(DO (LET a TRUE b FALSE) (PAIR a b))
Out[47]:
Lambda calculus language evaluator is written for easy to use, but configurable Hy macros. Two main macros are:
Ÿ
System is initialized by one of the four start up macros:
with-macros
with-alpha-conversion
with-alpha-conversion-nor-macros
with-alpha-conversion-and-macros
with-alpha-conversion-and-macros
is recommended way of using the module, because it will provide automatic alpha conversion for variable names and import useful shorthands for several dozens of well known custom lambda forms.
There are two parameters you can use on initialization:
Usually we see the notation 𝜆x.x
for lambda expressions where 𝜆
is the character identifier for the lambda function and .
is the separator between lambda function arguments and the body. In the case of 𝜆x.x
the first x
before the dot is the argument name and the second x
after the dot, is the body of the lambda function.
Lambda application in the simplest case could be: (𝜆x.x 1)
where 1
is the value of the argument x
. Evaluation, or better called beta-reduction is the process of substituting all instances of x
in the body with the 1
in this case.
So what we want to do next, is the initialization of the module with wanted lambda character and separation marker, let them be: 𝜆
and ·
in our case:
In [48]:
(with-alpha-conversion-nor-macros 𝜆 ·)
Out[48]:
Because we are using simple examples in the following parts, we used with-alpha-conversion-nor-macros
initializer, that doesn't provide alpha conversion nor custom macros.
Now we are ready to construct the first lambda expression in Hy. We will call this the abstration or definition of the function:
In [49]:
(𝜆 x · x)
Out[49]:
Evaluation of the lambda form gave us back the same expression because we didn't provide any parameters to the lambda function. This is an anonymous function in common programming languages that waits for the caller. If parameter is provided, for example:
In [50]:
(𝜆 x · x 1)
Out[50]:
then result would be as simple returning the same value. This is one of the most basic and the simplest forms, which is called identity function. It returns back what is provided as an argument. Note that the number 1 should not be regarded as a number however. It is just a character that has no actual meaning is the lambda calculus. This perhaps the first strange part you will face with lambda calculus. Different distinct forms and expressions have a meaning per se, not symbols that are used in the forms. For example: (𝜆 x · x)
is same as (𝜆 y · y)
because they will provide similar effect when argument is passed to the function. Take a look at this and compare to the previous code:
In [51]:
(𝜆 y · y 1)
Out[51]:
Outcome is same. But is you would make it: (𝜆 y · x 1)
then result would be different:
In [52]:
(𝜆 y · x 1)
Out[52]:
In this case argument y
has a value 1
, but y is not used on the function body. Rather x
is the body and because there is no substitution to the x
, returned value is just the original x
. We would call this a constant form because it will take one argument, but does nothing with it and rather gives a static value back.
Normally we can provide only one argument in lambda function. If several arguments are needed, then they can be nested:
In [53]:
(𝜆 x · (𝜆 y · (x y)))
Out[53]:
Arguments are passed, or lambda technically speaking curryed to the nested levels of the expression. In a formal definition od lambda calculus, only one and one only argument can be passed to the function. In practice it is more compact to write same thing as multiary function:
In [54]:
(𝜆 x y · (x y))
Out[54]:
Small comparison below demonstrated they both produce same output:
In [55]:
(, (𝜆 x · (𝜆 y · (x y)) 1 2)
(𝜆 x y · (x y) 1 2)
(= (𝜆 x · (𝜆 y · (x y)) 1 2)
(𝜆 x y · (x y) 1 2)))
Out[55]:
Originally even not as a part of the lambda calculus, it is possible to declare functions without arguments in calchylus module:
In [56]:
(𝜆 · x)
Out[56]:
In [57]:
;(macro-print '(APP x))
This will lead us to the discussion about the basic forms and provided macros in calchylus.
These are the usual Lambda forms from textbooks provided as Hy macros. Macros are created for shorthands. They are used to make Lambda terms shorter and more comprehensible:
CONST
IDENT
LET
LET*
TRUE
FALSE
REPLACE
PAIR
HEAD
TAIL
FIRST
SECOND
NIL
NIL?
ZERO
ZERO?
NUM
ONE
TWO
THREE
FOUR
FIVE
SIX
SEVEN
EIGHT
NINE
TEN
COND
AND
OR
NOT
XOR
EQ?
LEQ?
ADD
PRED
SUCC
EXP
SUM
PROD
SUB
SELF
YCOMB
SUMMATION
FACTORIAL
FIBONACCI
Definition:
``(defmacro CONST [&rest args] `(L , ~@args))``
Constant form doesn't take any arguments, but it will return given body as a "static" value. In calchylus
unnamed constant is a lambda form without any arguments, that is for example: (𝜆 · x)
. It will be evaluated directly because there are no bound variables in it. Macro for this form is CONST
and it will take the body of the lambda expression as an argument:
Also, if lambda abstraction is declared as a constant body, it will be returned as it is:
In [58]:
(CONST t (𝜆 x · x) 1 2)
Out[58]:
However, if function application is given, then function will be evaluated and result of it returned as a value of the constant:
In [59]:
(CONST t (𝜆 x · x))
Out[59]:
In [60]:
(CONST t (𝜆 x · x 1))
Out[60]:
If more arguments are passed to the lambda function, than it is requires, also free arguments will be returned on the final output. In this case y
is a free argument and returned as a left over. This happens not only in constants but on other function forms too:
In [61]:
(𝜆 · x y)
Out[61]:
Definition:
``(defmacro IDENT [&rest args] `(L a , a ~@args))``
The next basic form is the identity function. This function will return the given argument as it is. The formal definition of the identity function is: (L x , x)
. The custom macro provided by calchhylus module is IDENT
and it is used following way:
In [62]:
(IDENT x)
Out[62]:
Given extra argument, will return both the mandatory argument and latter ones in a parentheses:
In [63]:
(IDENT x y z)
Out[63]:
If no arguments are provided, then abstraction is returned:
In [64]:
(IDENT)
Out[64]:
Definition:
``(defmacro TRUE [&rest args] `(L a b , a ~@args))``
``(defmacro FALSE [&rest args] `(L a b , b ~@args))``
These two forms are also very fundamental for lambda calculus. Boolean forms takes two arguments, but return only the other.
True returns the first and omits the second argument. False returns the second and omits the first argument. So they are opposites as one would expect. This switch functionality will give the logical background for the whole calculus. Upon these two primitive form, most of the other forms are based.
In calchylus module, macros are provided accordingly: TRUE and FALSE. But let us first take a closer look to both forms. In true form, if arguments are passed to the function, the first argument will be returned as it is:
In [65]:
(𝜆 a b · a 1 2)
Out[65]:
In false form, the second argument is returned as it is:
In [66]:
(𝜆 a b · b 1 2)
Out[66]:
In both cases the other argument is left out as a useless option.
Definition:
`` ; logic (defmacro COND [&rest args] `(L p a b , (p a b) ~@args)) (defmacro AND [&rest args] `(L a b , (a b FALSE) ~@args)) ;(defmacro AND2 [&rest args] `(L a b , (a b a) ~@args)) (defmacro OR [&rest args] `(L a b , (a TRUE b) ~@args)) ;(defmacro OR2 [&rest args] `(L a b , (a a b) ~@args)) (defmacro NOT [&rest args] `(L p , (p FALSE TRUE) ~@args)) ;(defmacro NOT2 [&rest args] `(L p a b , (p b a) ~@args))? (defmacro XOR [&rest args] `(L a b , (a (NOT b) b) ~@args)) ``
Definition:
`` ; lists (defmacro NIL [&rest args] `(FALSE ~@args)) (defmacro PAIR [&rest args] `(L a b s , (s a b) ~@args)) (defmacro FIRST [&rest args] `(TRUE ~@args)) (defmacro SECOND [&rest args] `(FALSE ~@args)) (defmacro HEAD [&rest args] `(L s , (s TRUE) ~@args)) (defmacro TAIL [&rest args] `(L s , (s FALSE) ~@args)) (defmacro NIL? [&rest args] `(L s , (s FALSE TRUE) ~@args)) ``
This form takes one argument, but does nothing with it. One can think it like an omitter, but also replacer because the next possible argument will be returned instead of the first. This is also very close to the idea of the boolean false form:
In [67]:
(FALSE 1 2)
Out[67]:
In [68]:
(TRUE 2 d)
Out[68]:
In [69]:
(𝜆 aa · aa (𝜆 ab · (aa ab)) ac)
Out[69]:
In [70]:
(with-alpha-conversion-and-macros L ,)
(DO (LET a TRUE b FALSE)
(LET selector
(DO (LET* a selector b a) b))
(selector a b))
Out[70]:
In [71]:
#Ÿ(n (n (L x , x 1)))
Out[71]:
In [72]:
(SUMMATION THREE SUCC THREE a b)
Out[72]:
In [73]:
(SUCC TWO TWO a )
Out[73]:
In [74]:
(FACTORIAL FIVE)
Out[74]:
In [75]:
(FIBONACCI FIVE)
Out[75]:
In [76]:
(FIBONACCI)
Out[76]:
In [77]:
(with-alpha-conversion-and-macros L ,)
Out[77]:
In [78]:
(SELF (L f n , (COND (ZERO? n) ONE (PROD (f f (PRED n)) n))) THREE x y) ; 1*2*3 = 6
Out[78]:
In [79]:
; SELF
(L f n , (f f n)
; function
(L f n ,
; if n is ZERO?
(L f , (f (L a , (L a b , b)) (L a b , a)) n
; then ONE
(L x y , (x y))
; else PROD
(L m n x y , (m (n x) y)
; function PRED n
(f f (L n x y , (n (L g h , (h (g x))) (L x , y) (L a , a)) n))
; times n
n)))
; THREE
(L x y , (x (x (x y)))) x y) ; 1*2*3=6
Out[79]:
In [80]:
(SELF (L f n , (ZERO? n ZERO (SUM n (f f (PRED n))))) THREE x y) ; 1*2*3 = 6
Out[80]:
In [81]:
; SELF
(L f n , (f f n)
; function
(L f n ,
; COND
(L p a b , (p a b)
; if n is ZERO?
(L f , (f (L a , (L a b , b)) (L a b , a)) n)
; then ZERO
(L x y , y)
; else SUM
(L m n x y , (m x (n x y))
; function PRED n
(f f (L n x y , (n (L g h , (h (g x))) (L x , y) (L a , a)) n))
; times n
n)))
; THREE
(L x y , (x (x (x y)))) x y) ; 1*2*3=6
Out[81]:
In [82]:
(YCOMB a)
In [83]:
(YCOMB (L f n , (ZERO? n ZERO (SUM n (f (PRED n))))) THREE x y) ; 1*2*3 = 6
Out[83]:
In [84]:
(YCOMB
(L f n ,
; COND
(L p a b , (p a b)
; if ZERO? n
(L f , (f (L a , (L a b , b)) (L a b , a)) n)
; then ZERO
(L x y , y)
; else SUM
(L m n x y , (m x (n x y))
; function PRED n
(f (L n x y , (n (L g h , (h (g x))) (L u , y) (L u , u)) n))
; times n
n)))
(L x y , (x (x (x y)))) x y)
Out[84]:
In [85]:
(with-alpha-conversion-and-macros L ,)
(YCOMB (L f , (L n , (COND (ZERO? n) ONE (PROD (f (PRED n)) n)))) THREE x y) ; 1*2*3 = 6
Out[85]:
In [86]:
(L f , (L x , (f (x x)) (L x , (f (x x))))
(L f , (L n ,
(L p , (L a , (L b , (p a b)))
(L n , (L s , (s (L a , (L a , (L b , b))) (L a , (L b , a))) n) n)
(L x , (L y , (x y)))
(L m , (L n x y , (m (n x) y))
(f (L n , (L x , (L y , (n (L g , (L h , (h (g x)))) (L x , y) (L a , a)))) n))
n))))
(L x , (L y , (x (x (x y))))))
Out[86]:
In [87]:
(with-alpha-conversion-and-macros L ,)
Out[87]:
In [88]:
; apply YCOMBinator to function f and number n
(L f , (L n , (f (n n)) (L n , (f (n n))))
; for function f and number n
(L f , (L n ,
; if n is ZERO?
(L f , (f (L a , (L a , (L b , b))) (L a , (L b , a))) n
; then return number ONE
(L x , (L y , (x y)))
; else calculate PRODuct of a. and b.
(L m , (L n , (L x , (L y , (m (n x) y)))
; a. apply recursive function f to PREDecessor of n
(f (L n , (L x , (L y , (n (L g , (L h , (h (g x)))) (L u , y) (L u , u)))) n)))
; b. n
n))))
; number THREE
(L x , (L y , (x (x (x y))))) x y)
Out[88]:
In [89]:
#¤(SUM)
Out[89]:
In [90]:
#¤(SUM)
Out[90]:
In [91]:
; https://stackoverflow.com/questions/36511989/lambda-calculus-reduction-evaluating-expr
#¤(L f x , (f (f x)) (L y , (y ^ 2)) 5)
Out[91]:
In [92]:
#¤
(L f , (L n , (f (n n)) (L n , (f (n n))))
(L f n ,
(L f , (f (L a , (L a b , b)) (L a b , a)) n
(L x y , (x y))
(L m n x y , (m (n x) y)
(f (L n x y , (n (L g , (L h , (h (g x)))) (L u , y) (L u , u)) n))
n)))
(L x y , (x (x (x y)))) x y)
Out[92]:
In [93]:
#¤
(L f , (L n , (f (n n)) (L n , (f (n n))))
(L f n ,
(L f , (f (L a , (L a b , b)) (L a b , a)) n
(L x y , (x y))
(L m n x y , (m (n x) y)
(f (L n , (L x , (L y , (n (L g , (L h , (h (g x)))) (L u , y) (L u , u)))) n))
n)))
(L x y , (x (x (x (x y))))) x y)
Out[93]:
Copyright (c) 2017 Marko Manninen