#la lambda-abstraction existe en Python
#pour l'application, contrairement au lambda-calcul on parenthèse autour de l'argument

#par exemple, s = lambda n. lambda f. lambda x. f (n f) x se traduit en :
s = lambda n : lambda f : lambda x : f(n(f)(x))

#la fonction suivante permet de transformer un entier classique de Python en entier codé dans le lambda-calcul :
def code(n):
    if n==0:
        return lambda f : lambda x : x
    else:
        return s(code(n-1))

#la fonction suivante permet d'afficher un entier codé :

def print_lambda_int(n):
    print("lambda f. lambda x. ", end="")
    n(lambda x : print("f(", end = ""))(None)
    print("x", end = "")
    n(lambda x : print(")", end =""))(None)

code_3 = code(3)

print_lambda_int(code_3)

code_4 = s(code_3)
print_lambda_int(code_4)

#decode transforme un entier codé en lambda-calcul en entier usuel python
def decode(n):
    return n(lambda m : m+1)(0)

decode(code_4)

#On écrit maintenant les opérations arithmétiques dans le lambda-calcul. On travaille sur des entiers codés, et on n'utilise rien d'autre que des lambdas et des applications (et éventuellement des termes de lambda-calcul déjà définis).

add = lambda n : lambda m: lambda f : lambda x : n(f)(m(f)(x))
#ou
add = lambda n : lambda m: n(s)(m)

print_lambda_int(add(code_3)(code_4))

mult = lambda n : lambda m : lambda f : lambda x : n(m(f))(x)
#ou
mult = lambda n : lambda m : n(add(m))(code(0))

print_lambda_int(mult(code_3)(code_4))

puiss = lambda n : lambda m : m (mult(n))(code(1))

couple = lambda a : lambda b : lambda z :  z(a)(b)

premier = lambda c : c(lambda a:lambda b:a)

second = lambda c : c(lambda a:lambda b:b)

aux = lambda c : couple(second(c))(s(second(c)))

pred = lambda n : premier(n(aux)(couple(code(0))(code(0))))

moins = lambda n : lambda m : m(pred)(n)

print_lambda_int(moins(code_4)(code_3))