-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathLambda.lagda
1957 lines (1460 loc) · 84.8 KB
/
Lambda.lagda
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
\documentclass[spanish]{article}
% \usepackage[style=numeric,sorting=nyt,sortcites=true,backref=true,autopunct=false,backend=biber]{biblatex}
\usepackage{amssymb}
\usepackage[spanish]{babel}
\usepackage{amsmath}
\usepackage{dsfont}
\usepackage[]{hyperref}
\hypersetup{
pdftitle={Tipos dependientes y Agda},
pdfauthor={Alejandro Gadea, Emmanuel Gunther},
pdfsubject={},
pdfkeywords={Teoría de tipos,Programación funcional,Tipos dependientes,Cálculo Lambda,Agda},
bookmarksnumbered=true,
bookmarksopen=true,
bookmarksopenlevel=1,
colorlinks=false,
pdfstartview=Fit,
pdfpagemode=UseOutlines, % this is the option you were lookin for
pdfpagelayout=TwoPageRight
}
\addto\captionsspanish{
\renewcommand{\contentsname}%
{Indice}%
}
% This handles the translation of unicode to latex:
\usepackage{ucs}
\usepackage[utf8x]{inputenc}
\usepackage{autofe}
\date{}
\usepackage{listings}
% Para escribir las reglas de tipado.
\usepackage{bussproofs}
\usepackage{bigfoot}
% Some characters that are not automatically defined
% (you figure out by the latex compilation errors you get),
% and you need to define:
\DeclareUnicodeCharacter{8988}{\ensuremath{\ulcorner}}
\DeclareUnicodeCharacter{8989}{\ensuremath{\urcorner}}
\DeclareUnicodeCharacter{8803}{\ensuremath{\overline{\equiv}}}
\DeclareUnicodeCharacter{10236}{\ensuremath{\mapsto}}
\DeclareUnicodeCharacter{9655}{\ensuremath{\rhd}}
\DeclareUnicodeCharacter{65378}{\ensuremath{\lceil}}
\DeclareUnicodeCharacter{65379}{\ensuremath{\rfloor}}
\DeclareUnicodeCharacter{8759}{\ensuremath{\colon}}
\DeclareUnicodeCharacter{952}{\ensuremath{\theta}}
\DeclareUnicodeCharacter{964}{\ensuremath{\tau}}
\DeclareUnicodeCharacter{960}{\ensuremath{\pi}}
\DeclareUnicodeCharacter{955}{\ensuremath{\lambda}}
\DeclareUnicodeCharacter{931}{\ensuremath{\Sigma}}
\DeclareUnicodeCharacter{916}{\ensuremath{\Delta}}
\DeclareUnicodeCharacter{10230}{\ensuremath{\longrightarrow}}
\DeclareUnicodeCharacter{9679}{\ensuremath{\bullet}}
\DeclareUnicodeCharacter{8348}{\ensuremath{_t}}
\DeclareUnicodeCharacter{8799}{\ensuremath{\overset{?}{=}}}
\DeclareUnicodeCharacter{8347}{\ensuremath{_s}}
\DeclareUnicodeCharacter{7525}{\ensuremath{_v}}
\DeclareUnicodeCharacter{8343}{\ensuremath{_l}}
\DeclareUnicodeCharacter{8336}{\ensuremath{_a}}
\DeclareUnicodeCharacter{8604}{\ensuremath{\hookleftarrow}}
\DeclareUnicodeCharacter{8605}{\ensuremath{\hookrightarrow}}
% Add more as you need them (shouldn’t happen often).
% Using “\newenvironment” to redefine verbatim to
% be called “code” doesn’t always work properly.
% You can more reliably use:
\usepackage{fancyvrb}
\usepackage{color}
\DefineVerbatimEnvironment
{code}{Verbatim}
{fontsize=\footnotesize} % Add fancy options here if you like.
%% COMMANDS
\newcommand{\agType}[1] {\textbf{#1}}
\newcommand{\comment}[1] {\textbf{\textcolor{red}{#1}}}
\newcommand{\tjud}[2]
{\ensuremath{#1\,:\,#2}}
\newcommand{\depFun}[3] {
{\ensuremath{\Pi_{(\tjud{#1}{#2})}\,#3\,#1}}
}
\newcommand{\cprod}[2]
{\ensuremath{#1 \times #2}
}
\newcommand{\depPair}[3]
{\ensuremath{\Sigma_{(\tjud{#1}{#2})}\,#3\,#1}
}
\title{Programación certificada mediante lenguajes con tipos dependientes}
\author{Alejandro Gadea, Emmanuel Gunther}
\begin{document}
\maketitle
\tableofcontents
\newpage
%% Referencias: Type Theory and Functional Programming Simon Thompson
%% Homotopy Type Theory (Cap 1)
\section{Introducción}
En cualquier sistema de computación se pretende obtener programas que satisfagan problemas
de manera \textbf{correcta}. Si bien esto parece obviamente deseable, es algo difícil de lograr,
y que en la práctica pocas veces sucede.
Un programa se escribe a partir de una especificación y se asume que la ejecución del mismo la satisfará.
Pero esta asunción no es justificada: en la mayoría de los casos, el programa no satisface la especificación.
¿Cómo podemos entonces lograr tener software correcto? El testing podrá evidenciar errores pero nunca probar
su ausencia. Sólo una prueba formal de correctitud puede garantizar que un programa satisface una especificación.
Un enfoque para realizar este proceso podría ser desarrollar el programa, y luego dar una prueba de que satisface
una especificación, para lo cual deberíamos poder unir la implementación del programa con un sistema
formal de pruebas, o modelar el programa de tal manera de poder verificar formalmente su corrección.
Pero otro enfoque más adecuado sería el de desarrollar el programa de manera tal que deba satisfacer una especificación
por la forma en que es construido.
Los sistemas de tipos en los lenguajes de programación permiten agregar restricciones para la construcción de
programas, de manera de poder expresar en los tipos propiedades que deben satisfacerse. Un lenguaje no tipado
permitiría construir programas con algunos errores, que si bien uno podría probar luego que éstos no ocurren,
no se puede asegurar su ausencia de antemano. En un lenguaje tipado, tal programa no podría construirse.
Como ejemplo consideremos la función que toma el primer elemento de una lista. En un lenguaje sin tipos
fuertes como python podríamos escribirla de la siguiente forma:
\begin{verbatim}
def head (l):
return l[0]
\end{verbatim}
Dado que no tenemos un sistema de tipos fuerte, podríamos tener un programa que tenga la siguiente línea:
\begin{verbatim}
head (True)
\end{verbatim}
Lo que llevaría a tener un error en tiempo de ejecución.
Consideremos ahora un lenguaje con tipos fuertes como Haskell:
\begin{verbatim}
head :: [a] -> a
head (x:xs) = x
\end{verbatim}
Con este lenguaje no podríamos escribir \verb|head True| como antes, pero sí podemos
escribir \verb|head []|, lo que nuevamente nos llevaría a un error en tiempo de ejecución.
Lo que necesitaríamos es restringir la función \verb|head| para que el tipo del parámetro
no sea cualquier lista, sino una lista que tenga al menos un elemento.
En un lenguaje con \textbf{tipos dependientes}, como Agda, podríamos definir la función
head que tome como parámetros la lista y además una ``prueba" de que la misma tiene longitud
mayor que cero:
\begin{verbatim}
head : {a : Set} → (xs : List a) → 0 < length xs → a
head [] ()
head (x ∷ xs) _ = x
\end{verbatim}
Sin entrar en demasiados detalles, la línea \verb|head [] ()| expresa que este caso
es absurdo. Teniendo esta definición vemos que para poder aplicar la función \verb|head| sobre una lista \verb|xs|
tenemos que poder construir un elemento de \verb|0 < length xs|, lo que representa la
prueba de que la lista no es vacía. Por lo tanto nunca podríamos tener en un programa una llamada a \verb|head []|.
Un lenguaje con estas características permite garantizar la correctitud mediante un sistema de tipos
que permite escribir proposiciones lógicas como tipos mismos del lenguaje.
\medskip
En este trabajo estudiaremos un lenguaje con tipos dependientes: Agda. Para ello, primero haremos
un breve repaso de las bases de la teoría de tipos de Martin-Löf. Luego revisaremos las características
y generalidades del lenguaje Agda. Por último realizaremos una implementación en Agda de la inferencia de tipos
para el cálculo lambda simplemente tipado asegurando su corrección.
\section{Teoría de tipos}
Los sistemas de tipos más complejos que permiten expresar propiedades
sobre los programas se basan en la \textbf{teoría de tipos}. En ésta los tipos no son meros
representantes para conjuntos de elementos como en la mayoría de los lenguajes de programación,
sino que proveen un poder expresivo más grande.
\medskip
La teoría de tipos es una teoría fundacional de la matemática, alternativa a la de conjuntos de Zermelo-Fraenkel.
El concepto principal en la teoría es el de \textbf{tipo}, el cual puede representar un conjunto de elementos
o también una proposición lógica. En este último caso, los elementos de un tipo serán pruebas de que la misma
es válida.
En la teoría de tipos un elemento $x$ no puede existir en sí mismo si no es dentro de un tipo. Por lo tanto no se puede
expresar algo como ``sea $x$ tal que cumple alguna propiedad", sino que $x$ debe introducirse junto con algún tipo:
$\tjud{x}{A}$ significa que $x$ es un elemento de tipo $A$.
\subsection{Universos}
Puesto que un elemento no puede existir si no es dentro de un tipo, podríamos plantearnos qué son los tipos mismos.
Un tipo $A$ también deberá tener algún tipo. Usualmente se utiliza el término \textbf{universo}.
para referir al tipo cuyos elementos son tipos. Esto podría dar lugar a una
paradoja (ya que el tipo que contenga a todos los tipos en particular debería contenerse a sí mismo) y para evitarla se introduce una jerarquía en los universos:
\begin{align*}
\tjud{U_0}{\tjud{U_1}{\tjud{U_2}{...}}}
\end{align*}
\noindent donde cada $U_i$ es un elemento de $U_{i+1}$, y más aún si $\tjud{A}{U_i}$, luego también $\tjud{A}{U_{i+1}}$.
Si no es necesario conocer a qué nivel de universo explícitamente pertenece un tipo, se lo suele omitir anotando simplemente
$\tjud{A}{U}$.
\subsection{Definición de tipos}
La teoría se construye en base a la definición de tipos. Para introducir un nuevo tipo se debe especificar lo siguiente:
\begin{itemize}
\item \textbf{reglas de formación}, que definen la manera en que se forma el tipo (bajo qué condiciones
se puede formar).
\item \textbf{constructores}, que definen cómo construir elementos del tipo que se está definiendo.
\item \textbf{eliminadores}, los cuales representan cómo usar los elementos del tipo. Un eliminador
será un objeto que permite descomponer al tipo en otros tipos.
\item \textbf{regla de computación}, que corresponde a la definición del eliminador, es decir, expresa cómo
un eliminador actúa sobre un constructor.
\item \textbf{principio de unicidad} opcional, que permite expresar para algunos tipos que todo elemento
está únicamente determinado por los resultados de aplicar eliminadores y que luego puede ser
reconstruido a partir de esos resultados aplicando un constructor (puede pensarse como el dual de la regla
de computación).
\end{itemize}
Para introducir entonces un nuevo tipo especificaremos al menos sus reglas de formación, sus constructores y
sus eliminadores junto con las reglas de computación.
\subsection{Funciones}
\textbf{Regla de formación}:
Dados dos tipos $A$ y $B$ se puede construir un tipo $A \rightarrow B$ que representa funciones con dominio
en $A$ y codominio en $B$. Las funciones son un concepto primitivo en la teoría de tipos.
\smallskip
\textbf{Constructor}:
Para construir elementos de un tipo funcional $A \rightarrow B$ se puede realizar una definición o utilizar la abstracción lambda. La
definición
\begin{align*}
f\,x\,=\,E
\end{align*}
\noindent donde $x$ es una variable y $E$ una expresión que puede contener a $x$, es válida si se chequea que $\tjud{E}{B}$
asumiendo $\tjud{x}{A}$.
La misma definición mediante la abstracción lambda sería:
\begin{align*}
\lambda\,(\tjud{x}{A}).E
\end{align*}
\noindent que cumple el juicio $\tjud{\lambda\,(\tjud{x}{A}).E}{A \rightarrow B}$ bajo las mismas condiciones detalladas previamente
sobre $x$ y $E$. A partir de que la expresión tiene tipo $A \rightarrow B$, el tipo de la variable $x$ puede inferirse, por lo
cual podemos omitir ponerlo explícitamente.
\smallskip
\textbf{Eliminador}:
Dada una función $\tjud{f}{A \rightarrow B}$ y un elemento $\tjud{a}{A}$, luego se puede \textit{aplicar} la función
$f$ a $a$, lo cual notamos con $f\,a$ y cuyo tipo es $\tjud{f\,a}{B}$.
Con la definición de funciones se introduce una \textbf{regla de computación}:
\begin{align*}
(\lambda\,x.E)\,a\;\equiv\;E [x \longrightarrow a]
\end{align*}
\textbf{Unicidad}:
Otra regla que se introduce es la también conocida como \textbf{regla Eta}. Dado que tenemos definida una función
$\tjud{f}{A \rightarrow B}$, se da la siguiente igualdad por definición:
\begin{align*}
f \equiv \lambda\,x.f\,x
\end{align*}
\subsection{Funciones dependientes}
En la teoría de tipos podemos definir funciones más generales que las usuales, en donde el codominio puede depender
de valores del dominio. Al tipo de las funciones dependientes se las llama $\Pi-type$ en \cite{hottbook}.
Dado un tipo $\tjud{A}{U}$ y una familia $\tjud{B}{A \rightarrow U}$ podemos definir el tipo de las funciones dependientes
\begin{align*}
\tjud{\depFun{x}{A}{B}}{U}
\end{align*}
\noindent un elemento de este tipo será una función que tome un valor $x$ de tipo $A$ y retorne uno de tipo $B\,x$.
Notemos que si $B$ es constante tenemos el tipo de las funciones como lo definimos previamente.
Las reglas para este tipo son las mismas que definimos para las funciones comunes.
Si tenemos una función $\tjud{f}{\depFun{x}{A}{B}}$, \textit{para cada} elemento $\tjud{x}{A}$ podemos obtener
un elemento de $B\,x$, es decir, aplicando $f$ a cualquier $\tjud{x}{A}$ tenemos un habitante del tipo $B\,x$.
Notemos que si pensamos a los tipos como proposiciones, y consideramos que una proposición es válida cuando
el tipo que la representa tiene un elemento, el tipo de la función $f$ representa que \textit{para todo $\tjud{x}{A}$
vale $B\,x$}.
\subsection{Familias de tipos}
Podríamos tener una función que tome un valor de un tipo $A$ y retorne un elemento de $U$, es decir, que retorne
a su vez un tipo. A estas funciones se las suele llamar \textbf{familia de tipos}:
\begin{align*}
\text{Dado } \tjud{A}{U} \text{, podemos definir }\tjud{B}{A \rightarrow U}
\end{align*}
\noindent Si se aplica $B$ a algún valor de tipo $A$, se obtiene un tipo.
\subsection{Otros tipos de datos}
Veremos como ejemplo algunos tipos de datos de la teoría de tipos:
\medskip
\noindent \textbf{Producto}
\smallskip
\textbf{Regla de formación:} Dados dos tipos $\tjud{A}{U}$ y $\tjud{B}{U}$, introducimos el tipo $\tjud{\cprod{A}{B}}{U}$, al cual se lo suele llamar
\textit{producto cartesiano}.
\smallskip
\textbf{Constructor:} A partir de un elemento $\tjud{a}{A}$ y un $\tjud{b}{B}$ podemos construir el elemento
$\tjud{(a,b)}{\cprod{A}{B}}$.
\smallskip
\textbf{Eliminadores y regla de computación:} Podemos definir dos eliminadores del tipo:
\begin{align*}
&\tjud{pr_1}{\cprod{A}{B} \rightarrow A}\\
&\tjud{pr_2}{\cprod{A}{B} \rightarrow B}\\
\end{align*}
\noindent mediante las reglas de computación
\begin{align*}
&pr_1\,(a,b)\,=\,a\\
&pr_2\,(a,b)\,=\,b\\
\end{align*}
\medskip
\noindent \textbf{Pares dependientes}
\medskip
De la misma forma que generalizamos el tipo de las funciones, podemos generalizar el de los pares, teniendo que el tipo
del segundo componente del par, \textit{dependa} del primer componente.
\smallskip
\textbf{Regla de formación:} Dados un tipo $\tjud{A}{U}$ y una familia $\tjud{B}{A \rightarrow U}$ definimos el tipo de pares dependientes
\begin{align*}
\depPair{x}{A}{B}
\end{align*}
\smallskip
\textbf{Constructores:} Para construir elementos de este tipo lo hacemos de la misma forma que con el producto, si tenemos $\tjud{a}{A}$,
en el segundo componente tendremos algún $\tjud{b}{B\,a}$.
\smallskip
\textbf{Eliminadores y regla de computación:} Las proyecciones tal como las definimos para el producto también podemos definirlas
aquí.
\smallskip
En el caso particular donde $B$ es constante, el tipo será el producto cartesiano.
Observemos que para poder obtener un elemento del tipo $\depPair{x}{A}{B}$, necesitaremos encontrar
un $\tjud{x}{A}$ de tal forma que el tipo $B\,x$ contenga un elemento. Es decir, el tipo $\depPair{x}{A}{B}$
será habitado si \textit{existe} un elemento $\tjud{x}{A}$ tal que $B\,x$ es habitado. De la misma manera
que pensamos al tipo de las funciones dependientes como el análogo al cuantificador universal de la teoría
de conjuntos, el tipo de los pares dependientes es análogo al cuantificador existencial.
\medskip
\noindent \textbf{Coproducto}
\smallskip
\textbf{Regla de formación:} Dados $\tjud{A}{U}$, $\tjud{B}{U}$, introducimos el tipo $\tjud{A + B}{U}$, normalmente llamado como \textbf{coproducto} y
que representa la unión disjunta en la teoría de conjuntos.
\smallskip
\textbf{Constructores:} Para construir un elemento de $\tjud{A + B}{U}$ tenemos dos maneras, a partir de un elemento $\tjud{a}{A}$, o a partir
de uno $\tjud{b}{B}$ con los constructores $inl\,:\,A \rightarrow A + B$ y $inr\,:\,B \rightarrow A + B$ respectivamente
(inyección izquierda o inyección derecha).
\smallskip
\textbf{Eliminadores y regla de computación:} Observemos que si tenemos una función $\tjud{g_0}{A \rightarrow C}$ (es decir, que obtiene un elemento de $C$ a partir de uno
de $A$) y otra función $\tjud{g_1}{B \rightarrow C}$ (obtiene un elemento de $C$ a partir de uno de $B$) podemos
definir una función $f$ que a partir de un elemento del coproducto $A + B$ obtenga un elemento de $C$:
\begin{align*}
&\tjud{f}{A + B \rightarrow C}\\
&f\,(inl\,a)\,=\,g_0\,a\\
&f\,(inr\,b)\,=\,g_1\,a\\
\end{align*}
\noindent \textbf{Tipo vacío}
\smallskip
\textbf{Regla de formación:} Podemos considerar el tipo que no tiene ningún elemento, el tipo vacío $\mathbf{0}$.
\smallskip
\textbf{Constructores:} Puesto que no hay ningún elemento que tenga tipo $\mathbf{0}$, no tenemos constructores.
\smallskip
\textbf{Eliminadores y regla de computación:} Mediante un eliminador podemos obtener un elemento de algún tipo $C$ a partir
de los constructores del tipo que se está definiendo. Observemos que como no tenemos constructores podemos pensar que
cualquier elemento puede obtenerse a partir del tipo vacío. Tenemos entonces un eliminador del tipo vacío que es una función
que no toma argumentos y construye un elemento de cualquier tipo.
\medskip
\noindent \textbf{Tipo unario}
\smallskip
\textbf{Regla de formación:} Podemos considerar el tipo que contiene exactamente un elemento: $\mathbf{1}$.
\smallskip
\textbf{Constructores:} El tipo contendrá un solo constructor: $\tjud{*}{\mathbf{1}}$.
\smallskip
\textbf{Eliminadores y regla de computación:} La única manera de obtener un elemento de un tipo $C$ a partir del tipo
unario es si ese elemento es tomando como argumento de la función de eliminación al mismo elemento. Entonces el eliminador
del tipo $\mathbf{1}$ será una función $\tjud{g}{C \rightarrow \mathbf{1} \rightarrow C}$.
\subsection{Proposiciones como tipos}
En la introducción de esta sección vimos que podemos tener una analogía entre
las proposiciones lógicas y los tipos. Si un tipo representa una proposición, la misma será válida si se encuentra un elemento
del tipo. Tenemos entonces una correspondencia entre la lógica constructivista y un lenguaje de programación con tipos de datos
con las características que vimos previamente. Esta correspondencia fue observada por primera vez por Haskell Curry y William
Howard y se la conoce como \textbf{``isomorfismo de Curry-Howard''}.
Si observamos las reglas de la lógica proposicional podemos ver que las reglas de formación,
de introducción y eliminación de los conectivos lógicos se corresponden con tipos. Por ejemplo
la fórmula $A \wedge B$ se puede probar si se pueden probar las fórmulas $A$ y $B$. Podemos ver que
el constructor del tipo $\cprod{A}{B}$ se corresponde directamente con esta regla: Para construir
un elemento de $\cprod{A}{B}$, se necesita un elemento $\tjud{a}{A}$ y un $\tjud{b}{B}$.
De la misma manera que lo hicimos con el conectivo $\wedge$, podemos hacer una analogía con el resto de la lógica
proposicional:
\begin{itemize}
\item $\mathbf{True}$ se corresponde con $\mathbf{1}$.
\item $\mathbf{False}$ se corresponde con $\mathbf{0}$.
\item $\mathbf{A \wedge B}$ se corresponde con $\cprod{A}{B}$.
\item $\mathbf{A \vee B}$ se corresponde con $A + B$.
\item $\mathbf{A \Rightarrow B}$ se corresponde con $A \rightarrow B$.
\item $\mathbf{A \Leftrightarrow B}$ se corresponde con $\cprod{(A \rightarrow B)}{(B \rightarrow A)}$.
\item $\mathbf{\neg A}$ se corresponde con $A \rightarrow \mathbf{0}$.
\end{itemize}
Si consideramos la lógica de predicados, agregando los cuantificadores universal y existencial, tenemos
también la siguiente correspondencia, como lo observamos previamente:
\begin{itemize}
\item ($\forall x$, $P\,x$) se corresponde con $\depFun{x}{A}{B}$.
\item ($\exists x$, $P\,x$) se corresponde con $\depPair{x}{A}{B}$.
\end{itemize}
\section{Agda}
En esta sección presentaremos el lenguaje de programación con tipos dependientes \textbf{Agda}.
Estudiaremos brevemente alguna de sus características y la manera
de expresar los principales conceptos de la teoría de tipos.
Se pretende que la sección sea autocontenida, salvo algunas
menciones a la sección anterior, y por lo tanto una introducción para quien
se esté iniciando en la programación en Agda. Por otro lado, si el lector
conoce conceptos como; tipos de datos, pattern matching, funciones dependientes,
familias de tipos de datos, sentencia \verb|with|, argumentos implícitos,
dotted patterns, etc. una opción puede ser dirigirse directo a la sección
siguiente.
Es importante mencionar que todos los ejemplos fueron compilados con
Agda 2.4.2.
\subsection{Introduciendo Agda}
En lenguajes como Haskell existe una división bien marcada entre los tipos
(\verb|Int|, \verb|Bool|, \verb|String|, etc) y los valores (\verb|0|,
{\verb|True|, {\verb|"Haskell"|, etc). En cambio en lenguajes con
tipos dependientes, como Agda, esta separación es menos clara.
Para ejemplificar esto consideremos los clásicos vectores de algún tipo
con tamaño fijo. Podemos pensar en la siguiente implementación en
Haskell,
\begin{verbatim}
data Zero
data Suc n
data Vec a n where
Empty :: Vec a Zero
Const :: a -> Vec a n -> Vec a (Suc n)
\end{verbatim}
\noindent donde definimos los tipos \verb|Zero| y \verb|Suc n| sin constructores
con la finalidad de usarlos como \textit{valores} y de esta manera restringir
el tipo \verb|Vec|. A continuación podemos implementar la función que toma
el primer elemento de un vector, como
\begin{verbatim}
head :: Vec a (Suc n) -> a
head (Const e _) = e
\end{verbatim}
La particularidad de esta implementación es que durante el chequeo de tipos
estamos eliminando la posibilidad de escribir \verb|head Empty|, donde
si recordamos la implementación de \verb|head| realizada en la introducción,
esta nueva implementación sobre listas de cierto tamaño nos ahorra la prueba de
que el tamaño de la lista es mayor que 0. Ahora bien,
esta implementación parece razonable
y cómoda de entender; el vector vacío tiene tamaño \verb|Zero| y si agrego
un elemento \verb|e| a un vector con tipo \verb|Vec a n| (de tamaño \verb|n|) esto
me construye un vector de tamaño \verb|Suc n|. Lamentablemente la utilización
de los tipos como valores no sólo no parece una forma prolija de usar los
tipos, si no que para utilizar una idea similar pero ahora donde los valores a
\textit{imitar} con tipos son mas complejos, podría derivar en implementaciones
difíciles de entender o directamente malas.
En este punto es donde la \textit{separación menos clara} entre tipos y valores de Agda
(y los lenguajes con tipado dependiente en general) que mencionábamos antes
nos puede resultar muy útil.
A continuación introducimos distintas características del lenguaje Agda
con el fin de implementar el tipo de los vectores de un cierto tipo y tamaño,
como anteriormente realizamos en Haskell.
\subsection{Tipos de datos y pattern matching}
Empecemos introduciendo la forma de declarar tipos de datos
\textit{como los de Haskell}. Podemos definir el tipo \verb|Nat| de los
números naturales de la siguiente manera:
\begin{verbatim}
data Nat : Set where
zero : Nat
suc : Nat → Nat
\end{verbatim}
\verb|Nat| tendrá el tipo \verb|Set|, que representa el tipo de los tipos de datos
(en la sección 2 le llamamos $U$) y sus constructores serán \verb|zero| y \verb|suc|,
mediante los cuales podemos obtener elementos del tipo que estamos definiendo.
Podemos definir funciones por
pattern matching como hacemos en haskell, pero con la importante salvedad de que
estamos obligados a cubrir todos los casos debido a que Agda no admite programas
que no terminen, de ser el caso el chequeador de tipos nos dará un error.
Como ejemplo de función, definimos la suma de dos naturales. Aprovechamos para mencionar que
Agda es muy flexible con los nombres de función (constructores, tipos, etc) cualquier
secuencia de símbolos sin espacios puede ser considerado un nombre válido, además
para declarar operadores infijos se hace uso del \verb|_| de manera tal que al
usar el operador se puede utilizar por ejemplo como \verb|n + m| o \verb|_+_ n m|.
\begin{verbatim}
_+_ : Nat → Nat → Nat
zero + m = m
suc n + m = suc (n + m)
\end{verbatim}
Teniendo en cuenta la exigencia de terminación que impone Agda, notar que
el segundo caso de pattern matching es válido ya que el primer argumento de
la suma se vuelve más chico.
\subsection{Funciones dependientes y argumentos implícitos}
Hasta acá repasamos como escribir tipos de datos y funciones de Haskell, en Agda.
Introducimos ahora las funciones dependientes, como funciones en las que en su signatura
el tipo resultante puede depender de los valores de los argumentos.
En Agda podemos escribir \verb|(a : A) → B| como el tipo de una función que toma un \verb|a : A| y
retorna algo de tipo \verb|B|, en el cual probablemente aparezca \verb|a|(lo que se corresponde
con el tipo $\depFun{x}{A}{B}$ que introdujimos en la sección 2). Podemos definir la
función identidad por ejemplo como,
\begin{verbatim}
id : (A : Set) → A → A
id _ = λ x → x
\end{verbatim}
Esta función toma un tipo \verb|A| y retorna la función identidad para ese tipo.
Ahora bien, la implementación de la función identidad que dimos toma como primer
argumento un tipo con el fin de modelar el polimorfismo de tipos, pero ahora sucede
que para utilizar la función \verb|id| tenemos que suministrarle el tipo y la idea
de las funciones polimórficas es evitar esto, dejando que el tipo sea inferido por
el chequeador de tipos. Este problema se soluciona utilizando argumentos implícitos
los cuales se escriben encerrándolos entre llaves. Siguiendo con el ejemplo ahora
podemos implementar la función identidad de la siguiente manera
\begin{verbatim}
id : {A : Set} → A → A
id = λ x → x
\end{verbatim}
La declaración de los argumentos como implícitos nos exime de tener que pasarlos al
llamar a la función, pero existe la posibilidad de pasarlos si es que hiciera
falta o usarlos como un argumento normal en la declaración. La manera de usar a estos
argumentos implícitos es mediante llaves como cualquier otro argumento o además de las
llaves utilizando el nombre de la variable escrita en el tipo de la función
\begin{verbatim}
map : {A : Set} {B : Set} → (A → B) → List A → List B
map {A} {B} _ [] = []
map {B = B} f (x ∷ xs) = f x ∷ map f xs
\end{verbatim}
En este ejemplo se puede ver que para el caso de pattern matching en la lista vacía estamos usando
ambos argumentos implícitos utilizando el orden en el que aparecen en el tipo
de nuestra función. Otra posibilidad es, como mencionamos antes, utilizar
el nombre de la variable del tipo, por ejemplo en el caso en que la lista tiene
al menos un elemento podemos utilizar el nombre de variable \verb|B|,
de manera de evitar tener que utilizar el orden y como consecuencia tener
que mencionar todos los argumentos implícitos (previos) para referencia al argumento deseado.
\begin{verbatim}
mapFromNat : {B : Set} → (Nat → B) → List Nat → List B
mapFromNat = map {Nat}
mapToNat : {A : Set} → (A → Nat) → List A → List Nat
mapToNat = map {B = Nat}
\end{verbatim}
Como antes, para pasar argumentos implícitos a una función podemos de nuevo
utilizar el orden que determina el tipo de la función y el nombre de la variable
en el tipo. Así entonces, en \verb|mapFromNat| estamos fijando el tipo \verb|A|
y en \verb|mapToNat| estamos fijando el tipo \verb|B|.
\subsection{Familias de tipos de datos}
Veamos ahora cómo podemos definir familia de tipos indexadas implementando el
tipo de los vectores de un cierto tipo y tamaño
\begin{verbatim}
data Vec (A : Set) : Nat → Set where
empty : Vec A zero
const : {n : Nat} → A → Vec A n → Vec A (suc n)
\end{verbatim}
diremos que \verb|Vec| está paremetrizado por el tipo \verb|A|
e indexado por el tipo \verb|Nat|. Dado un tipo \verb|A|, \verb|Vec A| tendrá
tipo \verb|Nat → Set|, es decir será una familia de tipos indexada por \verb|Nat|, por
lo tanto tomando un \verb|n : Nat|, \verb|Vec A n| es un tipo; el de los vectores de tipo
\verb|A| y tamaño \verb|n|. Luego, \verb|empty| será un vector de tamaño cero con
tipo \verb|Vec A zero| y dado un elemento \verb|e : A| y un vector \verb|v : Vec A n|,
\verb|const e v| nos construye un vector de tamaño \verb|suc n| con tipo \verb|Vec A (suc n)|.
Esta implementación es bastante similar a la hecha anteriormente
en Haskell, pero con la diferencia importante de que \verb|n| es un valor y no un tipo.
Ahora podemos implementar la función que toma el primer elemento
\begin{verbatim}
head : {A : Set} {n : Nat} → Vec A (suc n) → A
head (const x _) = x
\end{verbatim}
Es importante notar que la condición de exhaustividad del checkeador de tipos
se cumple ya que no existe otra forma de construir algo de tipo \verb|Vec A (suc n)|
que no sea con el constructor \verb|const|.
\subsection{Más sobre pattern matching}
\subsubsection{Dotted patterns}
Consideremos la función \verb|zip| sobre vectores que dados dos vectores
de igual tamaño nos construye un vector de pares. El tipo \verb|Vec| nos permite
agregar la restricción de que el tamaño de los vectores sea el mismo (a diferencia
de la implementación de \verb|zip| con listas, donde no podríamos tener esta restricción).
\begin{verbatim}
zip : {A B : Set} {n : Nat} → Vec A n → Vec B n → Vec (A × B) n
zip empty empty = empty
zip (const a as) (const b bs) = const (a , b) (zip as bs)
\end{verbatim}
Notar que el tipo de los elementos del vector resultante de haber
aplicado \verb|zip| a dos vectores es \verb|A × B|, este tipo
se corresponde con el producto cartesiano definido en la sección 2.
Podemos pensar ahora qué ocurre si hacemos pattern matching en el
argumento \verb|n : Nat| en el segundo caso
\begin{verbatim}
zip {n = (suc n)} (const {ma} a as) (const {mb} b bs) = ...
\end{verbatim}
Es importante mencionar que, teniendo en cuenta la igualdad que se encuentra
entre llave en uno de los casos de pattern matching, el \verb|n| del
lado izquierdo se refiere al nombre de variable
del tipo y el \verb|n| del lado derecho es una variable fresca donde hacer
pattern matching, cada vez que hablemos de \verb|n| nos referiremos a este
último. Ahora bien, este \verb|n| debería ser (y lo es) el único tamaño posible
de las listas \verb|as| y \verb|bs|, es decir \verb|ma = n = mb|.
¿cómo podemos remarcar esto en el caso de pattern matching?; la solución esta en
usar dotted patterns, para esto prefijamos el caso de pattern matching con un punto
(\verb|.|) de manera de marcar que el valor fue deducido por el chequeo de tipos
y no por pattern matching. Luego podemos escribir
\begin{verbatim}
zip {n = (suc n)} (const {.n} a as) (const {.n} b bs) = ...
\end{verbatim}
Como se menciona en \cite{dependentlytyped}:
La regla para saber si un argumento debe tener prefijado el punto es: \textit{Si existe
un único valor para un argumento, este debe estar prefijado por el punto}.
\subsubsection{Pattern con with}
Introduzcamos ahora la sentencia \verb|with| que nos permite agregar mas
argumentos a la función y realizar pattern matching de la forma usual, teniendo
en cuenta la condición de exhaustividad. Así por ejemplo
esto nos permite combinar dos o mas argumentos y hacer pattern matching sobre su
resultado. La siguiente siguiente función de ejemplo retorna la cantidad de elementos
que cumplen una cierta propiedad sobre un vector.
\begin{verbatim}
#-filter : {n : Nat} {A : Set} → (A → Bool) → Vec A n → Nat
#-filter p empty = zero
#-filter p (const x xs) with p x
... | true = suc (#-filter p xs)
... | false = #-filter p xs
\end{verbatim}
Luego habiendo definido la función \verb|#-filter| podemos implementar la función
filter sobre los vectores.
\begin{verbatim}
filter : {n : Nat} {A : Set} →
(p : A → Bool) → (xs : Vec A n) → Vec A (#-filter p xs)
filter p empty = empty
filter p (const x xs) with p x
... | true = const x (filter p xs)
... | false = filter p xs
\end{verbatim}
\subsubsection{Pattern absurdo}
En la introducción se muestra la implementación de la función \verb|head| que
retorna el primer elemento de una lista con tipo \verb|List A| y se sugiere ignorar
el primer caso de pattern (\verb|head [] ()|). A este tipo de casos de pattern
los llamaremos patterns absurdos y se escriben como \verb|()|, la idea es que; así
como con dotted patterns podíamos simbolizar la existencia de un único valor, el
pattern absurdo nos permite decir que ese caso nunca puede ocurrir ya que es
imposible construir un valor de este tipo.
Así por ejemplo, introduciendo rápidamente una posible implementación para el
tipo \verb|≤|
\begin{verbatim}
data _≤_ : Nat → Nat → Set where
z≤n : {n : Nat} → zero ≤ n
s≤s : {m n : Nat} (m≤n : m le n) → suc m ≤ suc n
\end{verbatim}
y definiendo al \verb|<| como
\begin{verbatim}
_<_ : Nat → Nat → Set
m < n = suc m ≤ n
\end{verbatim}
podemos analizar el ejemplo de la función \verb|head|, el pattern
absurdo es ocasionado por \verb|head []| cuyo tipo, reemplazando valores,
será \verb|head [] : zero < zero → a|. Donde es claro notar que es imposible
construir un valor para el tipo \verb|zero < zero| o \verb|suc zero ≤ zero|
\footnote{Usamos \verb|zero| y \verb|suc zero| para evidenciar de forma mas clara
la imposibilidad de construir un valor de tal tipo. Sin embargo, escribir \verb|0| y \verb|1|
sería totalmente valido.} .
Finalmente, cuando sucede que existe un pattern absurdo no tenemos que
preocuparnos por dar una definición de función para ese caso.
\subsection{Tipos proposicionales y valores como pruebas}
Al comienzo de la sección anterior se menciona que, considerando a un tipo
\verb|A| como una proposición podemos decir que esta proposición es
cierta si podemos construir un valor \verb|a| que tenga tipo \verb|A|.
Podemos pensar entonces como será la implementación de la proposición \verb|False| (\verb|⊥|)
como tipo de dato, la idea es que nunca podamos construirnos un valor de este tipo y
por lo tanto podemos implementarlo simplemente como un tipo vacío
\begin{verbatim}
data ⊥ : Set where
\end{verbatim}
El hecho de nunca poder construir un elemento del tipo \verb|⊥| y con ayuda del pattern absurdo,
podemos definir la función que llamaremos \verb|⊥-elim| y que implementará la idea
de que \textit{falso implica cualquier cosa}
\begin{verbatim}
⊥-elim : {Whatever : Set} → ⊥ → Whatever
⊥-elim ()
\end{verbatim}
Por otro lado, ¿cómo será la implementación de la proposición \verb|True| (\verb|⊤|)?; esta
vez necesitamos poder construir siempre un elemento, podemos entonces implementarlo de la siguiente
manera \footnote{La implementación original se realiza utilizando records en \verb|Data.Unit|}
\begin{verbatim}
data ⊤ : Set where
tt : ⊤
\end{verbatim}
Siguiendo los pasos de la sección anterior, podemos introducir la igualdad de valores de un tipo,
es decir que dados dos valores de un mismo tipo, \verb|a : A| y \verb|b : A| vamos
a escribir \verb|a ≡ b : Set| para decir que los valores son iguales.
\begin{verbatim}
data _≡_ {A : Set} (x : A) : A → Set where
refl : x ≡ x
\end{verbatim}
Es importante notar que la definición de igualdad que estamos dando dice, dado
un tipo \verb|A| y un elemento \verb|x : A| obtenemos una familia de
\textit{pruebas} o \textit{valores} tal que se es igual a \verb|x|.
Podemos también implementar la negación proposicional, dado que tenemos
un tipo \verb|P|, escribimos \verb|¬ P| para referirnos a la negación del tipo.
Su implementación es directa de pensar en \textit{Principio de explosión}, es
decir, cualquier cosa es demostrable a partir de una contradicción
\begin{verbatim}
¬_ : Set → Set
¬ P = P → ⊥
\end{verbatim}
Ahora bien, habiendo definido \verb|¬| podemos definir un tipo de dato
que implemente la idea de que una proposición, o bien es cierta, o bien es falsa,
construyendo la prueba que corresponda. Podemos pensar a este tipo de dato
como el tipo \verb|Bool| pero con el agregado de que acarrea el valor o prueba
del por qué la proposición es cierta o porque es falsa.
\begin{verbatim}
data Dec (P : Set) : Set where
yes : P → Dec P
no : ¬ P → Dec P
\end{verbatim}
Por otro lado, podemos presentar además la noción de cuantificador existencial,
la cual como mencionamos antes, en la sección 2 se
corresponde con la idea de producto dependiente.\\
Mostrar la implementación esta fuera de la idea de esta sección;\footnote{Un
lector mas interesado puede encontrar la definición en el módulo Data.Product} y
por esta razón simplemente presentamos la sintaxis necesaria y algunas
funciones útiles. Considerando los tipos \verb|A : Set| y \verb|B : A → Set|
podemos escribir el tipo del producto dependiente como
\verb|Σ[ x ∈ A ] B x|\footnote{Otra posibilidad
es escribir \verb|∃ (λ x → B x)|, que se puede leer
como \textit{existe un x tal que B x}}, donde el constructor será \verb|_,_| y además
dispondremos de dos funciones \verb|proj₁| y \verb|proj₂| que nos
retornaran la primera y segunda componente de un par.
Podemos entonces usando el producto dependiente implementar una
versión del \verb|filter| mejorada, la cual no requiere calcular
de antemano el tamaño del nuevo vector con los elementos filtrados.
Tomando entonces \verb|Nat : Set| y \verb|Vec A : Nat → Set| podemos
escribir el producto dependiente \verb|Σ[ m ∈ Nat ] Vec A m|.
\begin{verbatim}
filter : {n : Nat} {A : Set} →
(A → Bool) → Vec A n → Σ[ m ∈ Nat ] Vec A m
filter p empty = zero , empty
filter {n = suc n'} p (const y vec) with p y
... | false = filter p vec
... | true = let (m , vec') = filter p vec
in (suc m , const y vec')
\end{verbatim}
\subsubsection{Pruebas}
Hasta acá hemos hablado de las proposiciones y los tipos, pero poco acerca
de los valores y las pruebas. Haciendo uso de lo que tenemos definido hasta
el momento y definiendo algunas cosas nuevas probemos algunas propiedades.
Podemos comenzar definiendo el tipo \textit{ser par} que nos permite construir
una prueba de que un natural es par, si este efectivamente lo es.
\begin{verbatim}
data IsEven : Nat → Set where
pz : IsEven zero
psuc : {n : Nat} → IsEven n → IsEven (suc (suc n))
\end{verbatim}
Veamos algunos ejemplos para entender este tipo de dato, podemos
empezar probando que el \verb|0|\footnote{En general usaremos las versiones
sintácticamente azucaradas, salvo cuando haga falta mencionar su
construcción original} es par, escribiendo una función con tipo
\verb|IsEven zero|, donde la prueba es utilizar el constructor
\verb|pz| directamente
\begin{verbatim}
zeroIsEven : IsEven zero
zeroIsEven = pz
\end{verbatim}
pensemos ahora en probar que \verb|2| y \verb|4| son pares teniendo
en cuenta que \verb|2 = suc (suc zero)| y que \verb|4 = suc (suc 2)|
de igual manera que con el \verb|0|, nos podemos construir funciones con tipos
\verb|IsEven 2| y \verb|IsEven 4|. Enfoquémonos en probar paso por paso
que dos es par; tenemos que construir un valor de tipo \verb|IsEven (suc (suc zero))|,
el constructor \verb|pz| no nos ayuda, pero si nos sirve el constructor \verb|psuc|, ahora
bien este constructor nos construye algo de tipo \verb|IsEven (suc (suc zero))| siempre
y cuando podamos suministrarle algo de tipo \verb|IsEven zero|, afortunadamente \verb|pz| ahora
sí nos sirve, por lo tanto la prueba queda
\begin{verbatim}
twoIsEven : IsEven 2
twoIsEven = psuc pz
\end{verbatim}
Si pensamos ahora la prueba de que \verb|4| es par, pasa algo parecido; necesitamos
construir un valor de tipo \verb|IsEven (suc (suc (suc (suc zero))))|, para esto
podemos utilizar de nuevo \verb|psuc| para lo cual necesitamos una prueba o valor de
tipo \verb|IsEven (suc (suc zero))|, por suerte esta es la prueba de que dos es par
\begin{verbatim}
fourIsEven : IsEven 4
fourIsEven = psuc twoIsEven
\end{verbatim}
Por otro lado, es interesante ver que no podemos probar que, por ejemplo, \verb|3| es
par. Utilicemos el mismo razonamiento que para \verb|2| y \verb|4|, necesitamos
construirnos un valor de tipo \verb|IsEven (suc (suc (suc zero)))|.
Usar \verb|pz| es imposible, pero podemos utilizar \verb|psuc|, luego necesitamos
construirnos un valor de tipo \verb|IsEven (suc zero)|; pero acá no podemos usar
\verb|pz|, ni \verb|psuc|.
Esta forma de pensar la manera de construir los valores o pruebas nos induce una
noción inductiva, ¿será posible entonces implementar una función que dado un
natural nos construya una prueba, si es que puede, de que este natural es par?; La
respuesta es sí y para esto podemos hacer uso del tipo \verb|Dec|. Con el cual
vamos a tener, o bien una prueba de que el natural es par o bien una prueba de
la negación.