notas-tsfc/calculo_lambda.tex

486 lines
24 KiB
TeX

\documentclass[10pt,a4paper]{article}
\usepackage[utf8]{inputenc}
\usepackage[spanish]{babel}
\usepackage{amsmath}
\usepackage{amsfonts}
\usepackage{amssymb}
\usepackage{hyperref}
\usepackage{graphicx}
\usepackage{listings}
\usepackage{xcolor}
\usepackage{caption}
\usepackage{pgf}
\usepackage{tikz}
\usetikzlibrary{automata,positioning,arrows}
\definecolor{codegreen}{rgb}{0,0.6,0}
\definecolor{codegray}{rgb}{0.5,0.5,0.5}
\definecolor{codepurple}{rgb}{0.58,0,0.82}
\definecolor{backcolour}{rgb}{0.95,0.95,0.92}
\definecolor{darkblue}{rgb}{0,0,.75}
\lstdefinestyle{mystyle}{
backgroundcolor=\color{backcolour},
commentstyle=\color{codegreen},
keywordstyle=\color{magenta},
numberstyle=\tiny\color{codegray},
stringstyle=\color{codepurple},
basicstyle=\ttfamily\footnotesize,
breakatwhitespace=false,
breaklines=true,
captionpos=b,
keepspaces=true,
numbers=left,
numbersep=5pt,
showspaces=false,
showstringspaces=false,
showtabs=false,
tabsize=2
}
\lstset{style=mystyle}
\lstloadlanguages{Matlab} %use listings with Matlab for Pseudocode
\lstnewenvironment{PseudoCode}[1][]
{\lstset{language=Matlab,basicstyle=\scriptsize, keywordstyle=\color{darkblue},numbers=left,xleftmargin=.04\textwidth,#1}}
{}
\renewcommand{\rmdefault}{ptm}
%\usepackage[all,cmtip]{xy}
%\usepackage{graphicx}
\author{Programación funcional para la física computacional}
\title{Cálculo $\lambda$ y programación funcional}
\begin{document}
\maketitle
\section*{Máquinas de Turing}
La máquina de Turing es uno de los modelos de computación más usados, es lo más cercano a una computadora (como la que usaron para bajar este pdf, y quizá hasta para leerlo), pero no es el único modelo. Ahora lo mencionó sólo para dar entrada a la idea del cálculo $\lambda$, para entender las cosas. No sé si será el método más pedagógico, o el más sencillo, pero es el estándar en la mayoría de libros del tema (además de que Turing es el más famoso de los matemáticos que trabajaron inicialmente en el área). Los modelos son equivalentes, así que no perderemos generalidad.
Una máquina de Turing se compone de un conjunto finito de estados $Q$, una cinta semi-infinita limitada por la izquierda con el símbolo $\vdash$ e ilimitada por la derecha (este límite izquierdo es para saber donde empieza la cinta) y una cabeza que puede moverse a izquierda y derecha, capaz de leer y escribir caracteres en la cinta\footnote{Son muy jóvenes para recordar los casetes quizá, aunque por ahí hay unos intentos retros de revivirlos, pero si alguna vez han visto uno con su respectivo reproductor podrán notar que cuenta con una cinta magnética de color café o negro, que es leída por una cabeza también magnética. Era un formato de no muy buena calidad, de riesgo pues un imán de potencia suficiente podía dañar la cinta y los carretes podían provocar accidentes como enrollarse o atorarse. Hubo computadoras que usaban casetes para leer programas, para una crónica al respecto echen un ojo a: \url{https://www.xataka.com/historia-tecnologica/cuando-los-videojuegos-venian-en-cassette-y-habia-que-rebobinarlos-para-poder-jugar}}. Las palabras de entrada, de longitud finita, se escriben sobre la cinta de izquierda a derecha (como escribimos nosotros). Al terminar la palabra de entrada en el resto de casillas de la cinta para distinguir que no contienen caracter alguno está pre-escrito el símbolo $\textvisiblespace$. Un esquema ejemplificando esta disposición se muestra en la figura \ref{fig:tur}.
\begin{figure}[h]
\begin{center}
\begin{tikzpicture}[every node/.style={block},
block/.style={minimum height=1.5em,outer sep=0pt,draw,rectangle,node distance=0pt}]
\node (A) {$a$};
\node (B) [left=of A] {$\ldots$};
\node (C) [left=of B] {$\vdash$};
\node (D) [right=of A] {$\ldots$};
\node (E) [right=of D] {$\textvisiblespace$};
\node (F) [above = 0.75cm of A,draw=red,thick] {\textsf Q};
\draw[-latex] (F) -- (A);
%\draw[-latex,blue] ($(F.east)!0.5!(A.east)$) -- ++(7mm,0);
\draw (C.north west) -- ++(-1cm,0) (C.south west) -- ++ (-1cm,0)
(E.north east) -- ++(1cm,0) (E.south east) -- ++ (1cm,0);
\end{tikzpicture}
\caption{Esquema mecánico de una máquina de Turing.}
\end{center}
\label{fig:tur}
\end{figure}
Podemos dar una definición formal:
\newtheorem{defi}{Definición}
\begin{defi}
Una máquina de Turing determinista, de cinta única es una 9-tupla (yo lo traduciría como un noneto o enéada, pero quizá no es la terminología) descrita como:
\begin{equation*}
M=(Q,\Sigma, \Gamma,\vdash,\textvisiblespace,\delta,s,t,r)
\end{equation*}
con:
\begin{itemize}
\item $Q$ es el conjunto finito de estados
\item $\Sigma$ es el alfabeto de entrada (finito)
\item $\Gamma$ es el alfabeto de cinta (finito), con $\Sigma \subseteq \Gamma$
\item $\vdash \in \Gamma - \Sigma$ el símbolo de inicio de la cinta
\item $\textvisiblespace \in \Gamma - \Sigma$ el símbolo de espacio en blanco
\item $\delta: Q\times \Gamma \rightarrow Q\times \Gamma \times \{ \rightarrow,\leftarrow \}$ la función de transición
\item $s\in Q$ el estado inicial
\item $t\in Q$ el estado de aceptación
\item $r\in Q$ el estado de rechazo.
\end{itemize}
\end{defi}
\subsection*{Un ejemplo}
Sea la máquina de Turing que decida el lenguaje $A=\{ 0^{2^n}|n\geq 0 \}$, es decir, el lenguaje con puras cadenas de $0$'s que su longitud sea una potencia de $2$.
\begin{center}
\begin{tabular}{ | c | c | c | c | c | c }
\hline
& $\vdash$ & $0$ & $x$ & $\textvisiblespace$ \\ \hline
$s$ & $(s,\vdash,\rightarrow)$ & $(q_1,\textvisiblespace,\rightarrow)$ & $(r,-,-)$ & $(r,-,-)$ \\ \hline
$q_1$ & & $(q_2,x,\rightarrow)$ & $(q_1,x,\rightarrow)$ & $(t,-,-)$ \\ \hline
$q_2$ & & $(q_3,0,\rightarrow)$ & $(q_2,x,\rightarrow)$ & $(q_4,\textvisiblespace,\leftarrow)$ \\ \hline
$q_3$ & & $(q_2,x,\rightarrow)$ & $(q_3,x,\rightarrow)$ & $(r,-,-)$ \\ \hline
$q_4$ & & $(q_4,0,\leftarrow)$ & $(q_4,x,\leftarrow)$ & $(q_1,\textvisiblespace,\rightarrow)$ \\
\hline
\end{tabular}
\end{center}
\begin{figure}[h]
\begin{center}
\begin{tikzpicture}%[shorten >=1pt,node distance=2cm,on grid,auto]
\node[state,initial,initial text= ] (S) {$S$};
\node[state,xshift=1cm,right=of S] (q_1) {$q_1$};
\node[state,above right=of q_1] (q_4) {$q_4$};
\node[state,below right=of q_4] (q_2) {$q_2$};
\node[state,below=of S] (r) {$r$};
\node[state,below=of q_1] (t) {$t$};
\node[state,below=of q_2] (q_3) {$q_3$};
\path [-stealth,thick]
(S) edge node[yshift=-0.3cm]{$0\Rightarrow \textvisiblespace, \rightarrow$} (q_1)
edge node[xshift=-0.5cm] {$\begin{array}{c l}
\textvisiblespace \Rightarrow - \\
x\Rightarrow -
\end{array}$} (r)
(q_1) edge node[yshift=-0.3cm]{$0\Rightarrow x,\rightarrow$} (q_2)
edge [loop above] node {$x\Rightarrow \rightarrow$} ()
edge node[xshift=-0.5cm]{$\textvisiblespace \Rightarrow -$} (t)
(q_2) edge[loop above] node{$x\Rightarrow \rightarrow$} ()
edge [bend right] node[xshift=-0.5cm]{$0\Rightarrow \rightarrow$} (q_3)
edge node{$\textvisiblespace \Rightarrow \leftarrow$} (q_4)
(q_3) edge[bend right] node[xshift=0.5cm]{$0\Rightarrow x,\rightarrow$} (q_2)
edge [loop right] node{$x\Rightarrow \rightarrow$} ()
edge [bend left] node[yshift=0.3cm]{$\textvisiblespace \Rightarrow -$} (r)
(q_4) edge[loop above] node{$\begin{array}{c l}
0 \Rightarrow \leftarrow \\
x\Rightarrow \leftarrow
\end{array}$} ()
edge node{$\textvisiblespace \Rightarrow \rightarrow$} (q_1);
\end{tikzpicture}
\caption{Diagrama de estados de la máquina de Turing descrita.}
\label{fig:tur3}
\end{center}
\end{figure}
\section*{Otros modelos computacionales}
El modelo de máquina de Turing ajusta muy bien para cuando se trabaja de manera teórica con computadoras, pero quizá pueda parecer demasiado mecánico y no se parece a como operamos con computadoras hoy en día. Existen más modelos que sonm equivalentes, sabemos de esa equivalencia por la \textbf{tesis Church-Turing}.
\newtheorem{church}{Tesis de Church-Turing}
\begin{church}
Toda función es efectivamente calculable sí y sólo sí es calculable por una máquina de Turing.
\end{church}
En esos otros modelos se encuentran las funciones recursivas $\mu$, relacionadas dierctamente al trabajo de Gödel al preguntarse ¿cuál es el mínimo de funciones necesarias para definir a todas las funciones computables?
\begin{enumerate}
\item \emph{Sucesor}. La función $\mathbf{s}:\mathbb{N} \rightarrow \mathbb{N}$ dadas por $\mathbf{s}(x)=x+1$ es computable.
\item \emph{Cero}. La función $\mathbf{z}: \mathbb{N}^0 \rightarrow \mathbb{N}$ dada por $\mathbb{z}()=0$ es computable.
\item \emph{Proyecciones}. Las funciones $\pi_k^n: \mathbb{N}^n \rightarrow \mathbb{N}$ dadas por $\pi_k^n(x_1,...,x_n) = x_k,\ 1\leq k \leq n$, son computables.
\item \emph{Composición}. Si $f:\mathbb{N}^k \rightarrow \mathbb{N}$ y $g_1,...g_k:\mathbb{N}^n \rightarrow \mathbb{N}$ son computables, entonces también lo es la función $f\circ (g_1,...,g_k):\mathbb{N}^n \rightarrow\mathbb{N}$ que en la entrada $\overline{x}= x_1,...,x_n$, da
\begin{equation*}
f(g_1(\overline{x}),...,g_k(\overline{x}))
\end{equation*}
\item \emph{Recursión primitiva}. Si $h_i:\mathbb{N}^{n} \rightarrow \mathbb{N}$ y $g_i:\mathbb{N}^{n+k+1} \rightarrow \mathbb{N}$ son computables, $1\leq i \leq k$, entonces también lo son las funciones $f_i:\mathbb{N}^{n+1} \rightarrow \mathbb{N},\ 1\leq i \leq k$, definidas por inducción mutua de la siguiente manera:
\begin{align*}
f_i(0,\overline{x}) &\overset{def}{=} h_i(x) \\
f_i(x+1,\overline{x}) &\overset{def}{=} g_i(x,\overline{x},f_1(x,\overline{x}),...,f_k(x,\overline{x})),
\end{align*}
donde $\overline{x}=x_1,...,x_n$.
\item \emph{Minimización no acotada}. Si $g:\mathbb{N}^{n+1} \rightarrow \mathbb{N}$ es computable, entonces también lo es la función $f:\mathbb{N}^n \rightarrow $ que con la entrada $\overline{x}=x_1,...,x_n$ de al menos $y$ tal que $g(z,\overline{x})$ esté definida para todas las $z\leq y$ y $g(y,\overline{x})=0$ si tal $y$, y está indefinida de otra manera. Esto se denota como:
\begin{equation*}
f(\overline{x})=\mu y.(g(y,\overline{x})=0)
\end{equation*}
\end{enumerate}
\section*{Cálculo $\lambda$}
Ya en la introducción habíamos mencionado una forma de ver a las funciones matemáticas como cajas negras, figura \ref{fig:func}. Ahora vamos un paso adelante a definir de manera abstracta lo que es una función, una \emph{abstracción funcional} y como aplicamos las funciones desde una perspectiva abstracta y meramente matemática.
\begin{center}
\begin{figure}[h!]
\begin{tikzpicture}
\draw[black, very thick] (0,0) rectangle (3,2);
\draw[-latex,line width=2pt,black] (-0.8,0.5)--(0,0.5);
\draw[-latex,line width=2pt,black] (-0.8,1.5)--(0,1.5);
\draw[-latex,line width=2pt,black] (3,1)--(3.8,1);
\draw[black, very thick] (6,0) rectangle (9,2);
\filldraw[black] (7.5,1.5) circle (0pt) node[below]{Producto escalar};
\draw[-latex,line width=2pt,black] (5.2,0.5)--(6,0.5);
\filldraw[black] (5.2,0.5) circle (1pt) node[below]{$(3,2,1)$};
\draw[-latex,line width=2pt,black] (5.2,1.5)--(6,1.5);
\filldraw[black] (5.2,1.5) circle (1pt) node[above]{$(2,1,2)$};
\draw[-latex,line width=2pt,black] (9,1)--(9.8,1);
\filldraw[black] (9.8,1) circle (0pt) node[above]{$10$};
\end{tikzpicture}
\caption{Una función representada como caja negra.}
\end{figure}
\label{fig:func}
\end{center}
Las características del cálculo $\lambda$ pueden listarse:
\begin{itemize}
\item Solo depende de funciones. Si no se puede escribir como función, no se puede incluir.
\item No tiene estado o efectos laterales
\item El orden de evaluación no es relevante
\item Todas las funciones son unitarias, sólo toman un argumento
\end{itemize}
El cálculo $\lambda$ fue iniciado en la década de los 30s del siglo $XX$ por Alonso Church. No fue la única persona trabajando en el área, Haskell Curry trabajo en la lógica combinatoria, Gödel en las funciones recursivas $\mu$ en las que podemos encontrar algunas similitudes, Emil Post en el sistema canónico de Post, o sistema de reescritura y Alan Turing con las máquinas de Turing. Todos ellos de alguna manera trataron de definir qué era un algoritmo y darle una estructura formal\footnote{Como vimos en la sección pasada los algoritmos existen incluso desde hace 2000 años, pero no había una teoría formal de ellos.}. Las funciones recursivas $\mu$ y las máquinas de Turing ya las vimos de rápido, en lo que sigue nos centraremos en la parte del trabajo de Church y Curry. Pero para ser justos hay que mencionar como funciona el sistema canónco de Post.
Se parte de una cantidad finitamente grande de cadenas de caracteres que serán manipuladas, de manera repetida son transformadas al aplicar un conjunto finito de reglas, formando un lenguaje regular. Al ser Turing completo, es equivalente a trabajar con máquinas de Turing. Ya sólo se estudia con interés histórico pues hay formalismos que incluyen las ideas de Post más usados hoy en día.
La idea de Post al crear el cálculo $\lambda$ era demostrar que el problema de la decisión de Hilbert no era resolvible por la aritmética de Peano. Lo que obtuvo fue un sistema para estudiar las matemáticas de una forma más general.
Con las reglas antes mencionadas sólo resta decir que para construir todas las funciones matemáticas en el cálculo $\lambda$ basta usar tres operaciones:
\begin{itemize}
\item Crear funciones que recibirán variables
\item Asociar una variable a la expresión
\item Aplicar una función a un argumento
\end{itemize}
¿Porque la diferencia entre asociar una variable y aplicar a un argumento? Tal como cuando definimos una función matemática del estilo $f(x)=x^2-3x$ las variable $x$ es más un marco donde eventualmente puede entrar un valor, alevaluar la ecuación en un número es que estamos aplicando la función a un argumento. Las variables son términos $\lambda$ y son la base de las definiciones deductivas del cálculo $\lambda$.
En el trato normal de las variables en el cálculo $\lambda$ los tipos no entran en juego, está des-tipado (no sé si así se dice, pero suena cagado).
Imaginemos que se tienen dos términos $\lambda$ $M$ y $N$, $MN$ también es un término $\lambda$, podría ser por ejemplo que $M$ es una función y $N$ una entrada (podría ser también una función). La forma más formal de escribirlo es $(M)N$ donde se dice que $N$ es una entrada aplicada a la función $M$. El paréntesis es operador \emph{aplicación} y se asocia a la izquierda (MN)P.
Pero realmente el orden de la aplicación es de izquierda a derecha, por ejemplo, si tenemos $\lambda x.E(x)$ lo que quiere decir es que con la entrada $x$ la función calcula $E(x).$
Veamos un ejemplo concreto. Se tiene:
\begin{lstlisting}{language=Haskell}
inc(x) = x+1
\end{lstlisting}
Lo que quiere decir que para incrementar $x$ se le suma $1$
\begin{lstlisting}{language=Haskell}
(x) -> x+1
\end{lstlisting}
Pero usando ya directamente la abstracción
\begin{equation*}
\lambda x.(x+1)
\end{equation*}
Si ahora tenemos la función
\begin{lstlisting}{language=Haskell}
sumcuad(x,y) = x^2+y^2
\end{lstlisting}
Que se traduce a
\begin{lstlisting}{language=Haskell}
(x,y)-> x^2+y^2
\end{lstlisting}
¿Cómo se pondría en la abastracción?
\begin{equation*}
\lambda x.(\lambda y.(x^2+y^2))
\end{equation*}
¡Muy bien! ¿Y ahora como se evalúa (aplicar a un argumento)? Para el caso del incremento al número $8$:
\begin{equation*}
\lambda x.(x+1)8
\end{equation*}
Vamos a un caso más abstracto, sea la composición de las funciones $f$ y $g$
\begin{equation*}
\lambda x.f(gx)
\end{equation*}
Pero en ese caso solo estamos dando como entrada a $x$ ¿y si quiero dejarlo como el esqueleto de una composición done también puedo darle como entrada las funciones $f$ y $g$?
\begin{equation*}
\lambda f.\lambda g. \lambda x. f(gx)
\end{equation*}
Con ese esqueleto ahora hagamos la composición de la función sucesor:
\begin{align*}
&(\lambda f.\lambda g. \lambda x. f(gx))(\lambda y.(y+1))(\lambda z.(z+1)) \\
&\rightarrow (\lambda g. \lambda x. ((\lambda y.(y+1))(gx)))(\lambda z.(z+1)) \\
&\rightarrow \lambda x.((\lambda y.(y+1))((\lambda z.(z+1))x))) \\
&\rightarrow \lambda x.((\lambda y.(y+1))(x+1)) \\
&\rightarrow \lambda x.((x+1)+1)
\end{align*}
\subsection*{Calculo $\lambda$ \emph{tipado}}
Una variante del cálculo $\lambda$ admite que se especifique el tipo de las variables a dar como argumento, así las funciones definidas se restringen al dominio dado.
Si en la función incremento sólo queremos operar sobre naturales
\begin{equation*}
\lambda x:\nu.(x+1)
\end{equation*}
Eso es el estilo Church, se infiere que al ser la entrada naturales la salida son naturales, pero el estilo Curry hace que toda la función sea definida dentro de los naturales
\begin{equation*}
(\lambda x.(x+1))\nu \rightarrow \nu
\end{equation*}
Si hay más de una variable la versión Post sería
\begin{equation*}
\lambda x:\nu.(\lambda y:\nu.(x^2+y^2))
\end{equation*}
Y la versión Curry
\begin{equation*}
\lambda x.(\lambda y.(x^2+y^2)):\nu \rightarrow \nu \rightarrow \nu
\end{equation*}
\subsection*{Operaciones de reducción}
Vamos ahora a expresar una función $\lambda$ en su forma más simple y pura
\begin{itemize}
\item \textbf{Reducción $\alpha$}: Renombrar variables. Se dice que dos funciones son $\alpha equivalentes$ cuando lo único que cambia es el nombre d ela variable ($\lambda x.(x+1)$ y $\lambda a.(a+1)$).
\item \textbf{Reducción $\beta$}: Remplaza variables por argumentos. Como ya hicimos líneas arriba.
\item \textbf{Reducción $\eta$}: Un aso antes para asegurar la reducción $\beta$
\end{itemize}
Un ejemplo de $\alpha$ equivalencia son la pareja de funciones:
\begin{align*}
\lambda x.&(x^2+3x) \\
\lambda a.&(a^2+3a),
\end{align*}
\noindent Si cambiamos el nombre de la variable en ambos casos, tenemos la misma función. Siempre es posible renombrar variables mientras no se use el nombre de una ya utilizada.
La reducción $\beta$ ya viomos un ejemplo, pero para no dejar damos un ejemplo evaluando la función en un natural:
\begin{align*}
\lambda x.&(x^2+3x)(2) \\
&(2^2+3(2)) \\
=& 10,
\end{align*}
\noindent y evaluando en optra función, a final de cuentas variables, constantes y funciones todas son $\lambda$ términos y valen lo mismo para la $\beta$ reducción
\begin{align*}
\lambda x.&(x^2+3x)(\lambda y.(y+1)) \\
&((y+1)^2+3(y+1)),
\end{align*}
La reducción $\eta$ es un paso más abstracto, de cierta manera se asegura de que la forma de la función $\lambda$ es correcta y está lista para una $\beta$ reducción. A este punto no es necesario ahondar mucho en ello.
\section*{Programación funcional en \emph{Python}}
Una de las características de la programación funcional a diferencia de la programación estructurada o la orientada a objetos es que funciona a partir de declaraciones en lugar de procedimientos paso a paso. Es decir, le decimos a la máquina que hacer pero no le indicamos como hacerlo. Así funciona Haskell por diseño, con la ya comentada ventaja de los datos inmutables (evitando los problemas de flujo de datos en concurrencia, como mencionamos la sección pasada).
¿Cómo es eso de los datos inmutables? Vemos en python:
\begin{lstlisting}{language=Python}
x=1
oldID=id(x)
x=x+1
id(x)==oldID
\end{lstlisting}
El valor ha mutado, pues lo hemos obligado a hacerlo así, pero en un caso más amplio si no somos cuidadosos podría provocar errores en nuestro programa.
La falta de estado, como sucede en Haskell, conlleva la desventaja de la falta de memoria.
\section*{Funciones en Haskell}
En Haskell existen dos opciones para definir funciones, de forma \emph{curriada} y \emph{no curriada} (por Haskell Curry, no porque le pongamos ese condimento de la comida oriental).
La definición de una función \emph{no curriada}
\begin{lstlisting}{language=Haskell}
suma (x,y) = x+y.
\end{lstlisting}
A partir de esa función suma definamos la función sucesor:
\begin{lstlisting}{language=Haskell}
suc (x) = suma (x,1).
\end{lstlisting}
Si esto mismo trataramos de hacer en $python$, para definir una operación que deja sin cambio una variable, se tiene que hacer:
\lstinputlisting[firstline=1,lastline=9]{suma_unch.py}
Es una definición especial de la suma que lo permite, pero no es la norma, por ejemplo:
\lstinputlisting[firstline=1,lastline=9]{lista-ch.py}
Regresando a Haskell, definamos las operaciones de suma e incremento de forma \emph{curriada}, es decir, definimos una función que sólo necesita un argumento y regresa otra función. Cierrra \textbf{GHCi} y vuelve a abrir. Ahora define:
\begin{lstlisting}{language=Haskell}
suma x y = x+y
\end{lstlisting}
Aquí parece que solo quitamos los paréntesis, pero no. Para notar la diferencia ahora definimos la función incremento como:
\begin{lstlisting}{language=Haskell}
inc = suma 1
\end{lstlisting}
Se puede usar \emph{curry} y \emph{uncurry} para pasar de una a la otra. Aprovecha para jugar con \emph{map}.
\begin{lstlisting}{language=Haskell}
suma_u = uncurry suma
\end{lstlisting}
Como haríamos esto mismo en $python$:
\begin{lstlisting}{language=Python}
def suma(x,y):
return x+y
\end{lstlisting}
Para definir el incremento en uno:
\begin{lstlisting}{language=Python}
def inc(x):
return suma (x,1)
\end{lstlisting}
Pero apliquemos lo visto anteriormente de cálculo $\lambda$ para ambos lenguajes de programación. En Haskell ya vimos un poco pero ampliemos, para definir una función tenemos tres formas equivalentes
\begin{align*}
suma\ x=& \setminus y -> x+y \\
suma =& \setminus x -> (\setminus y -> x+y) \\
suma =& \setminus x -> \setminus y -> x+y.
\end{align*}
Noten que es como una definición de funciones $\lambda$, en lugar de $\lambda$ se usa $\setminus$ y se sigue la misma estructura. De esta forma puede definirse la suma sin argumentos pero dentro de la definición están los $\lambda$ términos necesarios para aceptar variables.
Al verlo pueden pensar que so no tiene el mayor chiste, más parece que nos complicamos la vida, pero no, esto tiuene un sentido más. ¿Qué tal que quisiéramos definir un nuevo operador, llamémosle $+=$\footnote{Ahora no tiene mucha diferencia al operador $+$ salvo que nosotros lo definimos, pero imaginen aplicar esto para una función más complicada que puede ser evaluada con un simple operador.}?
\begin{align*}
(+=) =& \setminus x -> \setminus y -> x+y\\
(+=) =& \setminus x\ y -> x+y \\
3\ +=\ 5.
\end{align*}
¿Cómo podemos definir funciones $\lambda$ en $python$? Aunque para este lenguaje de programación el cálculo $\lambda$ no está directamente aplicado, está de alguna forma desde sus huesos. Por las características de ser más directamente aplicable. más sencillo, esto sirve más como un caso ilustrativo. Las dos formas de definir una función con cálculo $\lambda$ en $python$:
\begin{align*}
suma =& lambda\ x, y: x+y \\
&suma(3,2) \text{ la forema de llamar a la función}\\
suma =& lambda\ x: lambda\ y: x+y\\
&suma(3)(2) \text{ llamando a la función},
\end{align*}
\noindent vea que las diferentes definiciones en este caso implican llamr de forma distinta a la función. En la definición $\lambda$ se escribe $lambda$.
\begin{thebibliography}{10}
\bibitem{Thompson1996} Thompson, Simon J.. ``Haskell - the craft of functional programming.'' International computer science series (1996).
\bibitem{Mueller2019} Mueller, John P. ``Functional programming (for dummies)'' John Willey $\&$ Sons Inc. (2019).
\bibitem{Kozen} Kozen, Dexter C. ``Automata and Computability'' Springer (1997)
\end{thebibliography}
\end{document}