notas-tsfc/calculo_lambda.tex

308 lines
15 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}
\section*{Programación funcional en \emph{Python}}
Declaraciones en lugar de procedimientos paso a paso.
Le decimos que hacer pero no como hacerlo.
Haskell: datos inmutables
Vemos en python:
\begin{lstlisting}{language=Python}
x=1
oldID=id(x)
x=x+1
id(x)==oldID
\end{lstlisting}
La falta de estado, como sucede en Haskell, conlleva la desventaja de la falta de memoria.
\section*{Funciones en Haskell}
Dos opciones, \emph{curriado} y \emph{no curriado}.
La definición de una *non-curried*
\begin{lstlisting}{language=Haskell}
suma (x,y) = x+y
\end{lstlisting}
A partir de la 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 diferenbcia ahora definimos la función incremento como:
\begin{lstlisting}{language=Haskell}
inc = suma 1
\end{lstlisting}
Se puede usar *curry* y *uncurry* para pasar de una a la otra. Aprovecha para jugar con map.
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}
\begin{thebibliography}{10}
\bibitem{Thompson1996} Thompson, Simon J.. ``Haskell - the craft of functional programming.'' International computer science series (1996).
\bibitem{VanRoy2009} Van Roy, Peter. ``Programming paradigms for dummies: what every programmer should know.'' (2009).
\bibitem{VanRoy2004} Van Roy, Peter, Haridi, Seif. ``Concepts, Techniques, and Models of Computer Programming''. *The MIT Press*, (2004). ISBN: 0262220695
\bibitem{Kowalski1988} Kowalski, Robert. ``The early years of logic programming'',\textit{Communications of the ACM}, \textbf{31}, 1, (1988).
\bibitem{python_concurrency} \url{https://realpython.com/python-concurrency/}, revisado el 15 de febrero de 2023.
\end{thebibliography}
\end{document}