Programación con Ocaml
Vladimir Támara Patiño. Carlos F. Rodriguez
2004. Dominio público.
Este escrito se dedica a nuestro Padre Creador, a su santo
Espíritu y a Jesús su Hijo y maestro nuestro.
Agradecemos las definiciones aportadas por Edwin Castiblanco y
las correcciones de David Cruz Ramos y Edwin Castiblanco.
-
LOGRO: Identifica características de Ocaml
- LOGRO: Maneja conceptos de programación funcional pura
- LOGRO: Maneja conceptos de tipos
1.1 Vistazo de programación funcional y Ocaml
Indicadores de logro:
-
INDICADOR: Puede reducir expresiones que incluyan funciones y aplicación funcional
- INDICADOR: Asocia expresiones a nombres empleando let
- INDICADOR: Define funciones sencillas que emplean tipos básicos
- INDICADOR: Deriva tipo de expresiones sencillas que incluyan tipos básicos
- INDICADOR: Emplea la documentación de Ocaml
- INDICADOR: Emplea el entorno interactivo de Ocaml
Vistazo a Ocaml
Ocaml es uno entre muchos lenguajes funcionales (como Lisp, Scheme, Haskell)
que además de programación funcional pura, permite programar empleando
características imperativas, algunas características propias
de lenguajes orientados a objetos e incluye un sistema de módulos que
facilita el desarrollo de proyectos grandes (con signaturas y functores).
Se ha desarrollado desde 1992, con base en su predecesor Caml Special Light
(inicio de la década 1990), cuyo predecesor a su vez es Caml Light (1985-1990)
que pertenece a la familia de lenguajes de programación funcionales ML
(que se inicio en la década de 1970).
Como los demás lenguajes funcionales emplea un recolector de basura que
permite en muchos casos desentenderse del manejo de memoria dinámica
(el recolector elimina automáticamente memoria que no se usa --de forma
que no es necesario usar free como en C).
Cuenta con un sistema de tipos fuerte, con un derivador de tipos que
facilita el chequeo estático de programas sin requerir del programador
tiempo en declaración explicita de tipos. Además de tipos básicos (enteros,
flotantes, booleanos, caracteres y cadenas) ofrece diversas formas de construir
nuevos tipos como tuplas, arreglos, listas, registros, conjuntos, streams y
facilidades para definir tipos recursivos, polimórficos y paramétricos.
En cuanto a interpretación/compilación se basa en un modelo de máquina
abstracta portable (máquina Zinc). Cuenta con un interprete (ocaml)
que es a su vez un entorno interactivo útil para experimentar y dos
compiladores:
-
ocamlc que genera bytecode utilizable por la máquina Zinc y portable
a toda arquitectura donde se ha portado la máquina Zinc (incluyendo
x386/OpenBSD, x386/Linux, x386/Windows 95-NT, Mac, Digital, Solaris, IRIX)
- ocamlopt que genera binarios para diversas arquitecturas
(incluyendo procesadores i386 con sistemas operativos OpenBSD y Linux).
Incluso hay un compilador sobre la marcha (JIT o Just in Time Compiler)
que traduce en línea bytecode a código de máquina [5].
Cuenta con herramientas que facilitan el desarrollo como un depurador
simbólico; herramientas para análisis léxico y sintáctico; herramientas
para automatizar compilación de proyectos grandes; editor y visualizador
gráfico de módulos; modos para diversos editores; preprocesador muy
configurable pues permite redefinir la sintaxis del lenguaje; extractor de
documentación técnica; medidor de desempeño.
La distribución básica incluye diversas librerías portables para
manejo de estructuras de datos (e.g. tablas de hashing, conjuntos, pilas),
gráficas, aritmética de precisión arbitraria, multi-threading,
emulación de Unix, interfaz para Tcl/Tk y C. Hay también disponibles
librerías desarrolladas por diversas personas y grupos (ver índices
en:
http://caml.inria.fr/humps/caml_latest.html y
http://www.npc.de/ocaml/linkdb/)
Ciertos tipos de problemas pueden programarse de forma MUY eficiente
empleando Ocaml como lo muestran los resultados en las competencias
internacionales ICFP realizadas desde 1998, en los que han participado
grupos de programadores empleando su lenguaje de programación favorito
(incluyendo lenguajes imperativos como C, orientados a objetos como
Java y funcionales como Ocaml). De las 5 versiones de 1998 a 2002,
equipos que han programado en Ocaml han ocupado el primer puesto en
3 oportunidades, el segundo puesto en 2 y el tercero en uno.
En cuanto a licencia, las herramientas de Ocaml emplean la GPL, mientras
que las librerías la LGPL la cual permite poner la licencia que
se desee a programas producidos con Ocaml y enlazados con las librerías.
Programación funcional pura
En programación funcional pura no hay estados, ni asignación, ni
secuenciación, sólo funciones y aplicación funcional. En Ocaml
podría programarse de forma funcional pura usando funciones
anónimas, un ejemplo de una función anónima es:
fun x -> x+1;;
que define la función sucesor. Note que esta función
asocia una variable x con una expresión x+11.
Esta función podría aplicarse al número 2, por ejemplo:
(fun x -> x+1) 2;;
Operacionalmente aplicar un valor a una función significa remplazar
en la expresión la variable de la función por el valor2. En este ejemplo la aplicación resultaría en
2+1
Esta expresión es a su vez una aplicación funcional que al
reducirse3 da 3.
En programación funcional, una función es un ente de primera clase como
las constantes y variables. Es decir que puede emplearse en expresiones
y usarse como se usan constantes y variables. Por ejemplo :
fun x-> (fun y -> (x+y)/2)
es una función cuya expresión incluye otra función. Note que al aplicar
esta función a un valor se obtendría otra función:
(fun x-> (fun y -> (x+y)/2)) 5
®b
fun y -> (5+y)/2
Que promediaría un valor (y) con la constante 5.
Como abreviación para fun x -> fun y -> exp
puede emplearse:
fun x y -> exp4.
let asocia nombres con expresiones
Para asociar un nombre con una expresión se emplea
let nombre = expresion;;
por ejemplo:
let prom=(fun x -> (fun y -> (x+y)/2));;
asociará el nombre prom con una función que recibe dos enteros
y calcula el promedio entre ellos.
Puede usarse por ejemplo así:
prom 2 6
que reducirá a 4. O por ejemplo
let prom10=prom 10;;
que asociará el nombre prom10 a una función que recibe un entero
y calcula el promedio entre 10 y ese entero.
La palabra reservada let también permite abreviar la
definición de funciones:
let f=fun x->exp
puede escribirse como
let f x=exp
Así que la función prom pudo haberse escrito como
let prom x y= (x+y)/2;;
Derivación de tipos
Los tipos básicos en Ocaml son enteros int (e.g 2, -100),
flotantes (e.g -0.23) float ,
caracteres char (e.g 'a') y cadenas string
(e.g "Dios nos ama"). Para
cada uno de estos tipos hay diversas funciones definidas.
Ocaml es fuertemente tipado es decir que toda expresión tiene un tipo
único y sólo pueden aplicarse valores a funciones cuando el tipo
del valor corresponde al tipo esperado por la función.
Por ejemplo (+) es la función que suma dos enteros, mientras (+.) es la que
que suma dos flotantes (en general los operadores entre enteros tienen
una análogo para flotantes cuyo nombre termina con el carácter punto).
La ventaja de este tipado fuerte es la posibilidad de derivar automáticamente
el tipo de expresiones (librando al programador de esa tarea). Por ejemplo
una expresión como
fun y -> y +. y
es una función que recibe un flotante y retorna un flotante, lo derivamos
sabiendo que (+.) recibe dos flotantes y retorna un flotante.
1.1.2 Hacía la práctica
Sobre documentación
Hay abundante documentación sobre Ocaml, aunque la mayoría esta
en inglés, la referencia más exacta aunque tal vez no la más
fácil es la guía del usuario (ver [3]).
Para aprender además de estas guías sugerimos Developing
Applications in Ocaml (ver [1]).
Sobre instalación
Hay binarios precompilados de la versión más reciente de
Ocaml para diversas plataformas (incluyendo x86/Windows) en el
sitio de distribución http://caml.inria.fr/ocaml. Para seguir
estas guías sugerimos de forma especial emplearlo en un ambiente tipo
Unix (puede ser un sistema operativo de fuentes abiertas como
OpenBSD o Linux). Para algunas plataformas tipo Unix
hay precompilados disponibles, en otras tipo Unix pueden descargarse
las fuentes y compilarse (proceso que generalmente no tiene
complicaciones). Junto con las fuentes más recientes se
distribuye un documento (INSTALL) que explica como realizar la compilación
y que hacer en caso de algunas dificultades.
En caso de compilar, sugerimos que primero instale Tcl/Tk y
se asegure de compilar con soporte para LablTk, de forma que
pueda crear interfaces gráficas desde Ocaml usando Tcl/Tk.
Uso del entorno interactivo
Una vez instalado Ocaml puede emplearse el entorno interactivo para
experimentar y hacer los primeros programas. Se inicia con:
ocaml
Allí pueden ingresarse construcciones válidas del lenguaje terminadas
con ;; para que sean reducidas.
Por ejemplo al ingresar la constante 2
# 2;;
- : int = 2
# es el prompt de este entorno interactivo 2;; es el dato
ingresado y la respuesta del entorno interactivo es el tipo de
la expresión reducida.
Por ejemplo al ingresar una función que eleva un entero al cuadrado:
# fun x -> x*x;;
- : int -> int = < fun >
el entorno interactivo analiza tipos y contesta indicando que
la expresión es una función que recibe un entero y retorna un
entero.
Al ingresar una aplicación funcional el entorno realiza la reducción
completa y retorna el tipo (y de ser posible el valor) de la
expresión resultante:
# (fun x -> x*x) 5;;
- : int = 25
Note que las funciones que se definan tienen asociatividad derecha, es
decir:
fun x -> fun y -> x+y;;
equivale a
fun x -> (fun y -> x+y);;
el tipo que Ocaml responde para esta función es:
int -> int -> int = <fun>
que puede leerse como
int -> (int -> int) = <fun>
Es decir una función que recibe un entero y devuelve una función de
enteros en enteros.
A diferencia por ejemplo de:
fun f -> 1+(f 5);;
cuyo tipo es:
(int -> int) -> int
es decir es una función que retorna un entero y que recibe una función
de enteros en enteros.
Además de poder ingresar expresiones directamente en el entorno interactivo
pueden cargarse de un archivo fuente empleando:
#use "archivo";;
O desde la línea de comandos pueden interpretarse las expresiones de un
archivo ejecutando:
ocaml archivo
1.1.3 Lecturas recomendadas
Un vistazo más completo de Ocaml puede consultarse en:
http://caml.inria.fr/ercim.html y en
http://caml.inria.fr/FAQ/general-eng.html
Puede consultar más sobre programación funcional pura y sus bases
teóricas (calculo l) en los 3 primeros capítulos
de [2].
Para lograr la instalación además de la documentación oficial de Ocaml
puede consultar el capítulo correspondiente de [1].
1.1.4 Ejercicios para afianzar teoría
-
¿Qué expresión resulta tras aplicar el valor 3 a la siguiente función
(haga sólo un paso de reducción)?:
fun z -> z+z;;
-
¿Qué valor resulta tras hacer la reducción completa de la siguiente expresión?:
(fun var1 -> (fun var2 -> (var1*var1)+var2)) 2 3;;
-
La función suma puede verse en una notación prefija así: (+) 2 3 que
equivale a 2+3. ¿Qué valor se obtiene al reducir por completo la
siguiente expresión?:
(fun v -> (+) v 3) 5 ;;
-
¿Qué valor resulta tras reducir por completo la siguiente aplicación?:
(fun f -> 1+(f 5)) (fun y -> 2*y);;
-
¿Qué valor resulta tras reducir por completo la siguiente aplicación?:
(fun f x y -> (f x)+(f y)) (fun y -> 2*y) 3 4;;
-
Consulte en la documentación de Ocaml la lista de operadores. ¿En que sección
del manual está dicha lista?
-
¿Cual es el tipo de la siguiente expresión?
fun s t -> s t
-
Busque en la documentación del módulo Pervasives una función que
permita convertir un entero en flotante. Después defina una función
que reciba una variable entera x y retorne el flotante que resulta
de sumar x (como flotante) con 0.5.
-
Suponga que ha ingresado en el entorno interactivo la función dif:
let dif=fun f x h-> ((f (x+.h))-.(f x))/.h;;
que calcula la aproximación a la pendiente de la tangente de una función
en un punto x (usando como referencia el punto x+.h.
Emplee esta función para definir una nueva función con nombre
dif02 que haga lo mismo que la anterior pero fijando el valor de
h en 0.2.
-
Use la función dif02 del ejercicio anterior para calcular una
aproximación a la derivada de la función x2 en x=1.
¿Cuál es el error en la
aproximación? (el error entre un par de valores u y v es
u - v ).
1.1.5 Ejercicios de programación
-
En un archivo de nombre areas.ml asocie el nombre areatriangulo
a una función cuyo tipo sea float -> float -> float
y que calcule el área de un triángulo, dada la base (primer parámetro)
y la altura (segundo parámetros).
Asocie el nombre alturatriangulo a una función de tipo
float -> float -> float -> float que calcule la altura de
un triángulo, recibiendo como primer parámetro la base (lado mayor),
y como segundo y tercer parámetros la longitud de los otros dos lados.
Puede serle de utilidad emplear la función sqrt: float -> float que
retorna la raíz de un flotante no negativo.
Finalmente asocie el nombre areatriangulo2 a una función de tipo
float -> float -> float -> float que calcule el área de un
triángulo recibiendo los mismos parámetros de la función
alturatriangulo (sugerimos que emplee la
función alturatriangulo).
- 1
- En este ejemplo
decimos que la variable x está acotada en la expresión.
- 2
- Esta
aplicación en cálculo lambda (l), se llama reducción
beta (b).
- 3
- Reducir significa efectuar aplicaciones
funcionales reiteradamente hasta obtener una expresión que
no puede aplicarse más.
- 4
- fun permite definir una función,
function también aunque este último no permite la abreviación
mencionada.
Capítulo 2 Características funcionales
-
LOGRO: Maneja funciones y expresiones
2.1 Introducción
-
INDICADOR: Reconoce los tipos básicos que utiliza Ocaml.
- INDICADOR: Define funciones que emplean tipos agregados como tuplas y listas.
- INDICADOR: Define y emplea funciones recursivas.
- INDICADOR: Define funciones que emplean colecciones de datos como tuplas y listas.
Sistema de tipos.
Tanto programación funcional como en la mayoría de los lenguajes de
programación, utilizamos los tipos para definir conjuntos de datos.
Los tipos de datos se pueden clasificar en dos grupos: los tipos de datos
básicos (llamados también atómicos) y los tipos agregados. Ya hemos
trabajado con algunos tipos atómicos como los enteros y flotantes.
Los tipos agregados son colecciones de datos, dos muy usados son
listas y tuplas.
Tipos básicos (atómicos).
Enteros - Tipo int
El tipo int es el tipo asignado a los números enteros (negativos, cero
y positivos). El rango soportado depende de la arquitectura de hardware,
para arquitecutras comptibles con Intel i386 y en general de 32 bits
es -230 a 230-1 (otros tipos enteros con rangos diferentes o
independientes de arquitectura son Int32, Int64
y Nativeint --ver 3.2.1).
A continuación se ejemplifica la sintaxis de literales de tipo entero
y algunos operadores:
# 4;;
- : int = 4
# 4+5;;
- : int = 9
# (-) 9 5;;
- : int = 4
# 0x2 + 0X8;;
- : int = 10
# 0b101 + 0o13;;
- : int = 16
Note que el operador infijo -, puede usarse como prefijo poniendolo
entre paréntesis. Por defecto los enteros se especifican en decimal,
pueden especificarse enteros en hexadecimal precediendolos
con 0x o con 0X y usando los digitos adicionales
A, B, C, D, F. Pueden especificarse también números en octal
(precediendolos con 0o) y en binario (0b).
Los operadores utilizados para el manejo de enteros se presentan
a continuación (los que se presentan entre paréntesis son
operadores infijos, los otros son prefijos):
-
- : int -> int o - que retorna inverso
aditivo
- succ: int-> int sucesor
- pred: int-> int predecesor
- (+) : int -> int -> int suma
- (-) : int -> int -> int resta
- (*) : int -> int -> int multiplicación
- (/) : int -> int -> int división entera. Lanza la excepción
Divsion_by_zero si el denominador (i.e segundo parámetro) es 0.
Si tanto numerador
como denominador son positivos retorna el mayor entero menor o igual
a la división. Si ambos operandos son negativos da lo mismo que
la división entera entre sus valores absolutos, si uno de los operandos
es negativo da lo mismo que la división entera entre valores absolutos
pero con signo negativo.
- mod: int -> int -> int residuo de división entera. Si
el denominador (segundo parámetro) es 0 lanza la excepción
Divsion_by_zero. El comportamiento con negativos se presenta
con un ejemplo: 5 mod 2 es 1; -5 mod 2 es -1;
5 mod -2 es 1 y -5 mod -2 es -1. Note que se
cumple: x = (x / y) * y + (x mod y), con
abs(x mod y) <= abs(y)-1.
- abs: int -> int que retorna valor absoluto.
Las constantes max_int : int y min_int retornan máximo
y mínimo entero representable respectivamente, i.e. 230-1 y -230
en plataformas de 32 bits.
También hay definidas funciones que operan enteros a nivel de bits:
-
land : int -> int -> int conjunción lógica (da 1 sólo si
los dos bits son 1).
- lor : int -> int -> int disyunción (da 0 sólo si los dos
bits son 0).
- lxor : int -> int -> int o exclusivo (da 1 sólo si los dos
bits son diferentes).
- lnot : int -> int negación
- lsl : int -> int -> int corrimiento a la izquierda, el segundo
parámetro indica cuantas posiciones debe correrse el primer parámetro.
El segundo parámetro debe ser mayor o igual a 0 y menor que 32 en
plataformas de 32 bits o que 64 en plataformas de 64 bits (valores fuera
de este rango dan resultados no especificados).
- asr : int -> int -> int corrimiento a la derecha, el segundo
parámte indica el número de posiciones (la misma convención que lsr.
Flotante - Float - float
Se emplea para representar números de punto flotante. A continuación se
dan ejemplos de literales (i.e valores constantes) de tipo flotante:
-
0.3
- 2E1 que equivale a 20.0
- -2.2e-1 que equivale a -0.22
Note que para distinguir un flotante de un entero debe o bien usarse
el punto decimal o bien especificarse un exponente con e o con
E1
Los operadores utilizados para el manejo de números de punto
flotante son:
-
-. o -. que retorna inverso aditivo (prefijo)
- +. suma (infijo)
- -. resta (infijo)
- *. multiplicación (infijo)
- /. división (infijo)
- ** exponenciación (infijo)
Las siguientes son funciones incluidas en la librería básica2:
-
sqrt: float -> float raíz cuadrada
- exp: float -> float exponenciación
- log: float -> float logaritmo natural
- log10: float -> float logaritmo en base 10
- cos: float -> float coseno
- sin: float -> float seno
- tan: float -> float tangente
- acos: float -> float arcocoseno
- asin: float -> float arcoseno
- atan: float -> float arcotangente (para obtener ángulos precisos, de ser posible,
mejor emplear atan2 que se describe más adelante).
- cosh: float -> float coseno hiperbólico
- sinh: float -> float seno hiperbólico
- tanh: float -> float tangente hiperbólica
- ceil: float -> float techo, i.e el menor entero mayor o igual al argumento
- floor: float -> float piso, i.e el mayor entero menor o igual al argumento
- abs_float: float -> float valor absoluto de un flotante
- atan2: float -> float -> float que recibe cateto opuesto (y) y
cateto adyacente (x) y retorna el ángulo (teniendo en cuenta signos de
x e y).
- mod_float: float -> float -> float resto que queda tras
dividir el primer argumento (a) entre el segundo (b).
Retorna a -. n *. b donde n es el
piso de a /. b.
- frexp : float -> float * int retorna una pareja con la parte
significativa (x) y el exponente (n) del argumento. Cuando el argumento
es x=0 y n=0. Si el argumento (f) no es cero se cumple:
f=x2n y 0.5£ x £ 1.0.
- ldexp: float -> int -> float recibe dos argumentos x:float y n:int y retorna el flotante x2n.
- modf: float -> float * float returna una pareja con la parte
fraccional y entera del argumento.
- float: int ->float o float_of_int: int -> float retorna
el entero que reciben como número de punto flotante.
- truncate: float -> int y int_of_float que trunca la parte
decimal del argumento que recibe. El comportamiento es insospechado si
al truncar se sale del rango representable con enteros.
También pueden emplearse las constantes:
-
infinity que representa infinito positivo
- neg_infinity que representa infinito negativo
- nan (abrevia Not a Number), representa un valor que no es número
- max_float que corresponde el máximo flotante representable
- min_float que corresponde al menor flotante positivo que
no es cero y que no está denormalizado.
- epsilon_float mínimo flotante positivo x tal que
1.0 +. x <> 1.0.
así como la función classify_float que se explicará en la sección
sobre tipos.
Caracter - char
El tipo char implementa los carácteres del estándar ASCII. Para escribir un
carácter se utiliza comillas simples ejemplo: ' a', ' B', '9'. También se
puede especificar el codígo ASCII utilizando \ seguido del decimal
de 3 dígitos, dentro de comillas simples, ejemplos:
# '\126';;
- : char = ' '
# '\065';;
- : char = 'A'
# '\111';;
- : char = 'o'
También puede especificarse el ASCII en hexadecimal por ejemplo
'\x1F'. Y pueden usarse algunas secuencias de escape
para representar ciertos caracteres:
\\ |
barra invertida \ |
\" |
comillas " |
\' |
apostrofe ' |
\n |
cambio de línea (LF) |
\r |
retorno de carro (CR) |
\t |
tabulador horizontal (TAB) |
\b |
espacio atras (BS) |
Para hacer conversiones entre caracter y código ASCII se pueden utilizar
las funciones:
-
int_of_char : char -> int
- char_of_int : int -> char
o las siguientes definidas en el módulo Char:
Booleano - bool
Los tipos de datos booleanos sólo tienen dos valores : true y
false.
Los operadores de relación son polimórficos (i.e. operan sobre
diversos tipos) y retornan un valor booleano.
-
x = y igualdad3
- x <> y x no es igual a y
- x < y x menor que y
- x <= y x menor o igual que y
- x >= y x mayor o igual que y4
- x > y x mayor que y
Algunos ejemplos:
# (fun x y -> x=y) 5 5;;
- : bool = true \\
# (fun x y -> x=y) 5 54;;
- : bool = false \\
# (fun x y -> x<>y) 5 54;;
- : bool = true \\
# (fun x y -> x<=y) 5 54;;
- : bool = true \\
# (fun x y -> x<=y) 5 5;;
- : bool = true
Entre booleanos pueden emplearse los operadores:
-
not : bool -> bool que es negación (prefijo)
- (&&) : bool -> bool -> bool que es conjunción corta, si el
primer argumento evalua a false no evalua el segundo sino que retorna false.
- (||) : bool -> bool -> bool que es disyunción corta, si el
primer operador evalua a true retorna true
sin evaluar el segundo.
Cadenas - string
Una cadena es una secuencia de caracteres5. Las literales de tipo
cadena se escriben entre comillas, por ejemplo:
# "Dios es el único que lo sabe todo";;
- : string = "Dios es el único que lo sabe todo"
Pueden emplearse las mismas secuencias de escape del caso de
caracters (ver 2.1.1).
El único operador entre cadenas es el operador infijo que
concatena dos cadenas. Para convertir entre cadenas y otros
tipos básicos pueden emplearse las siguientes funciones:
string_of_bool : bool -> string
bool_of_string : string -> bool
string_of_int : int -> string
int_of_string : string -> int
string_of_float : float -> string
string_of_float : float -> string
float_of_string : string -> float
Tipos agregados.
Tuplas.
Una tupla es un elemento de un producto cartesiano de dos o más tipos.
Sintacticamente corresponde a una secuencia ordenada y heterogénea de
valores separados por comas. Por ejemplo:
let f = 2, true;;
En donde creamos una tupla de 2 elementos, un entero y un booleano.
Pueden obtenerse los valores individuales de una tupla empleando
let con tantas variables como valores tenga la tupla:
# let estudiante = "Carlos",18;;
val estudiante : string * int = ("Carlos", 18)
# let nombre,edad = estudiante;;
val nombre : string = "Carlos"
val edad : int = 18
En donde nombre tomara el valor string "Carlos" y
edad tomara el entero 18.
Como funciones sobre tuplas están predefinidas:
-
fst : 'a * 'b -> 'a que reotrna el primer elemento de una pareja.
- snd : 'a * 'b -> 'b que retorna el segundo elemento de una pareja.
Listas.
Una lista es una secuencia de valores homogénea (i.e todos son
valores del mismo tipo). A diferencia de las tuplas, la cantidad de
valores en una lista es variable.
Una lista puede ser una lista vacia (sin elementos). Una lista
que no es vacía siempre tiene una cabeza seguida de una cola, donde
la cabeza es un elemento y la cola es la lista de elementos, del mismo
tipo que la cabeza (la cola puede ser vacía).
Una lista en Ocaml se puede representar de las siguientes formas:
-
La lista vacía se representa de la forma [ ] .
- Una lista se puede representar entre paréntesis y los elementos separados
con punto y coma (;). Ejemplos:
# [1;2;3];;
- : int list = [1; 2; 3]
# ["Hola";"mundo";"ocaml"];;
- : string list = ["Hola"; "mundo"; "ocaml"]
- Utilizando el operador :: se construyen listas especificando
cabeza y cola por ejemplo:
# "Hola"::"mundo"::"ocaml"::[ ];;
- : string list = ["Hola"; "mundo"; "ocaml"]
# 1::[2;3;4;5;6];;
- : int list = [1; 2; 3; 4; 5; 6]
Ocaml utiliza para concatenar listas el simbolo arroba @ ejemplos:
# [1]@[1;2;3];;
- : int list = [1; 1; 2; 3]
# 1::2::[3;4;5]@[1];;
- : int list = [1; 2; 3; 4; 5; 1]
Puede consultar funciones sobre listas disponibles en la biblioteca estándar
de Ocaml en 3.2.1
Expresiones y Funciones.
Entre las expresiones posibles, se cuentan funciones. Una función puede
definirse con la palabra reservada fun; luego de la
cual siguen los argumentos que recibe, el separador ->
y el cuerpo de la función. Ejemplos:
fun x -> x+x
fun x y -> x*y
fun x -> (fun y -> (x*.y));;
Las expresiones anteriore definen funciones sin nombre, pero se le puede
asignar uno utilizando la palabra clave let para asociar un nombre a
una expresión. Ejemplo:
let suma = fun x y -> x+y;;
let suma2 = x+y;;
Recursión.
Definir una cosa por partes, entre las partes se identifica uno o más
casos que emplena la misma definición (empleando una parte más ``pequeña'')
y uno o más casos que no acuden directa o indirectamente a la misma
definición (i.e. los casos triviales).
Una función recursvia es una función que se llama a si misma.
Puede asociarse un nombre a na función recursiva empleando antes de la
definición las palabras reservadas let rec.
Una función recursiva esta definida siempre por dos tipos de casos
-
Caso trivial: es aquel que si se cumple devuelve un resultado que
puede ser final o parte de la solución.
- Caso recursivo: es aquel que llama de nuevo a la función recursiva.
Un ejemplo clasico de recursividad es el cálculo del factorial de un número.
El factorial de un número n es el producto de todos los números enteros
entre 1 y n. Por ejemplo, 3 factorial es 1*2*3 es 6. El caso trivial de
la función fatorial es si N es 1 o 0 devuelve 1.
El caso recursivo si n es mayor a 1 retorna n por el factorial de n-1.
# let rec fc = function
1 -> 1
| n -> n*(fc (n-1));;
val fc : int -> int = <fun>
2.1.2 Hacía la práctica
2.1.3 Lecturas recomendadas
Un Tutorial de OCaml básico en
http://www.malditainternet.com/index.php/section=article/sid=344
Introduction to the Objective Caml Programming Language
http://www.cs.caltech.edu/courses/cs134/cs134b/book.pdf
2.1.4 Ejercicios para afianzar teoría
-
Asocie el nombre cuadrado a una función cuyo tipo sea int -> int y que calcule el cuadrado de un entero dado.
-
Utilizando la función cuadrado del punto anterior, realice una función que
calcule el cubo de un entero dado.
-
¿Qué nombre debería recibir la siguiente función?
let rec nombre = function
0 -> 1
| n -> n*(nombre (n-1));;
-
¿Qué diferencia tiene la anterior función a la siguiente ?
let rec nombre n =
if n=0 then 1
else n*(nombre (n-1));;
-
¿Cómo se llama el mecanismo que permite que una función se comporte como un
switch case ? De un ejemplo:
-
¿Cuál es el tipo de las siguientes funciones ?
let suma x y z = x+y+(z (x-y));;
let rec serie j i n = if i=n then (j i) else (j n)+(serie j i (n-1));;
-
¿Que condición debe cumplirse para que opere correctamente la siguiente
función?
let rec serie j i n =
if i=n then
(j i)
else
(j n)+(serie j i (n-1))
;;
-
¿Qué función permite pasar un número ASCII a caracter ?
-
Asocie el nombre miembro a una función que recibe una lista
y un elemento, la función debe determinar si el elemento esta o no
en la lista. ¿Cuál es el tipo de la función ?
-
Asocie el nombre anexar a una función que recibe 2 listas y
las une colocando la primera detras de la ultima.
La función no debe ser recursiva.
2.1.5 Ejercicios de programación
-
En un archivo de nombre tadlista.ml construya las siguientes funciones:
-
listavacia dice si una lista es vacia o no (true o false).
- invertir devuelve una lista dada invertida.
- esta_elemento dice si un elemento esta en una lista dada.
- num_ocurrencias calcula el número de veces que esta un
elemento en una lista dada.
- lista_ordenada dice si una lista esta ordenada o no.
- espalindrome dice si una lista es palindrome (se lee igual
de derecha a izquierda que de izquierda a derecha).
2.2 Funciones recursivas
-
INDICADOR: Emplea el condicional if.
- INDICADOR: Emplea funciones para realizar operaciones básicas de entrada/sálida (e.g print_endline)
- INDICADOR: Emplea correspondencia de patrones (pattern matching).
- INDICADOR: Emplea recursión mutua y definiciones locales.
- INDICADOR: Emplea comentarios que pueden ser usados por Ocamldoc.
Condicional if
La sintaxis del condicional if es:
if expr1 then expr2 else expr3
Donde expr1 es una expresión de tipo bool, mientras que
expr2 y expr3 son expresiones del mismo tipo. El tipo del
condicional completo es el mismo de expr2 y expr3. Por
ejemplo:
if 2>3 then "xy" else "cd"
Omitir la parte else expr3 es equivalente a escribir
else (). La constante () es el único valor del tipo unit,
se emplea en funciones que no deben retornar o recibir valor alguno. Algunos
ejemplos de funciones que emplean el tipo unit son6 :
-
print_endline: string -> unit que imprime por
salida estándar la cadena que recibe con un salto de línea al final.
- prerr_endline: string -> unit análoga a la anterior
pero que imprime por error estándar.
- print_char: char -> unit, print_string: string -> unit,
print_int: int -> unit, print_float: float -> unit que
imprimen en salida estándar valores de los tipos presentados
(sin salto de línea). Así como las funciones análogas para error
estándar que comienzan con prerr en lugar de print.
- print_newline: unit -> unit y prerr_newline: unit -> unit
que hacen en un salto de línea en salida estándar o error
estándar7.
- read_line: unit-> string que lee de entrada estándar hasta
encontrar un carácter de cambio de línea y lee la cadena leída sin el
cambio de línea.
- read_int: unit-> int y read_float: unit -> float que
leen una línea de entrada estándar y la convierten a entero y flotante
respectivamente. Si la línea leída no puede convertirse al tipo esperado,
estas funciones lanzan una excepción (que se explicará como manejar más
adelante).
Correspondencia de patrones
Pueden definirse funciones por casos empleando function en lugar
de fun y patrones. Por ejemplo, la siguiente
función retorna la cadena es uno cuando recibe el entero 1 y
la cadena diferente en otro caso:
function
| 1 -> "es uno"
| _ -> "diferente" ;;
Decimos que en esta definición los casos son 1 -> "es uno" y
_ -> "diferente" , que el patrón del primer caso es 1 y
el del segundo es _, mientras que "es uno" y "dos"
son los
cuerpos de cada casos. Note que un caso se separa de otro con el
carácter |8.
Al aplicar esta función a un valor (que llamaremos objetivo) se inicia un
algoritmo de correspondencia de patrones que determina el patrón apropiado
(diremos que concuerda) con el objetivo y el cuerpo que se evalúa es
el del patrón que concuerde.
En el ejemplo presentado, el carácter _ es un patrón que concuerda
con cualquier objetivo, mientras que 1 es un patrón que sólo
concuerda con el objetivo 1.
Los patrones son expresiones que pueden formarse con constructoras de
tipos (por ejemplo :: o [] o [n1;n2;···;nk]
en el caso de listas, o , en el caso de tuplas), constantes, el
carácter _
y variables que concordarán con cualquier objetivo y quedaran asociadas
a este.
Por ejemplo el patrón 1::n::_ concuerda con todas las listas
de dos o más elementos cuya cabeza sea 1 y tras la correspondencia
quedará asociada la variable n al segundo elemento de la lista.
Puede emplearse por ejemplo en:
function
| [] -> 0
| [_] -> 0
| 1::n::_ -> n;;
que devolverá el segundo elemento de listas de dos o más
enteros y 0 si recibe la lista vacía o listas de un entero.
En un patrón no debe emplear la misma variable más de una
vez, de requerir hacer corresponder lo mismo en dos posiciones diferentes del
patrón, emplee variables diferentes y comparelas como parte
del cuerpo del caso o empleando when. Por ejemplo:
let f=function
| a,b when a=b -> 1
| _ -> 0;;
De requerirse, pueden emplearse paréntesis para especificar un patrón,
puede indicarse el tipo del objetivo con el que concuerde
una variable de un patrón con la sintaxis (var:tipo) o puede
asociarse una variable a una parte del objetivo que concuerde con
patrón as var. Por ejemplo el siguiente patrón que asocia
una pareja con la variable c y cada elemento de la pareja con las
variables a y b.
function ((a:int),b) as c -> (a,b+1,c)
Al definir una función por casos empleando correspondencia de patrones,
si no cubre con patrones todos los posibles casos, al compilar o interpretar
Ocaml genera una advertencia.
Además de poder usar correspondencia de patrones al definir una
función con function, puede emplearlo con la expresión:
match expr with
| patrón1 -> expr1
| patrón2 -> expr2
:
| patrónn -> exprn
en la que expr será el objetivo y que evaluará y retornará la expresión
del caso cuyo patrón concuerde con el objetivo. Tenga en cuenta que
todas las expresiones de los casos deben ser del mismo tipo (ese será
el tipo retornado por esta expresión).
Definiciones locales y recursión mutua
Para abreviar algunas expresiones que incluyen subexpresiones repetidas
pueden introducirse definiciones locales que asocian una variable con
el valor de una subexpresión y que pueden emplearse en una expresión.
Se introducen con
let var = subexpr in expr
El siguiente ejemplo resulta de adaptar una solución de dominio público
de Ugo Albarello y Juan Carlos Avellaneda a un ejercicio del primer taller:
let alturatriangulo=fun b c d ->
let semip=(b +. c +. d) /. 2.0 in
((2.0 /. b) *. sqrt( semip *. (semip -.b ) *.
(semip -. c) *. (semip -. d))
;;
Por otra parte, para definir ``simultáneamente'' varias funciones
recursivas puede emplearse
and así:
let rec nomfun1 = expr1
and nomfun2 = expr2
:
and nomfunn = exprn
con lo cual pueden definirse funciones que empleen recursión mutua como:
let rec intriga x=
if (x<5) then g (x-1)
else 1+(intriga (x-1))
and g x=
if (x>=4) then intriga x
else if (x<=0) then 0
else 2+(intriga (x-1))
;;
2.2.2 Hacía la práctica
Documentación de fuentes con Ocamldoc
La herramienta ocamldoc permite extraer documentación de
fuentes en Ocaml para generarla en HTML o en LaTeX o en Texinfo
o como páginas man de Unix o como un grafo de dependencia entre módulos.
La documentación por extraer debe ponerse en comentarios especiales
que comienzan con (** y termina con *), tal documentación
se asocia a porciones de código como funciones (de las cuales
ocamldoc extrae el tipo automáticamente) y puede incluir
construcciones que se interpretan de forma especial como:
-
(b texto) para emplear negrillas
- (i texto) para emplear itálicas
- (c texto) para centrar
- [texto] estilo de letra de código fuente
- @author texto para establecer autor (de módulo o función)
- @version texto para establecer versión (de módulo o función)
- @param id texto para describir parámetro id de una función
- @return texto para describir valor retornado por una función
- @see url para referenciar un URL (también puede ser un archivo).
Empleado estos comentarios puede incluir un encabezado para sus
fuentes análogo al siguiente:
(** Programa para sumar dos números.
@author Juan Perez.
Estas fuentes se liberan al dominio público. 2004. Sin garantías.
http://elsitio.org/donde/publico
*)
y podría documentar cada función de forma análoga al siguiente ejemplo:
(** [suma x y] retorna la suma de [x] con [y] *)
let suma x y= x+y;;
Una vez agregue comentarios especiales a sus fuentes (digamos en
un archivo suma.ml), puede generar documentación HTML desde
la línea de comandos con:
ocamldoc -html suma.ml
2.2.3 Lecturas recomendadas
-
Las funciones de error estándar y salida estándar las encuentra en la
documentación del módulo Pervasives de [3].
- Puede consultar sobre los fundamentos de correspondencia de patrones
(pattern matching en inglés) en la sección 5 de [4].
- La sintaxis completa de patrones puede consultarla en la sección
6.6 de [3].
- Todas las posibilidades de Ocamldoc se documentan en el capítulo
15 de [3].
2.2.4 Ejercicios para afianzar teoría
-
¿A que evalúa la expresión if true=false then false else true ?
-
¿Cual es el tipo de la expresión if x<y then x else y+1 ?
-
¿Con qué objetivos concuerda el patrón [1]?
-
Escriba un patrón que concuerde con todas las listas con los elementos
"wq" e "il" usando solo los constructores :: y []
-
¿Cual es el tipo de la siguiente función (presentada en la lectura)?
function ((a:int),b) as c -> (a,b+1,c)
-
¿Que objetivos no cubren los casos de la siguiente función?
function
| (0,_::_) -> 0
| (1,[1]) -> 1
| (2,[x;y]) -> x
| (n,_) when n<>0 -> 1;;
Note que si en tiempo de ejecución, se intenta evaluar esta función en un
objetivo que no concuerde con patrón alguno se producirá una excepción.
-
En la función del ejercicio anterior algunos objetivos son cubiertos
por más de un patrón. Tras experimentar o consultar la documentación
(sección sobre expresiones, subsección definición de funciones) diga
cual es el caso que el algoritmo de correspondencia de patrones
escoge cuando los patrones de varios casos cubren un objetivo.
-
Entre las funciones típicas sobre listas (en programación funcional general)
está la función map:
let rec map f = function
| [] -> []
| h::t -> (f h)::(map f t)
;;
¿Cual es el tipo de esta función?
-
Con una definición local para una función f que multiplique por dos
use la función map para transformar la lista [3;0;1;8] en la lista
[6;0;2;16].
-
Otra función típica para lista es la función fold:
let rec fold f v = function
| [] -> v
| h::t -> fold f (f v h) t
;;
¿qué valor retorna esta función al evaluar la siguiente aplicación?:
fold (fun n e -> n+1) 0 [8;3;3;1];;
2.2.5 Ejercicios de programación
Trabaje los siguientes ejercicios en un archivo de nombre
taller3.ml:
-
Haga una función reconoce que reciba una cadena con números enteros
no negativos con el operador + y espacios entre ellos y que retorne el
resultado de evaluar la suma. Si la expresión que recibe no es válida genere
excepciones con mensajes de error usando la función
(failwith "mensaje").
Pueden servirle las funciones:
-
String.get s i que retorna el i-ésimo carácter
de la cadena s (el primer carácter tiene índice 0).
- String.sub s i l que retorna la subcadena de s de
longitud l que comienza en el i-esimo carácter.
- String.length s que retorna la longitud de la cadena s.
- int_of_string s que convierte la cadena de dígitos s a
entero (si no puede convertirse lanza una excepción).
-
Implemente la función filter p l de tipo
('a -> bool) -> 'a list -> 'a list que filtra la lista
l con el predicado p. Por ejemplo al aplicar esta
función así
filter (fun n -> n>5) [1;8;3;9;6]
daría
[8;9;6]
2.3 Diversos tipos de funciones
-
INDICADOR: Emplea funciones anónimas.
- INDICADOR: Identifica, emplea y construye funciones polimórficas.
- INDICADOR: Emplea funciones de orden superior
- INDICADOR: Maneja el concepto de Curried.
- INDICADOR: Conoce y aplica la transferencia referencial
Funciones anónimas.
Las expresiones pueden ser usadas para definir funciones anónimas como
# (fun x -> x + 1) 3;;
- : int = 4
# (fun x -> fun y -> fun z -> (x,y,z) ) 2 3 4;;
- : int * int * int = (2, 3, 4)
Las funciones anónimas no pueden ser recursivas porque, siendo anónimas, no tienen un nombre por el cual se puedan llamar. Sin embargo, como ya hemos visto a una función anónima se le puede asociar un nombre de la forma
let nombre = expresion;;
ejemplos:
# let sucesor = fun x -> x + 1 ;;
val sucesor : int -> int = <fun>
# sucesor 3;;
- : int = 4
# let punto3d = fun x -> fun y -> fun z -> (x,y,z) ;;
val punto3d : 'a -> 'b -> 'c -> 'a * 'b * 'c = <fun>
# punto3d 1 2 3;;
- : int * int * int = (1, 2, 3)
# punto3d 1.5 2.43 3.5;;
- : float * float * float = (1.5, 2.43, 3.5)
Funciones polimórficas.
Las funciones polimórficas son aquellas que reciben un parametro de x tipo y hacen algo con el, un ejemplo de una función polimórfica es la funcion identidad.
# let identidad x = x ;;
val identidad : 'a -> 'a = <fun>
La función generada es de tipo val identidad : 'a -> 'a = <fun>. donde 'a es un elemento de cualquier tipo.
Los operadores de comparación introducidos en la guía2 son polimórficos, al
igual que las siguientes funciones:
-
compare : 'a -> 'a -> int que retorna 0 si x=y, un
entero positivo si x>y y un negativo en otro caso.
- min : 'a -> 'a -> 'a que retorna el menor de los dos
argumentos.
- max : 'a -> 'a -> 'a que retorna el mayor de los dos
argumentos.
El polimorfismo puede usarse para definir funciones para manipular listas
que se apliquen a listas de cualquier tipo de elementos. Un ejemplo de una
función polimórfica es el numeroocurrencias, que consiste en calcular el
número de veces que esta un elemento x en una lista.
# let rec numeroocurrencias x = function
| [ ] -> 0
| c::cs -> if (c=x) then (1+(numeroocurrencias x cs)) else (numeroocurrencias x cs);;
val numeroocurrencias : 'a -> 'a list -> int = <fun>
Como vemos la función generada es de tipo val numeroocurrencias : 'a -> 'a list -> int = <fun> donde 'a es un elemento de cualquier tipo, 'a list es una lista de tipo 'a y la función retorna un entero.
Ejemplos utilizando la función numeroocurrencias:
# numeroocurrencias 5 [1;2;3;4;5;5;6];;
- : int = 2
# numeroocurrencias 5 [];;
- : int = 0
# numeroocurrencias 'a' ['2';'a';'e';'a'];;
- : int = 2
Funciones de orden superior.
Son funciones que toman funciones como parámetros y/o producen funciones como resultado. Por ejemplo:
# let test (f, x) = f x;;
val test : ('a -> 'b) * 'a -> 'b = <fun>
# test (List.hd, [1;2;3]);;
- : int = 1
# test (List.tl, [1;2;3]);;
# - : int list = [2; 3]
La función test recién definida recibe en una pareja, el primer
elemento de la pareja
es una función f y el segundo un elemento x, lo que hace
es aplicar la funciòn al elemento.
Algunas funciones de orden superior que trabajan con listas son
filter, map y fold en sus diferentes variantes.
La función map aplica una función sobre todos los elementos de una lista
o sobre los pares de elementos obtenidos de dos listas. Por ejemplo,
# let rec map f = function
| [] -> []
| c::cs -> (f c)::(map f cs);;
val map : ('a -> 'b) -> 'a list -> 'b list = <fun>
# map (fun x -> x+1) [1;2];;
- : int list = [2; 3]
Las funciones fold aplican un operador binario sobre los elementos
de una lista hasta obtener un único valor. Si se empieza a aplicar el
operador por la izquierda tenemos la función foldl (l de left) y si
se empieza por la derecha foldr (r de rigth).
Ejemplo:
# let rec foldr x f = function
| [ ] -> x
| c::cs -> f(c, foldr x f cs);;
val foldr : 'a -> ('b * 'a -> 'a) -> 'b list -> 'a = <fun>
# let sumar (x,y) = x+y;;
val sumar : int * int -> int = <fun>
# foldr 0 sumar [45;32;645;90];;
- : int = 812
# let mayor (x,y) =
if x>y then x else y;;
val mayor : 'a * 'a -> 'a = <fun>
# foldr 0 mayor [1;2;4;12;2;2;54];;
- : int = 54
# foldr 0 mayor [1;2;4;12;2;2;5;4];;
- : int = 12
2.3.2 Curried. Funciones parciales
Curried / Currificación Correspondencia entre cada función de múltiples parámetros y una de alto orden que retorna una función intermedia que completa el trabajo. Por cada función f definida como f (a,b) -> c siempre se puede escribir f a -> (b -> c).
Curried es una forma de reducir el número de paréntesis de una expresión consiste en reemplazar un argumento estructurado por una secuencia de argumentos más simples. Ejemplo:
let minimo (x,y) = if x < y then x else y;;
La función minimo recibe un argumento que consiste en una tupla de dos elementos. Esta expresión es equivalente a:
let minimo2 x y = if x < y then x else y;;
Donde minimo2 toma dos argumentos, uno después del otro. Más precisamente, minimo2 es una función que toma un entero x como argumento y devuelve una función minimo2 x; la función minimo x toma un entero y como argumento y devuelve un entero, el menor entre x e y. Otro ejemplo:
# let suma x y = x + y;;
val suma : int -> int -> int = <fun>
# suma 5 3;;
- : int = 8
# (suma 5) 3;;
- : int = 8
# let suma5 = suma 5;;
val suma5 : int -> int = <fun>
# suma5 3;;
- : int = 8
En este caso suma recibe dos argumentos x e y y suma5 recibe un argumento y se lo adiciona a 5.
let suma1 (x,y) = x+y;;
let suma2 x y = x+y;;
Para cada entero x la función suma2 añade x a un entero. Ejemplo suma2 1 es la función sucesor y suma2 0 es la función identidad sobre los enteros.
Para que la currificación funcione se necesita que la operación de aplicación funcional asocie a la izquierda en las
expresiones. Ejemplo
minimo2 50 42;; significa (minimo2 50) 42
suma2 52 34;; significa (suma2 52) 34
Esta forma que permite reemplazar argumentos estructurados por una secuencia de argumentos más simples, se conoce como currificación, en memoria del lógico americano Haskell B. Curry.
2.3.3 Transparencia referencial
OJO. Más bien un ejemplo explicado ?
La transparencia referencial se refiere a que la evaluación de una función siempre produce el mismo resultado. Una expresión representa un valor y una propiedad a esto se llama transparencia referencial, cada expresión representa siempre al mismo valor, sin importar en que lugar de un programa aparece. Otros paradigmas de programación permiten que una misma expresión o instrucción represente o indique una acción distinta, de acuerdo al contexto en el que se encuentre. Pero la programación funcional, no.
La transparencia referencial es muy útil a la hora de modificar un programa, ya que no tenemos que preocuparnos porque las modificaciones que hagamos en una parte del mismo afecten los cálculos que se hacen en otras. También es muy poderosa a la hora de verificar un programa (demostrar matemáticamente que cumple la especificación), ya que podemos utilizar
propiedades ya demostradas de todas las subexpresiones que constituyen una expresión y que valen en cualquier contexto.
2.3.4 Hacía la práctica
ocamldep, ocamlopt, ocamlc
Makefile
2.3.5 Lecturas recomendadas
David Matuzeck. A concise introduction to Objective Caml. 2000 http://www.csc.vill.edu/ dmatusze/resources/ocaml/ocaml.html
Richard Bird. Introducción a la programación Funcional con Haskell. Segunda Edición. Capítulo 1 Conceptos fundamentales.
Piere Weis. Caml programming guidelines. 2003 http://caml.inria.fr/FAQ/pgl-eng.html Indentation of programs
2.3.6 Ejercicios para afianzar teoría
-
¿A qué evalua la siguiente función?
(fun x y -> (if x>y then x else y)) 23 4;;
-
Escriba la función anónima anexar que recibe dos listas y a la primera
lista le una la segunda.
-
Escriba la función curry que recibe una función que recibe una pareja
(x,y) y que retorne la currificación
-
La función quicksort recibe una lista x y nos devuelve una lista y con los elementos ordenados de x. ... ¿La función quicksort es una función polimórfica? si o no.
-
La función quicksort recibe una lista x y nos devuelve una lista y con los elementos ordenados de x. ... ¿Cuál es el tipo de la función quicksort ?
-
¿Cuál función de orden superior me sirve para obtener de una lista de enteros el número menor ?
-
Utilizando funciones de orden superior y funciones anónimas, arme una
función que obtenga el número de ocurrencias de un elemento en una lista.
De como respuesta la función aplicada a encontrar el número de
ocurrencias de 'a' en
['1';'2';'a';'b';'c';'a';'c'] (como requerimiento debe utilizar
List.length).
-
¿Cuál es el tipo de cada una de las funciones
List.map y List.map2?
-
En cuanto a funcionalidad ¿cuál es la diferencia entre List.map y List.map2?
-
¿Una función de orden superior puede ser polimórfica?
2.3.7 Ejercicios de programación
-
En un archivo llamado funciones.ml escriba las siguiente funciones
-
Función insertar, que, dados una lista no
decreciente ys y un elemento y, devuelva una lista no
decreciente igual a ys más el elemento
y insertado en el lugar correspondiente.
- Función unico, que, dada una cadena devuelva una
cadena que contenga exactamente los elementos que se encuentran
solamente una vez en la cadena dada.
Por ejemplo: unico "Cuales son las letras unicas en esta frase?" debe
dar "oicf?" .
-
En un archivo llamado enterog.ml utilizando listas represente enteros positivos
grandes y realice las funciones necesarias para sumar, restar y
multiplicar enteros grandes, y convertir un entero en un entero grande.
Ejemplos
-
el número 156 representado en entero grande es [1;5;6]
- los números 156 y 32 representados comon enteros grandes
son [1;5;6] y [3;8]. El resultado de la suma es [1;9;4]
- al restar [1;5;0;0;0] de [5;5;5;0] se obtiene [9;4;5;0]
Opcional: Idee una representación para enteros negativos e implemente las
mismas funciones con la nueva representación.
-
Punto adicional: Cambie el archivo enterog.ml ha floatg.ml y edite las funciones, para permitir guardar flotantes grandes de la forma (parteentera, parteflotante) ejemplo el número 15.5 se representa en floatg ([1;5],[5]).
2.4 Tipos definidos por el usuario
-
INDICADOR: Define nuevos tipos
- INDICADOR: Emplea nuevos tipos y excepciones
- INDICADOR: Depura y rastrea programas Ocaml
Definición de tipos
Un tipo representa un conjunto de valores. Por ejemplo el tipo bool
corresponde al conjunto { true, false}, mientras que el tipo
int corresponde al conjunto de enteros entre 2-30 y 230-1.
Es posible definir nuevos tipos empleando la sintaxis:
type nomtipo = expr-tipo;;
siendo nomtipo la identificación del tipo y exptipo una expresión de tipo
que describe el cojunto que corresponde al tipo.
La identificación se compone con letras, números y
_, teniendo que ser el primer caracter una letra minúscula.
La expresión de tipo más simple es el nombre de un tipo ya definido, con
lo cual se introduce una abreviatura. Por ejemplo una abreviatura
para un tipo básico es:
type tnombre=string;;
o una abreviatura para un tipo compuesto es:
type tvars=int list;;
Una vez definidos, estos tipos pueden emplearse en funciones, por ejemplo:
let datose (nombre:tnombre) (edad:int)=
(nombre,edad)
;;
Tipos variantes
Un tipo variante se define empleando constructores de tipo, un constructor
puede ser bien una nueva constante o bien como una función que aporta
elementos de otro tipo al tipo que se define. Por ejemplo el tipo
bool se forma con dos constructoras constantes: true
y false.
Una expresión de tipo que define un tipo variante, define cada una
de las constructoras con esta sintaxis:
const_tipo1 | const_tipo2 | ··· | const_tipon
En los tipos variante definidos por el usuario, cada constructor tiene un
identificador que puede tener letras, números y _ y que debe iniciar con
letra mayúscula. Los constructores constantes emplean la sintaxis:
id-const
mientras que las constructoras función:
id-const of expr-tipo
Empleando un tipo variante puede crearse un tipo con sólo nuevas constantes:
type pinta=Pica | Trebol | Diamante | Corazón
que consta justamente de 4 elementos diferentes. Podría usarse así:
let pintaroja=function
| Pica -> false
| Trebol -> false
| Diamante -> true
| Corazón -> true
;;
O como el tipo fpclass empleado por la función
classify_float : float -> fpclass que es:
type fpclass =
| FP_normal (* Normal number, none of the below *)
| FP_subnormal (* Number very close to 0.0, has reduced precision *)
| FP_zero (* Number is 0.0 or -0.0 *)
| FP_infinite (* Number is positive or negative infinity *)
| FP_nan (* Not a number: result of an undefined operation *) OJO
Algunos de los
constructores pueden ser funciones de otros tipos en el que se define, para
lograrlo se usa :
type numero=Entero of int | Flotante of float
Figure 2.1: Ejemplo de tipo variante
el cual se representa en la figura 2.1 y
que podría usarse para definir una función suma común para enteros y
flotantes empleando correspondencia de patrones:
let suma x t= match (x,y) with
| Entero(e1),Entero(e2) -> Entero(e1+e2)
| Entero(e1),Flotante(f2) -> Flotante((float\_of\_int e1)+f2)
| Flotante(f1),Entero(e2) -> Flotante(f1+(float\_of\_int e2))
| Flotante(f1),Flotante(f2) -> Flotante(f1+f2)
;;
Tuplas y registros
Una tupla es un elemento de un producto de tipos, por ejemplo
la tupla (true,3) es un elemento del producto bool*int.
Un producto de tipos se indica con el operador *.
Usando un producto puede definirse una estructura que mantenga
datos de una persona como nombres, apellidos, edad y sexo:
type tsexo=M | F;;
type datosper=string*string*int*tsexo;;
Sin embargo es fácil confundir el orden en el que se usan nombre
y apellidos pues ambos son cadenas. Para facilitar la utilización
de tipos como este se emplean registros, los cuales asocian a cada
componente de la tupla un nombre:
type datosper2={nombre:string; apellido:string; edad:int; sexo:tsexo};;
Puede definirse un nuevo registro definiendo todos los componentes,
por ejemplo:
let j={nombre="Andrés"; apellido="Benavidez"; edad=5; sexo=M};;
Y puede extraerse uno sólo de los componentes de la tupla con el operador
punto (.), por ejemplo:
let nombreCompleto r=r.nombre^" "^r.apellido;;
Tipos recursivos
Para definir tipos más complejos puede emplearse recursión en la
definición del tipo. Una ejemplo es una lista de enteros:
type lisent= Vacia | Cons of int*lisent;;
La función longitud que determina la cantidad de elementos
en una lista de estas puede ser:
let rec longitud=function
| Vacia -> 0
| Cons (_,cola) -> 1+(longitud cola)
;;
Esta función podría aplicarse a la lista con los enteros 3; 29; -1:
longitud (Cons(3,Cons(29,Cons(-1,Vacia))));;
Tipos paramétricos
Entre los tipos predefinidos en Ocaml está option cuya definición es:
type 'a option = None | Some of 'a
Este tipo permite definir funciones que en algunos casos retornan
un valor y en otros ninguno. Además es un tipo que emplea un parámetro
de tipo ('a) que indica que puede usarse con cualquier otro tipo.
Podría instanciarse para representar ninguna o alguna cadena con:
type stringOpt=string option;;
Un ejemplo del uso del tipo option es la siguiente función que recibe
una lista de enteros y retorna el primer elemento mayor que 10:
let rec primerMayor10 = function
| [] -> None
| h::t ->
if (h>10) then
Some h
else
primerMayor10 t
;;
Esta función podría emplearse en otra así:
let nomMayor le =
match (primerMayor10 le) with
| None -> "No hay elemento mayor que 10"
| Some e -> "El primer elemento mayor que 10 es "^(string_of_int e)
;;
Un ejemplo de un tipo variante, parámetrico y recursivo puede ser un árbol binario:
type 'a arbbin= Vacio | Nodo of 'a * 'a arbbin * 'a arbbin;;
donde adoptaremos una convención para el constructor Nodo, la primera
componente de la tupla es el elemento de la raíz, la segunda es el
subárbol izquierdo y la tercera el subárbol derecho.
Ahora podríamos definir una función que calcule la altura de
uno de estos árboles:
let rec altura=function
| Vacio -> 0
| Nodo (r,i,d) -> 1+(max (altura i) (altura d))
;;
Otro ejemplo de tipo parámetrico y recursivo es el tipo 'a list
predefinido en Ocaml. Se trata de un tipo variante, parámetrico y
recursivo, sus constructoras son [] y el constructor
infijo :: (note que en Ocaml puro el usuario no puede definir
constructores con esos nombres porque: (1) se espera que los constructores
comiencen con letra mayúscula y (2) los constructores son prefijos).
Excepciones
Las excepciones se emplean para salir de funciones de forma anormal,
sin retornar valor alguno. Pueden emplearse excepciones predefinidas
o definir nuevas con:
exception id [ of exprtipo]
por ejemplo:
exception NoEsta;;
o
exception ErrorEnPosicion of int*string;;
Para generar una excepción se emplea raise, por ejemplo:
let rec esperaPrimeraX p=function
| [] -> raise NoEsta
| h::t ->
if (p=0 && h="X") then
true
else if (p=0) then
raise (ErrorEnPosicion(p,"En lugar de X está "^h))
else if (h="X") then
raise (ErrorEnPosicion(p,"X aparece antes en la pos "
^(string_of_int p)))
else
esperaPrimeraX (p-1) t
;;
Las excepciones predefinidas son:
-
Match_failure of (string * int * int)
Assert_failure of (string * int * int)
Invalid_argument of string puede usarse con función
invalid_arg: string -> 'a
Failure of string puede emplearse con función
failwith: string -> 'a
Not_found
Out_of_memory
Stack_overflow
Sys_error of string
End_of_file
Division_by_zero
Sys_blocked_io
Undefined_recursive_module of (string * int * int)
Una función que llama a otra que genere una excepción puede terminar
abruptamente con la excepción que eventualmente genera la función llamada.
Para controlar esto se emplea:
try expr with pat1 -> expr1 | ··· | patn -> exprn
que detecta generación de excepciones en la expresión expr. Si no ocurre
excepción alguna esta expresión evalua a lo que evalúe expr. Si
ocurre una excepción al evaluar expr, se hace una correspondencia
de patrones entre la excepción generada como objetivo y
los patrones pat1 a patn, el
resultado en tal caso es la evaluación de la primera expresión expri
tal que el patrón patri corresponda al objetivo.
Por ejemplo:
let laXen5 l =
try
if (esPrimeraX 5 l) then
"Primera X en posición 5"
else
"Nunca entra a este caso"
with
| _ -> "No se encontró la primera X en la posición 5"
;;
2.4.2 Hacía la práctica
Depuración con ocamldebug
Para emplear el depurador símbolico ocamldebug compile empleando
la opción -g. Por ejemplo:
ocamlc -g -o prog prog.ml
Después puede iniciar el depurador con el programa prog con:
ocamldebug prog
Este depurador emplea algunos puntos en las fuentes para definir pasos
a tales puntos les llama eventos, por ejemplo inicio de funciones o
evaluación de primera expresión de un if, el número de pasos ejecutados
desde el comienzo del programa hasta cierto punto se llama el tiempo.
Puede avanzar uno o más pasos con step numero y
a diferencia de otros depuradores también puede deshacer pasos
con backstep numero (en realida bastan las primeas letras de un
comando por ejemplo s 10). Hay un límite en el número de pasos que
pueden deshacerse, este límite
se cambia con set history tamaño.
Puede comenzar la depuración de su programa avanzando algunos pasos
(por ejemplo 12) y retrocediendo hasta reconocer instrucciones que hagan
parte de sus fuentes (aparecerá el código que se ejecutará en el siguiente
paso, el número de línea a la izquierda, el módulo --i.e el archivo fuente--
y el tiempo). Notará que las llamadas a funciones y el comienzo del
programa se depuran en fuentes de la librería de Ocaml.
Si desea continuar la ejecución de su programa hasta el final puede ejecutar
run, o para retroceder la ejecución hasta el comienzo
reverse. Puede poner un punto de ruptura en cierta línea de
sus fuentes con break @numlinea o una vez una función ha sido
definida al ejecutar paso a paso, puede poner un punto de ruptura
al comienzo con break nomfunción. Si conoce el tiempo exacto
en el que desea detenerse puede emplear goto tiempo. Puede
ver los puntos de ruptura que ha establecido con info breakpoints.
Para continuar hasta el final de la función en la que este, puede emplear
finish
o para regresar al comienzo de la función start.
El comando step entrará a funciones pero
si desea ejecutar la función completa y detenerse en el siguiente evento
use next (su análogo hacia atras es previous).
Para evaluar e imprimir el valor asociado a símbolos puede emplear
print simbolo que mostrará la estructura completa en casos
de tipos compuestos o display simbolo que sólo mostrará
el comienzo de la estructura9
Para ver ayuda de los comandos disponibles puede emplear
help o help comando y para salir
quit.
Rastreo
Desde el entorno interactivo puede rastrearse una función ya definida
usando #trace función ;;. Al rastrear una función cada vez
que esta sea evaluada y cada vez que termine su evaluación se imprimirán
los parametros de entrada y el valor de salida respectivamente.
Un ejemplo de una sesión en la que se usa #trace es:
# #use "taller5-2.ml";;
type exprar =
Const of int
| ...
val reconoce_exprar : string -> exprar = <fun>
- : exprar = Suma (Const 1, Const 2)
# #trace reconoce_exprar ;;
reconoce_exprar is now traced.
# reconoce_exprar "((5+3)*2)";;
reconoce_exprar <-- "((5+3)*2)"
reconoce_exprar --> Prod (Suma (Const 5, Const 3), Const 2)
- : exprar = Prod (Suma (Const 5, Const 3), Const 2)
2.4.3 Lecturas recomendadas
Pueden definirse tipos variantes más complejos de los que
se presentan aquí así como clases y otras formas de tipos, puede
consultar la sintaxis completa en las secciones 6.4 y 6.8.1 de [3].
La documentación sobre ocamldebug la encuentra en el capítulo 16
de [3], mientras que la de rastreo en la sección
"Debugging tools" del capítulo 10 de [1].
2.4.4 Ejercicios para afianzar teoría
-
Defina un tipo variante trivaluada cuyos constructores sean Si,
No y NoSabe.
-
Defina un tipo variante intinf que pueda representar todos los
enteros soportados por el tipo int con la constructora Ent y las
dos constructoras InfP e InfN que representan inf
y -inf. Después defina la función sumaii que recibe dos elementos
de este tipo y retorna la suma, en el caso de sumar dos infinitos
(bien sean positivos o negativos) debe generar la excepción predefinida en
Ocaml Invalid_argument, la cual recibe una cadena.
una excepción
-
Con el tipo que definió en el punto anterior podría definirse un racional
(numerador, denominador) y una función que reduce:
type racinf=intinf*intinf;;
let redrac r1=match r1 with
| (Ent n,Ent d) -> if (n mod d=0) then (Ent (n/d), Ent 1) else (Ent n,Ent d)
| (InfP,Ent d) | (Ent d,InfP) -> if (d>0) then (InfP,Ent 1) else (InfN,Ent 1)
| (InfN,Ent d) | (Ent d,InfN)-> if (d>0) then (InfN,Ent 1) else (InfP,Ent 1)
| (InfN,InfP) | (InfP,InfN) -> (InfN,Ent 1)
| (InfN,InfN) | (InfP,InfP) -> (InfP,Ent 1)
;;
Cual es el tipo de redrac ?
-
Empleando el tipo registro datosper2 introducido en la lectura,
escriba una función mayor: datosper2 -> datosper2->bool que retorne
true sólo si la edad del primer registro es mayor que la del segundo.
-
Defina el tipo exprar que represente expresiones aritméticas entre
enteros. Una expresión puede ser una constante (constructor Const),
el inverso aditivo de una expresión (InvAd),
una suma entre dos expresiones (Suma), una resta entre dos
expresiones (Resta), un producto (Prod) o una División
(Div).
-
El tipo que definió en el ejemplo anterior puede usarse en una función que
evalue una expresión aritmética. Complete la función siguiente y después
usela para evaluar la expresión (3+5)*-4 (use InvAd para
representar -4):
let rec evalua = function
| Const i -> i
| InvAd e -> -(evalua e)
...
;;
-
Defina un tipo parámetrico narb que permita representar un árbol n-ario.
Después de definido debería poderse definir la siguiente función
que calcula la cantidad de nodos de un árbol n-ario
(consulte la especificación de List.fold_left en la documentación
de Ocaml):
let rec npeso = function
| Vacio -> 0
| Nodo (e,l) -> (List.fold_left (fun a s -> a+(npeso s)) 1 l)
;;
-
El tipo definido en el ejercicio anterior tiene el inconveniente de
permitir representar el mismo árbol de diversas formas, por ejemplo:
(Nodo ("a",[])) debería ser el mismo (Nodo ("a",[Vacio]))
o con tantos subárboles vacios como se desee
((Nodo ("a",[Vacio;Vacio;Vacio;Vacio]))). El tipo paramétrico
narb2 definido a continuación soluciona ese inconveniente (aunque
introduce otros)
type 'a nnod=Hoja of 'a | Sub of 'a * (('a nnod) list);;
type 'a narb2=Vacio2 | Nodo2 of 'a nnod;;
Escriba la función npeso2 que retorne el peso de un arbol de
tipo narb2.
-
Otra posibilidad para el tipo narb es emplear una convención,
por ejemplo no permitir que como hijos de un nodo aparezca el árbol Vacio.
Defina la excepción VacioComoSubarbol y escriba la función
narbBueno: 'a narb -> unit que retorne () cuando el árbol siga
la convención o en otro caso que lance la excepción definida
(si lo desea y le son de utilidad emplee
las funciones List.exists y List.iter).
-
Escriba la función npeso3: 'a narb -> int empleando las funciones
narbBueno y npeso de los ejercicios anteriores. Debe retornar
el peso de árboles que sigan la convención y -1 para árboles que no la
sigan.
2.4.5 Ejercicios de programación
-
En un archivo taller5-1.ml escriba la función
sublistas: 'a list -> 'a list list que reciba una
lista y retorne todas las posibles sublistas no vacias de elementos
consecutivos,
por ejemplo si recibe [1;2;3]
debe retornar [[1];[2];[3];[1;2];[2;3];[1;2;3]]
-
Descargue y descomprima las fuentes del programa repasa disponibles en
http://structio.sourceforge.net/repasa. Compilelo y pruebe por
lo menos la interfaz plana repasa.
El archivo lrepasa.ml es una librería de funciones con diversos
propósitos.
Entre otras se define la función
test_equal_answer: heuresp -> string -> string -> bool
que decide si una respuesta es correcta empleando una posible heurística.
Complete el archivo taller5-1.ml extendiendo el tipo
heuresp con el constructor HIgnoreSpaces y extienda la función
test_equal_answer para que emplee la nueva heurística.
Lo que debe hacer es decidir que dos cadenas sean iguales aún cuando
haya espacios en blanco redundantes (se consideran espacios en blanco:
' ', '\n', '\t' y '\r'). Por ejemplo con esta heurísitca las
cadenas " hoy es miercoles" y "hoy es miercoles" deben
ser consideradas iguales (aunque son diferentes a "hoyes miercoles" ).
Opcional: Modifique las fuentes de repasa con su cambio y compile
(más rápido con make repasa).
Es recomendable que verifique los sitios donde se usan constructores
de heuresp por ejemplo desde el interprete de comandos con:
grep "HCase" *ml
y que agregue el nuevo caso. Si amplia la función parse_heur
(digamos para que la cadena "ignoraesp" corresponda a HIgnoreSpaces) y
el DTD dtds/def.dtd para incluir la nueva cadena como caso del atributo
heur del elemento sig, podrá
modificar algún banco de definiciones para que emplee su heurística cuando
sea usado con las interfaces de repaso repasa o repasatk.
Por ejemplo puede modificar uno de los significados de regr/ejsimple.def
para agregar el atributo heur="ignoraesp" y después verificar
su heurística con:
./repasa -forma-abierta -D dtds regr/ejsimple.def
-
Para analizar, interpretar o compilar un programa puede ser conveniente
convertir la sintaxis concreta del programa a un árbol de sintaxis
abstracta (AST - Abstract Syntax Tree).
En el ejercicio de afianzamiento 5 se pedía definir un tipo para un
AST apropiado para expresiones aritméticas, mientras que en el ejercicio
6 se pedía implementar una función para evaluar.
En un archivo taller5-2.ml haga la función
reconoce_exprar: string -> exprar que recibe
una cadena con una expresión aritmética y retorna el AST correspondiente.
Puede suponer que la cadena no tiene errores pero si espacios
arbitrarios, que emplea el operador prefijo - (inverso aditivo)
y los operadores infijos , +, -, * y /,
y posibles subexpresiones entre paréntesis. Asigne la
precedencia y asociatividad que usted desee a los operadores infijos.
Sugerencia: Compile el archivo y de requerirlo depure con
ocamldebug.
Opcional: Mejore la función que implementó para que detecte errores
y lance una exepción, y para que de prioridades y asociatividad a los
operadores así:
-
- prefijo
- * / con asociatividad a la izquierda
- + - con asociatividad a la izquierda
- 1
- 3.2E4 corresponde a 3.2·104
- 2
-
Librería básica es el término con el que traducimos `core library.´
- 3
- Esta igualdad polimórfica compara igualdad
estructural, puede emplearse con tipos como listas o con tipos definidos
por usuario o con estructuras mutables.
- 4
- Las comparaciones funcionan
sobre tipos básicos de la manera usual (para booleanos se considera
true > false. En estructuras compuestas se extienden
de manera natural --aprovechando su definición usual para enteros,
caracteres, flotantes, cadenas y booleanos.
- 5
- La implementación
actual soporta cadenas de hasta 224-5 caracteres en
plataformas de 32 bits
- 6
-
Estas funciones en Ocaml no son "puras"
porque tienen efectos laterales (como imprimir en pantalla).
En programación funcional pura las funciones no tienen efectos laterales,
es decir al llamar una función todo el resultado de su operación será
retornado a la función llamadora.
- 7
- También puede hacerse un salto de línea empleando la
secuencia de escape \n en una cadena.
- 8
- En realidad no se necesita el primer
| después de function, lo ponemos para facilitar visualizar
y eventualmente reordenar o agregar casos.
- 9
- Es posible definir funciones
de impresión para ciertos tipos (ver en el manual de referencia
load_printer).
Capítulo 3 Característcas imperativas
-
LOGRO: Maneja tipos mutables
- LOGRO: Maneja estructuras de control tipicamente imperativas
- LOGRO: Emplea funciones con efectos laterales
- LOGRO: Emplea funciones de diversas librerías de Ocaml
3.1 Características imperativas
-
INDICADOR: Emplea funciones con características imperativas.
- INDICADOR: Emplea estructuras de datos modificables.
- INDICADOR: Identifica y construye funciones utilizando vectores.
- INDICADOR: Identifica y construye funciones utilizando cadenas.
- INDICADOR: Identifica y construye funciones utilizando registros con campos mutables.
- INDICADOR: Identifica y construye funciones utilizando referencias.
- INDICADOR: Implementa funciones utilizando canales de entrada-salida.
- INDICADOR: Identifica y construye funciones utilizando estructuras de control como secuencias o ciclos.
Estilo imperativo.
Thomas Kuhn define paradigma como "logros científicos universalmente
reconocidos que durante un tiempo proporcionan problemas y soluciones
modelo para una comunidad de profesionales". Además sostiene que "a
pesar de ambigüedades ocacionales, los paradigmas de una ciencia
madura pueden ser determinados con relativa facilidad".
Un paradigma de programación representa un esquema particular para la
construcción de software, abarca ciertos lenguajes que comparten
elementos estructurales y elementos metodológicos.
El paradigma imperativo es aquel que facilita los cálculos por medio
de cambios de estado (condición de una memoria o almacenamiento).
Un programa imperativo realiza una secuencia de acciones que se ejecutan
en orden, pero también cuenta con herramientas para modificar el orden de
ejecución de las acciones. Algunos lenguajes de programación que utilizan el
paradigma imperativo son Fortran, C, C++, Pascal y Cobol. Como hemos visto
Ocaml es uno entre muchos lenguajes funcionales que además de programación
funcional pura, permite programar empleando características imperativas,
algunas características propias de lenguajes orientados a objetos.
Las dos principales desventajas, comparadas al estilo puramente funcional,
son:
-
complica el sistema del tipo de la lengua, rechazando ciertos
programas que de otra manera serían considerados correctos
- tiene que no perder de vista la representación de la memoria
y el orden de los cálculos.
El fin de integrar elementos imperativos en un lenguaje funcional,
es poder escribir ciertos algoritmos
en este estilo cuando sea necesario, ya que ciertos algoritmos son más fáciles
de escribir en este estilo de programación. Como ejemplo el cómputo del
producto de dos matrices. Aunque es ciertamente posible traducirlo a una
versión puramente funcional, en la cual las listas substituyen vectores,
esto no es natural ni eficiente.
Entre las características imperativas de Ocaml se encuentran:
-
Estructuras de datos modificables como vectores, cadenas,
campos mutables y referencias.
- Operaciones de entrada y salida.
- Estructuras de control como secuencias y ciclos.
En este capitulo trabajaremos con las características imperativas de Ocaml.
Estructuras de datos modificables.
Vectores.
Los vectores son una lista de datos con un número fijo de elementos,
todos del mismo tipo. Se puede escribir un vector directamente
enumerando sus valores entre los símbolos [| y |] , separado por puntos
y comas como en las listas. Ejemplo:
# let v = [| 3.5; 26.25; 19.0 |] ;;
val v : float array = [| 3.5; 26.25; 19.0 |]
Para crear un vector también se puede utilizar la función Array.create,
la cual recibe el tamaño del vector y el elemento inicial o inicialización
de los campos del vector. Ejemplo:
# Array.create;;
- : int -> 'a -> 'a array = <fun>
zz # let v = Array.create 3 3.14;;
val v : float array = [|3.14; 3.14; 3.14|]
# let v2 = Array.create 4 '1';;
val v2 : char array = [|'1'; '1'; '1'; '1'|]
Al igual que en otros lenguajes de programación, el primer elemento de un
vector tiene índice 0 y el índice del elemento final es la longitud del
vector menos 1. Para tener acceso a un elemento particular,
se debe dar el índice de la siguiente forma:
expr1 . ( expr2 )
y para modificar una posición:
expr1 . ( expr2) <- expr3
donde expr1 debe ser un vector (tipo array) luego sigue un punto
y la expr2 entre paréntesis, es el índice. Se utiliza el símbolo
<- para decir que va tomar un valor en el índice expr2. Ejemplo:
# let v = [| 3.5; 26.25; 19.0 |] ;;
val v : float array = [| 3.5; 26.25; 19.0 |]
# v.(1) ;;
- : float = 26.25
# v.(0) <- 4.5 ;;
- : unit = ()
# v ;;
- : float array = [| 4.5; 26.25; 19.0 |]
# v.(2);;
- : float = 19.
Si el índice usado para tener acceso a un elemento en el vector es mayor o
igual a la longitud del vector, una excepción se produce en el momento del
acceso. Ejemplo:
# v.(3);;
Exception: Invalid_argument "Array.get".
Las funciones para manejar vectores (arreglos) hacen parte del módulo
Array, en los siguientes ejemplos, se utilizaran cuatro funciones del
módulo Array:
-
create que crea un arreglo de un tamaño dado con un valor inicial dado
- length retorna la longitud de un vector
- append que concatena dos vectores.
- copy para copiar un vector a otro.
Ejemplos:
# let v = [|1;2;3|];;
val v : int array = [|1; 2; 3|]
# let v2 = Array.create 5 0;;
val v2 : int array = [|0; 0; 0; 0; 0|]
# let v3 x y = Array.append x y;;
val v3 : 'a array -> 'a array -> 'a array = <fun>
# v3 v2 v;;
- : int array = [|0; 0; 0; 0; 0; 1; 2; 3|]
# v3 v v2;;
- : int array = [|1; 2; 3; 0; 0; 0; 0; 0|]
# let longitud x = Array.length x;;
val longitud : 'a array -> int = <fun>
# longitud v;;
- : int = 3
# longitud v2;;
- : int = 5
# longitud (v3 v2 v);;
- : int = 8
# let v4 = Array.copy v;;
val v4 : int array = [|1; 2; 3|]
# v4;;
- : int array = [|1; 2; 3|]
Compartir valores en un vector.
Todos los elementos de un vector contienen el valor que fue pasado cuando fue
creado. Esto implica compartir de este valor, si es un valor estructurado.
Por ejemplo, si se crea una matriz (un vector de vectores) m que
utiliza la función create del módulo Array.
# let v = Array.create 3 0;;
val v : int array = [|0; 0; 0|]
# let m = Array.create 3 v;;
val m : int array array = [|[|0; 0; 0|]; [|0; 0; 0|]; [|0; 0; 0|]|]
Si se modifica uno de los campos del vector v, que fue utilizado en la
creación de m, entonces se modifica automáticamente todo las "filas" de la
matriz. Ejemplo:
# v.(1)<-5;;
- : unit = ()
# v;;
- : int array = [|0; 5; 0|]
# m;;
- : int array array = [|[|0; 5; 0|]; [|0; 5; 0|]; [|0; 5; 0|]|]
La duplicación del valor de inicialización (función create) ocurre debido
a que el valor que se comparte es un valor estructurado en este caso el
vector.
Pueden compararse vectores completos con el operador = (igualdad estructural),
pero al igual que con las demás estructura mutables también puede
emplearse el operador igualdad física e1 == e2 , que es cierto
sólo cuando la modificación física de e1 afecta también a
e2. Con el ejemplo anterior m.(0).(0)==m.(1).(0)
sería true mientras que:
# let m2=[|0;1;2|];;
val m2 : int array = [|0; 1; 2|]
# m2.(0)==m2.(1);;
- : bool = false
Por su parte la diferencia física se decide con el operador infijo
!=
Matrices no-rectagulares.
Una matriz, un vector de vectores, no necesariamente debe ser rectangular.
Se puede manejar una matriz cuyas filas (vectores) tengan diferentes número
de columnas (longitudes de cada vector). Ejemplo la matriz t construye
una matriz triangular para los coeficientes del triángulo del PASCAL.
# let t = [\|
[| 1 |];
[| 1; 1 |];
[|1; 2; 1|];
[|1; 3; 3; 1|];
[|1; 4; 6; 4; 1|];
[|1; 5; 10; 10; 5; 1|]
|] ;;
val t : int array array = [|[|1|]; [|1; 1|]; [|1; 2; 1|]; [|1; 3; 3; 1|]; [|1; 4; 6; 4; ...|]; ...|]
En donde el contenido de la fila 4 es [|1; 3; 3; 1|]
# t.(3) ;;- : int array = [|1; 3; 3; 1|]
OJO. Usar make en lugar de create
get : 'a array -> int -> 'a
set : 'a array -> int -> 'a -> unit
make : int -> 'a -> 'a array
init : int -> (int -> 'a) -> 'a array
make_matrix : int -> int -> 'a -> 'a array array
append : 'a array -> 'a array -> 'a array
concat : 'a array list -> 'a array
sub : 'a array -> int -> int -> 'a array
copy : 'a array -> 'a array
fill : 'a array -> int -> int -> 'a -> unit
blit : 'a array -> int -> 'a array -> int -> int -> unit
to_list : 'a array -> 'a list
of_list : 'a list -> 'a array
iter : ('a -> unit) -> 'a array -> unit
map : ('a -> 'b) -> 'a array -> 'b array
iteri : (int -> 'a -> unit) -> 'a array -> unit
mapi : (int -> 'a -> 'b) -> 'a array -> 'b array
fold_left : ('a -> 'b -> 'a) -> 'a -> 'b array -> 'a
fold_right : ('a -> 'b -> 'b) -> 'a array -> 'b -> 'b
sort : ('a -> 'a -> int) -> 'a array -> unit
stable_sort : ('a -> 'a -> int) -> 'a array -> unit
fast_sort : ('a -> 'a -> int) -> 'a array -> unit
Cadenas
Una cadena en Ocaml no es lo mismo que un vector de caracteres.
Hay una sintaxis especial para acceder a sus elementos:
expr1 . [ expr2 ]
Los elementos de una cadena pueden ser modificados físicamente:
expr1 . [ expr2 ] < - expr3
# let s = "hola Colombia";;
val s : string = "hola Colombia"
# s.[2];;
- : char = 'l'
# s.[0]<-'o';;
- : unit = ()
# s;;
- : string = "oola Colombia"
OJO. Todas las funciones del módulo String.
Campos mutables
Los campos de un registro pueden ser declarados mutables. Lo que se debe
hacer es a la hora de declarar el tipo del registro se le antepone la
palabra clave mutable. Ejemplo:
# type punto2d = { mutable xc : float; mutable yc : float } ;;
type punto2d = { mutable xc : float; mutable yc : float; }
# let p2 = { xc = 1.6; yc = 0.5 } ;;
val p2 : punto2d = {xc = 1.6; yc = 0.5}
En este caso el tipo punto2d esta formado por las coordenadas
xc e yc de tipo float y son declarador mutables,
de esta forma el valor de un campo que sea declarado mutable se
puede modificar usando la sintaxis:
expr1 . nombre < - expr2
La subexpresión expr1 de la expresión debe ser un tipo de
registro que tiene el nombre del campo. Ejemplo:
# p2;;
- : punto2d = xc = 1.6; yc = 0.5
# p2.xc <- 4.5;;
- : unit = ()
# p2;;
- : punto2d = xc = 4.5; yc = 0.5
Podemos escribir una función para mover un punto2d modificando sus
componentes.
Utilizamos un declaración local con la concordancia con el modelo para
ordenar los efectos secundarios.
# let mover p dx dy =
let () = p.xc <- p.xc +. dx
in p.yc <- p.yc +. dy ;;
val mover : punto2d -> float -> float -> unit = <fun>
# p2;;
- : punto2d = xc = 4.5; yc = 0.5
# mover p2 1.1 2.2 ;;
- : unit = ()
# p2 ;;
- : punto2d = xc = 5.6; yc = 2.7
Nota: Es posible mezclar campos mutable y non-mutable en la definición de un
registro, pero solamente los campos declarador como mutable pueden ser
modificados.
Referencias
Ocaml proporciona una referencia polimórfica del tipo ref que se pueda
utilizar como apuntador a un valor; en la terminología de Ocaml se le
denomina referencia a un valor. Un valor referido puede ser modificado.
La referencia del tipo se define como registro con un campo modificable:
type 'a ref = mutable contents:'a
Este tipo se proporciona un atajo sintáctico. Para construir una referencia
a un valor se utiliza la función ref. El valor referido se puede tomar
usando la función del prefijo (!). La función que modifica el contenido de
una referencia es la función (:=). Ejemplo:
# let x = ref 3 ;;
val x : int ref = contents = 3
# x;;
- : int ref = contents = 3
# !x;;
- : int = 3
# x:=4;;
- : unit = ()
# x:= !x+2;;
- : unit = ()
# x;;
- : int ref = contents = 6
# !x;;
- : int = 6
OJO: incr : int ref -> unit, decr : int ref -> unit
Entrada salida: Canales, lectura escritura
Las funciones de entrada-salida calculan un valor (a menudo del tipo unit)
pero durante su cálculo causan una modificación del estado de los
periféricos (puertos) de entrada-salida: modificación del estado del
buffer del teclado, utilizando como salida la pantalla, escribiendo en un
archivo, o modificación de un apuntador leído. En Ocaml se definen dos
tipos de canales in_channel y out_channel, canal de entrada
y canal de salida respectivamente. Cuando se llega al final de un archivo
se produce la excepción End_of_file. Finalmente, las tres constantes
siguientes corresponden a los canales estándares para entrada, salida,
y error al estilo de Unix: stdin, stdout y stderr.
Canales
Las funciones de entrada-salida de la librería estándar de Ocaml para
manejar los canales de comunicaciones toman valores de tipo
in_channel o out_channel. Aparte de los tres valores
predefinidos, la creación de un canal utiliza una de las siguientes
funciones:
# open_in;;
- : string -> in_channel = <fun>
# open_out;;
- : string -> out_channel = <fun>
La función open_in abre un archivo si existe, de lo contrario
ejecuta la excepción Sys_error.open_out.
# let ic = open_in "koala";;
val ic : in_channel = <abstr>
# let oc = open_out "koala";;
val oc : out_channel = <abstr>
el archivo koala existe
# let ic = open_in "leer";;
Exception: Sys_error "leer: No such file or directory".
el archivo leer no existe.
Las funciones para cerrar un canal son:
# close_in ;;
- : in_channel -> unit = <fun>
# close_out ;;
- : out_channel -> unit = <fun>
OJO. Constantes:
stdin : in_channel
tdout : out_channel
stderr : out_channel
Lectura y escritura
Las funciones más generales para lectura y escritura son:
OJO. Mejor separar lectura de escritura.
type open_flag =
| Open_rdonly (* open for reading. *)
| Open_wronly (* open for writing. *)
| Open_append (* open for appending: always write at end of file. *)
| Open_creat (* create the file if it does not exist. *)
| Open_trunc (* empty the file if it already exists. *)
| Open_excl (* fail if the file already exists. *)
| Open_binary (* open in binary mode (no conversion). *)
| Open_text (* open in text mode (may perform conversions). *)
| Open_nonblock (* open in non-blocking mode. *)
open_in_bin : string -> in_channel
open_in_gen : open_flag list -> int -> string -> in_channel
input_char : in_channel -> char
input_line : in_channel -> string
input : in_channel -> string -> int -> int -> int
really_input : in_channel -> string -> int -> int -> unit
input_byte : in_channel -> int
input_binary_int : in_channel -> int
input_value : in_channel -> 'a
seek_in : in_channel -> int -> unit
pos_in : in_channel
in_channel_length : in_channel -> int
close_in : in_channel -> unit
close_in_noerr : in_channel -> unit
set_binary_mode_in : in_channel -> bool -> unit
open_out : string -> out_channel
open_out_bin : string -> out_channel
open_out_gen : open_flag list -> int -> string -> out_channel
flush : out_channel -> unit
flush_all : unit -> unit
output_char : out_channel -> char -> unit
output_string : out_channel -> string -> unit
output : out_channel -> string -> int -> int -> unit
output_byte : out_channel -> int -> unit
output_binary_int : out_channel -> int -> unit
output_value : out_channel -> 'a -> unit
seek_out : out_channel -> int -> unit
pos_out : out_channel -> int
out_channel_length : out_channel -> int
close_out : out_channel -> unit
close_out_noerr : out_channel -> unit
set_binary_mode_out : out_channel -> bool -> unit
# input_line ;;
- : in_channel -> string = <fun>
# input ;;
- : in_channel -> string -> int -> int -> int = <fun>
# output ;;- : out_channel -> string -> int -> int -> unit = <fun>
-
input_line ic lee por el canal de entrada ic todos los
caracteres hasta el primer retorno del carro o el fin del archivo,
y los devuelve en la forma de una lista de caracteres (excepto el retorno del
carro).
- entrada ic s p l procura leer 1 carácter del canal de la
entrada ic y los almacena en la lista s iniciando con el carácter p th.
El número de los caracteres leídos son retornados.
- output oc s p l escribe en el canal de salida
oc parte de la lista s , comenzando en el p-esimo -
con longitud l.
Las siguientes funciones leen de la entrada estándar o escriben en la
salida estándar:
# read_line ;;
- : unit -> string = <fun>
# print_string ;;
- : string -> unit = <fun>
# print_newline ;;- : unit -> unit = <fun>
Un ejemplo: Higher/Lower
let rec hilo n =
let () = print_string "type a number: " in
let i = read_int () in
if i = n then
let () = print_string "BRAVO" in
let () = print_newline ()
in print_newline ()
else
let () =
if i < n then
let () = print_string "Higher"
in print_newline ()
else
let () = print_string "Lower"
in print_newline ()
in hilo n ;;
# hilo 64;;
type a number: 88
Lower
type a number: 44
Higher
type a number: 64
BRAVO
- : unit = ()
Estructuras de control: secuencias, ciclos
Secuencias.
La primera de las estructuras de control típicamente imprescindible
es la secuencia. Esta permite la evaluación de izquierda a derecha de una
secuencia de expresiones separadas por puntos y comas.
expr1 ; ... ; exprn
Una secuencia de expresiones se puede ver como una expresión, donde el
valor que se retorna es el de la ultima expresión pasada en la secuencia
(exprn). Sin embargo, se evalúan todas las expresiones. Ejemplo:
# let x = ref 4 ;;
val x : int ref = contents=1
# x:=!x+1 ; x:=!x*4 ; !x ;;
- : int = 20
# print_string "2 = "; 1+1 ;;
2 = - : int = 2
Los valores obtenidos antes del punto y coma se desechan.
Como regla general se recomienda tener entre paréntesis las secuencias, para
clarificar su alcance. Sintácticamente, los paréntesis puede tomar dos formas:
( expr )
o
begin expr end
Ejemplo: el programa Higher/Lower escrito de forma más natural:
let rec hilo n =
print_string "type a number: ";
let i = read_int () in
if i = n then print_string "BRAVO\n\n"
else
begin
if i < n then print_string "Higher\n" else print_string "Lower\n" ;
hilo n
end ;;
Ciclos
Las estructuras iterativas de control también se encuentran fuera del
mundo funcional. La expresión condicional para repetir, o no, un
ciclo no tiene sentido a menos que pueda haber una modificación
física de la memoria que permite que su valor cambie. Hay dos estructuras
iterativas del control en Ocaml: for ciclo para una iteración limitada y
while para el ciclo de una iteración no-limitada.
Sintaxis del for
for name = expr1 to expr2 do expr3 done
o bien
for name = expr1 downto expr2 do expr3 done
El expr1 y la expr2 son de tipo int. Si la
expr3 no es de tipo unit, el compilador produce un mensaje de alerta.
Ejemplo
# for i=1 to 10 do print_int i;
print_string " " done; print_newline() ;;
1 2 3 4 5 6 7 8 9 10
- : unit = ()
# for i=10 downto 1 do print_int i; print_string " " done; print_newline() ;;
10 9 8 7 6 5 4 3 2 1
-: unit = ()
Para el while la sintaxis es:
while expr1 do expr2 done
La expr1 debe ser del tipo bool y la expr2 del tipo unit, de lo
contrario el compilador produce un mensaje de alerta.
Ejemplo:
# let r = ref 1
in while !r < 11 do
print_int !r ;
print_string " " ;
r := !r+1 done ;;
1 2 3 4 5 6 7 8 9 10
- : unit = ()
3.1.2 Ejercicios para afianzar teoría
-
La función que me permite conocer la longitud de un vector es
-
La función que me permite conocer el número de filas de una matriz es
-
Construya un vector v de enteros, de longitud 5 donde los campos del
vector este inicializados con -1.
-
Cree una función c que reciba un entero x y retorne
un nuevo vector de caracteres, con x caracteres, cada uno
un espacio.
-
La función Array.copy recibe como parametro.
-
Escriba un tipo de dato que utilice dos registros mutables
y uno no-mutable. Explique el funcionamiento.
Construya una referencia x al valor 5.
-
Construya una referencia z al valor -5 y calcule el
resultado de sumar los valores referenciados por x y por z.
-
Para abrir un archivo si este no existe que excepción se ejecuta?
-
Escriba una función que abra un canal de entrada, leyendo del archivo
leer.txt
.
3.1.3 Ejercicios de programación
-
En un archivo vectores.ml escriba las siguientes funciones para
manejo de arreglos de tipo entero:
-
creararreglo : dada la longitud y el entero inicial.
- invertira : dado un arreglo retorna uno nuevo con los mismos elementos del que recibe pero invertidos.
- sumararreglo : suma dos arreglos (al estilo entero grande), retorna un nuevo arreglo con la suma.
- prefijoa : recibe un arreglo x y un arrreglo z y dice si z es prefijo de x.
- sufijoa : recibe un arreglo x y un arrreglo z y dice si z es sufijo de z.
-
En un archivo cat.ml escriba un programa que reciba por línea
de comandos el nombre de un archivo y que lo imprima en sálida estándar.
Cree un ejecutable y pruebelo.
(Ayuda: Para sacar parámetros recibidos por la línea de comandos
puede emplear el vector Sys.argv).
-
En un archivo archivo2.ml escriba un programa que reciba por línea
de comandos el nombre de un archivo, un entero a y
un entero b. Debe crear un archivo con el nombre recibido
en el que cada línea tiene un entero comenzando en b+1
y terminando en a-1, debe generar una excepción definida por
usted en caso de que b sea menor o igual a a.
Ejemplo si el intervalo es de 1 a 7 en el archivo debe quedar los números
del 2 al 6 de la siguiente forma:
2
3
4
5
6
-
En las fuentes de repasa revise el tipo repasa_interface
que mantiene la información requerida tanto por la interfaz modo texto
(archivo repasa.ml) como por la interfaz gráfica
(archivo repasatk.ml). En este momento están definidos dos
tipos de preguntas choiceQuestion y openQuestion. Analice
la implementación de ambas en la interfaz modo texto y después agregue
un campoHaga el cambio al registro tanto en defprog.ml
como en defprog.mli.
para un tercer tipo de pregunta ahorcadoQuestion
que presente la pregunta y permita al usuario jugar ahorcado para
responderla. Para comenzar a implementarla en la interfaz plana copie el
cuerpo de openQuestion_plain en ahorcadoQuestion_plain,
y haga los cambios necesarios en defprog.ml para que pueda
usarse. Compile y verifique sus modificaciones.
Opcional: Implemente la funcionalidad real de ahorcadoQuestion_plain.
3.2 Librerías
-
INDICADOR: Conoce diversas funciones de las librerías de Ocaml
Un módulo se compone de una interfaz donde se declaran las funciones
públicas del módulo y un archivo fuente donde se definen las funciones
(si no se escribe una interfaz explicita, se asume que todas las funciones
definidas son públicas).
Ocaml se distribuye junto con diversos módulos agrupados
en librerías que entre otras permiten:
manejo de tipos básicos, operaciones aritméticas de diversa precisión,
manejo de estructuras de datos,
operaciones con archivos, operaciones comunes en sistemas Unix.
Todas estas librerías (excepto num en el momento de este escrito) están
cubiertas con una licencia que permiten enlazarlas con código de
licencias arbitrarias.
Fuera del equipo de desarrolladores de Ocaml, otras personas han
escrito librerías con diversos propósitos, en muchos casos con licencias
generosas que permiten emplearlas en todo tipo de proyectos.
En esta guía se presentan algunas librerías y algunos ejemplos de su uso.
Librería estándar de Ocaml
La librería estándar de Ocaml (stdlib) incluye el módulo Pervasives
(que se abre por defecto) y otros módulos bastante usados (por ejemplo
List, String, etc.). Esta librería se encadena automáticamente
a programas compilados con ocamlc y es cargada automáticamente
cuando se inicia una sesión interactiva con ocaml.
En las guías anteriores se han estudiado tipos y funciones del módulo
Pervasives, así como algunas funciones y tipos de otros módulos (por ejemplo
Char, String y List). Diversos módulos de la librería
estándar se presentan a continuación agrupados por categorias
(el módulo Char se presentó completo en la guía 2, mientras que
el módulo Array en la guía 6).
La mayoría de descripciones son traducción de la documentación
de referencia (ver [3]).
Módulos para manejo de tipos básicos
Módulo String
-
String.length : string -> int retorna la longitud (número de
caracteres) de la cadena dada.
- String.get : string -> int -> char
String.get s n retorna el carácter numero n en la cadena
s. El primer caracter es el número 0. El último caracter
es el número (String.length s) - 1. Lanza Invalid_argument
si n está fuera del rango 0 a (String.length s) - 1.
También puede escribir s.[n] en lugar de String.get s n.
- String.set : string -> int -> char -> unit
String.set s n c modificar la cadena s,
remplazando el caracter número n por c.
Lanza Invalid_argument si n está fuera del rango
0 a (String.length s - 1).
También puede escribir s.[n] <- c en lugar de
String.set s n c.
- String.create : int -> string
String.create n retorna una nueva cadena de
longitud n. La cadena contiene inicialmente
caracteres arbitrarios.
Lanza Invalid_argument si n < 0 o
n > Sys.max_string_length.
- String.make : int -> char -> string
String.make n c retorna una nueva cadena de longitud n,
llenao con el caracter c.
Lanza Invalid_argument si n < 0 o
n > Sys.max_string_length.
- String.copy : string -> string
Retorna una copia de la cadena dada.
- String.sub : string -> int -> int -> string
String.sub s comienzo long retorna una nueva cadena de longitud
len,
que contiene los caracteres número comienzo a
comienzo + long - 1 de la cadena s.
Lanza Invalid_argument si comienzo y long no
designan una subcadena válida de
s; esto es, si start < 0,
o len < 0, o start + len > String.length s.
- String.fill : string -> int -> int -> char -> unit
String.fill s comienzo long c modifica la cadena s,
remplazando los caracteres número start a start + len - 1
por c.
Lanza Invalid_argument si start y len no designan
una subcadena válida de s.
- String.blit : string -> int -> string -> int -> int -> unit
String.blit fuente despfuente dest despdest long copia long
caracteres de la cadena fuente, comenzando en el caracter número
despfuente, a string dest, comenzando en el
caracter despdest. Funciona correctamente incluso si
fuente y dest son la misma cadena,
y si la fuente y el destino se sobrelapan.
Lanza Invalid_argument si despfuente y long no
designan una subcadena válida de fuente, o si
despdest y long no designan una subcadena válida de
dest.
- String.concat : string -> string list -> string
String.concat sep sl concatena la lista de cadenas sl,
insertando la cadena separadora sep entre unas y otras.
- String.iter : (char -> unit) -> string -> unit
String.iter f s aplica la función f en orden a todos
los caracteres de s. Es equivalente a
f s.(0); f s.(1); ...; f s.(String.length s - 1); ().
- String.escaped : string -> string
Retorna una copia del argumento, con los caracteres especiales
representados por secuencias de escapes, siguiendo las convenciones
léxicas de Objective Caml. Si no hay caracteres especiales
en el argumento, retorna la cadena original misma, no una copia.
- String.index : string -> char -> int
String.index s c retorna la posición de la
ocurrencia más a la izquierda del caracter
c en la cadena s.
Lanza Not_found si c no ocurre en s.
- String.rindex : string -> char -> int
String.rindex s c retorna la posición de la ocurrencia más
a la derecha del caracter c en la cadena s.
Lanza Not_found si c no courre en s.
- String.index_from : string -> int -> char -> int
Análoga a String.index, pero comienza buscando en la posición
dada como segundo argumento.
String.index s c es equivalente a String.index_from s 0 c.
- String.rindex_from : string -> int -> char -> int
Análoga a String.rindex, pero comienza buscando en la posición
dada como segundo argumento.
String.rindex s c es equivalente a String.rindex_from s
(String.length s -1) c.
- String.contains : string -> char -> bool
String.contains s c verifica si el caracter c
aparece en la cadena s.
- String.contains_from : string -> int -> char -> bool
String.contains_from s comienzo c verifica si el caracter c
aparece en la subcadena de s que comienza en comienzo hasta
el inal de s.
Lanza Invalid_argument si comienzo no es un índice
válido de s.
- String.rcontains_from : string -> int -> char -> bool
String.rcontains_from s fin c verifica si el caracter c
aparece en la subcadena de s que comienza al comienzo de
s hasta el índice stop.
Lanza Invalid_argument si stop no es un índice válido
de s.
- String.uppercase : string -> string
Retorna una copia del argumento, con todas las letras minúsculas
traducidas a mayúsculas, incluyendo letras acentuadas del
conjunto de caracteres ISO Latin-1 (8859-1).
- String.lowercase : string -> string
Retorna una copia del argumento, con todas las letras mayúsculas
traducidas a minúsculas, incluyendo letras acentuada del
conjunto de caracteres ISO Latin-1 (8859-1).
- String.capitalize : string -> string
Retorna una copia del argumento, con la primera letra en mayúscula.
- String.uncapitalize : string -> string
Retorna una copia del argumento, con la primera letra en
minúsculas.
- String.type t = string
Alias para el tipo cadena.
- String.compare : String.t -> String.t -> int
Función de comparación para cadenas, con la misma especificación
de Pervasives.compare. Junto con el tipo String.t, esta
función compare permite que el módulo String sea pasado
como argument a los funtores Set.Make y Map.Make.
Módulo List
Algunas funciones no emplean recursión de cola.
Una función que emplea recursión de cola usa espacio constante de pila,
mientras que una función que no emplee recursión de cola, usa
espacio de pila proporcional a la longitud del argumento que
sea lista, lo cual es un problema para listas muy largas.
Cuando la función recibe diversos argumentos que sean listas,
una fórmula aproximada para el uso de pila (en una unidad
constante no especificada) se presenta en paréntesis.
Las consideraciones anteriores usualmente pueden ser ignodara
si sus listas no tienen más de 10000 elementos.
-
List.length : 'a list -> int
Retorna la longtiud (número de elementos) de la lista dada.
- List.hd : 'a list -> 'a
Retorna el primer elemento de la lista dado. Lanaz
Failure "hd" si la lista es vacía.
- List.tl : 'a list -> 'a list
Retorna la lista dada sin el primero elemento. Lanza
Failure "tl" si la lista es vacía.
- List.nth : 'a list -> int -> 'a
Retorna el n-esimo elemento de la lista dada.
El primer elemento (cabeza de la lista) está en la posición 0.
Lanza Failure "nth" si la listas es muy corta.
- List.rev : 'a list -> 'a list
Invierte la lista.
- List.append : 'a list -> 'a list -> 'a list
Concatena dos listas. La misma función del operador infijo
@.
No emplea recursión de cola (longitud del primer argumento). El operador
@ tampoco emplea recursión de cola.
- List.rev_append : 'a list -> 'a list -> 'a list
List.rev_append l1 l2 invierte l1 y lo concatena a l2.
Esto es equivalente a List.rev l1 @ l2, pero rev_append emplea
recursión de cola y es más eficiente.
- List.concat : 'a list list -> 'a list
Concatena una lista de listas. Los elementos del argumento son todos
concatenadso (en el mismo orden).
No emplea recursión de cola
(longitud del argumento + longitud de la sub-lista más larga).
- List.flatten : 'a list list -> 'a list
Lo mismo que concat. No emplea recursión de cola
(longitud del argumento + longitud de la sub-lista más larga).
Iteradores
-
List.iter : ('a -> unit) -> 'a list -> unit
List.iter f [a1; ...; an] aplica la función f en orden
a a1; ...; an. Es equivalente a
begin f a1; f a2; ...; f an; () end.
- List.map : ('a -> 'b) -> 'a list -> 'b list
List.map f [a1; ...; an] aplica la función
f a a1, ..., an,
y construye la lista [f a1; ...; f an]
con los resultados retornados por f. No emplea
recursión de cola.
- List.rev_map : ('a -> 'b) -> 'a list -> 'b list
List.rev_map f l da el mismo resultado de
List.rev(List.mapf l), pero emplea
recursión de cola y es más eficiente.
- List.fold_left : ('a -> 'b -> 'a) -> 'a -> 'b list -> 'a
List.fold_left f a [b1; ...; bn] es
f (... (f (f a b1) b2) ...) bn.
En el archivo lrepasa.ml del programa repasa, puede verse
una función que emplea
List.fold_left:
(** [txt2tex s] Translates the string [s] with pure text to plain TeX *)
let rec txt2tex s =
(* order is important, \, $, {, } *)
let convt=[
("\\","$\\backslash$");
("$","\\$");
("\\$\\backslash\\$","$\\backslash$");
("{","$\\{$");
("}","$\\}$");
("%","\\%");
("_","\\_");
("&","\\&");
("#","\\#");
("^","\\^{\\kern0em}");
("~","\\~{\\kern0em}");
("¿","?`");
("¡","!`");
]
in
let s=List.fold_left apply_str_conv s convt in
let rec transquote state s =
if (String.contains s '"') then
let i=String.index s '"' in
let l=str_to s i in
let r=str_from s (i+1) in
let c=if state then "``" else "''" in
transquote (not state) (l^c^r)
else
s
in transquote true s
;;
- List.fold_right : ('a -> 'b -> 'b) -> 'a list -> 'b -> 'b
List.fold_right f [a1; ...; an] b es
f a1 (f a2 (... (f an b) ...)). No emplea recursión de cola.
Iteradores sobre dos listas
-
List.iter2 : ('a -> 'b -> unit) -> 'a list -> 'b list -> unit
List.iter2 f [a1; ...; an] [b1; ...; bn] llama en orden
f a1 b1; ...; f an bn.
Lanza Invalid_argument si las dos listas tiene
longitudes diferentes.
- List.map2 : ('a -> 'b -> 'c) -> 'a list -> 'b list -> 'c list
List.map2 f [a1; ...; an] [b1; ...; bn] es
[f a1 b1; ...; f an bn].
Lanza Invalid_argument si las dos listas tienen longitudes
diferentes. No emplea recursión de cola.
- List.rev_map2 : ('a -> 'b -> 'c) -> 'a list -> 'b list -> 'c list
List.rev_map2 f l da el mismo resultado de
List.rev(List.map2f l), pero emplea
recursión de cola y es más eficiente.
- List.fold_left2 : ('a -> 'b -> 'c -> 'a) -> 'a -> 'b list -> 'c list -> 'a
List.fold_left2 f a [b1; ...; bn] [c1; ...; cn] es
f (... (f (f a b1 c1) b2 c2) ...) bn cn.
Lanza Invalid_argument si las dos listas tienen longitudes
diferentes.
- List.fold_right2 : ('a -> 'b -> 'c -> 'c) -> 'a list -> 'b list -> 'c -> 'c
List.fold_right2 f [a1; ...; an] [b1; ...; bn] c es
f a1 b1 (f a2 b2 (... (f an bn c) ...)).
Lanza Invalid_argument si las dos listas tienen longitudes
diferentes. No emplea recursión de cola.
Recorridos en listas
Busquedas en listas
-
List.find : ('a -> bool) -> 'a list -> 'a
find p l retorna el primer elemento de la lista l
que satisfaga el predicado p.
Lanza Not_found si there is ningún valor de la lista l
satisface p.
- List.filter : ('a -> bool) -> 'a list -> 'a list
filter p l retorna todos los elementos de la lista l
que satisfacen el predicado p. El orden de los
elementos en la lista de entrada se mantiene.
- List.find_all : ('a -> bool) -> 'a list -> 'a list
find_all es otro nombre para List.filter.
- List.partition : ('a -> bool) -> 'a list -> 'a list * 'a list
partition p l retorna un par de listas (l1, l2), done
l1 es la lista de todos los elementos de l que
satisfacen el predicado p, y l2 es la lista de todos los
elementos de l que no satisfacen p.
El orden de los elementos de la lista de entrada se preserva.
Listas asociativas
-
List.assoc : 'a -> ('a * 'b) list -> 'b
assoc a l retorna el valor asociado con la llave a en la
lista de parejas l. Es decir,
assoc a [ ...; (a,b); ...] = b
si (a,b) es la asociación más a la izquierda de a en
la lista l.
Lanza Not_found si no hay valor asociado con a en la
lista l.
- List.assq : 'a -> ('a * 'b) list -> 'b
Lo mismo que List.assoc, pero emplea igualdad física en lugar
de igualdad estructural al comparar las llaves.
- List.mem_assoc : 'a -> ('a * 'b) list -> bool
Lo mismo que List.assoc, pero simplemente retorna true
si existe una asociación, y false si no existe asociación
alguna para la llave dada.
- List.mem_assq : 'a -> ('a * 'b) list -> bool
Lo mismo que List.mem_assoc, pero empleaa igualdad física en lugar
de igualdad estrucutra al comparar llaves.
- List.remove_assoc : 'a -> ('a * 'b) list -> ('a * 'b) list
remove_assoc a l retorna la lista de parejas
l sin la primera pareja con llave a, si hay alguna.
No emplea recursión de cola.
- List.remove_assq : 'a -> ('a * 'b) list -> ('a * 'b) list
Igual al List.remove_assoc, pero usa igualdad física en lugar
de igualdad estructural al comparar llaves. No emplea recursión
de cola.
Listas de parejas
-
List.split : ('a * 'b) list -> 'a list * 'b list
Transforma una lista de parejas en un par de listas:
split [(a1,b1); ...; (an,bn)] es ([a1; ...; an], [b1; ...; bn]).
No emplea recursión de cola.
- List.combine : 'a list -> 'b list -> ('a * 'b) list
Transforma un par de listas en una lista de parejas:
combine [a1; ...; an] [b1; ...; bn] es
[(a1,b1); ...; (an,bn)].
Lanza Invalid_argument si las dos listas tienen longitudes
diferentes. No emplea recursión de cola.
Ordenamiento
-
List.sort : ('a -> 'a -> int) -> 'a list -> 'a list
Ordena una lista ascendemente de acuerdo a una función de comparación.
La función de comparación debe retornar 0 si los argumentos comparados
son iguales, un entero positivo si el primero es mayor y un
entero negvor si el primer el primero es menor
(ver una especificación comnpleta en Array.sort). Por ejemplo
Pervasives.compare es una función de comparación apropiada,
siempre que no haya valores de tipo flotante NaN entre los datos.
La lista resultante está ordenada en orden ascendiente.
Se garantiza que List.sort corre en espacio de datos
constante (además del tamaño de la lista resultante) y en
espacio de pila logarítmico.
La implementación actual emplea Merge Sort.
- List.stable_sort : ('a -> 'a -> int) -> 'a list -> 'a list
Iguala a List.sort, se garantiza que el algoritmo de ordenamiento
es estable (i.e los elementos que se comparan como iguales se
mantienen en su orden origianal).
La implmentación actual emplea Merge Sort.
Corre en espacio de datos constante y espacio de pila logaritmico.
- List.fast_sort : ('a -> 'a -> int) -> 'a list -> 'a list
Igual a List.sort o List.stable_sort, el que resulte
más rapido sobre una entrada típica.
- List.merge : ('a -> 'a -> int) -> 'a list -> 'a list -> 'a list
Mezcla dos listas:
Supone que l1 y l2 son ordenadas de acuerdo a la
función de comparación cmp, merge cmp l1 l2 retornará un
lista ordenada que contiene todos los elementos de l1 y l2.
Si diversos elementos se comparan como igual, los elementos de
l1 estarán antes de los elementos de l2.
No emplea recursión de cola (suma de las longitudes de los argumentos).
Módulos para manejo de estructuras de datos
Módulo Hashtbl
Tablas de hash y funciones hash.
Las tablas de hash son tablas de asociación que permiten modificación.
Primitiva polimórfica de hash
- Hashtbl.hash : 'a -> int
Hashtbl.hash x asocia un entero positivo a un valor arbitrario de
tipo arbitrario. Se garantiza que
si x = y, entonces hash x = hash y.
Además, hash siempre termina, incluso en estructuras
cíclicas.
- Hashtbl.hash_param : int -> int -> 'a -> int
Hashtbl.hash_param n m x computa un valor de hash para x,
con las misas propiedades de hash. Los dos parámetros extra n y
m dan un control más preciso sobre el hashing. Se realiza un
recorrido a profunidad primero, de derecha a izquierda de la
estructura x, deteniendose después de n nodos significativos,
o después de m nodos significativos o no. Los nodos significativos
son: enteros, números de punto flotante, cadenas, caractres, booleanos
y constructores constantes. Valores más grandes de m y n
significan que más nodos se tienen en cuenta al computar el valor de
hash final, y por tanto es menos probable que ocurran colisiones.
Sin embargo, el hashing requiere más tiempo. Los parámetros
m y n gobiernan el equilibrio entre precisión y velocidad.
Módulo Queue
Este módulo implementa colas primero en entrar, primero en salir
con elemento modificables.
- type 'a Queue.t
El tipo de colas que contienen elementos de tipo 'a.
- exception Queue.Empty
Lanzada cuando Queue.take o Queue.peek se aplican a colas
vacías.
- Queue.create : unit -> 'a t
Retorna una nueva cola, inicialmente vacía.
- Queue.add : 'a -> 'a t -> unit
add x q agrega el elemento x al final de la cola q.
- Queue.push : 'a -> 'a t -> unit
push es sinónimo de add.
- Queue.take : 'a t -> 'a
take q elimina y retorna el primer elemento de la cola
q, o lanza Empty si la cola es vacía.
- Queue.pop : 'a t -> 'a
pop es sinónimo de take.
- Queue.peek : 'a t -> 'a
peek q retorna el primer elemento de la cola q, sin
eliminarlo de la cola, o lanza Empty si la cola está vacía.
- Queue.top : 'a t -> 'a
top es sinónimo de peek.
- Queue.clear : 'a t -> unit
Descarta todos los elementos de la cola.
- Queue.copy : 'a t -> 'a t
Retorna una copia de la cola dada.
- Queue.is_empty : 'a t -> bool
Retorna true si la cola dada está vacía, false en otro caso.
- Queue.length : 'a t -> int
Retorna el número de elementos en una cola.
- Queue.iter : ('a -> unit) -> 'a t -> unit
iter f q aplica f en sucesión a todos los elementos de
q, desde el ingresado menos recientemente hasta el ingresado más
recientemente. La cola misma no es modificada.
- Queue.fold : ('a -> 'b -> 'a) -> 'a -> 'b t -> 'a
fold f accu q es equivalente a List.fold_left f accu l,
donde l es la lista con los elementos de q. La cola no
es modificada.
- Queue.transfer : 'a t -> 'a t -> unit
transfer q1 q2 agrega todos los elementos de q1 al final
de la cola q2, después limpia q1. Es equivalente a la secuencia
iter (fun x -> add x q2) q1; clear q1, pero se ejecuta en tiempo
constante.
Módulo Stack
Este módulo implementa una pila último en entrar, primero en salir, con
elementos modificables.
- type 'a Stack.t
El tipo de pilas que contienen elementos de tipo 'a.
- exception Stack.Empty
Lanzada cuando se aplican Stack.pop o Stack.top a pilas
vacías.
- Stack.create : unit -> 'a t
Retorna una nueva pila, inicialmente vacía.
- Stack.push : 'a -> 'a t -> unit
push x s agrega el elemento x en el tope de la pila s.
- Stack.pop : 'a t -> 'a
pop s elimina y retorna el elemento del topo de la
pila s, o lanza Empty si la pila está vacía.
- Stack.top : 'a t -> 'a
top s rtorna el elemento que está en el tope de la pila s,
or lanza Empty si la pila está vacía.
Por ejemplo en las fuentes de la librería Markup (ver [6]),
se emplea la siguiente porción de código que tiene la funcionalidad de
top:
method current =
(* Get the top element of the element stack *)
(* In O'Caml 3: Stack.top *)
try
let x = Stack.pop elstack in
Stack.push x elstack;
let el, entid = x in
el
with
Stack.Empty -> assert false
(* Not possible, because the VR is always the element at the
* bottom of the stack.
*)
- Stack.clear : 'a t -> unit
Descarta todos los elementos de una pila.
- Stack.copy : 'a t -> 'a t
Retorna una copia de la pila dada.
- Stack.is_empty : 'a t -> bool
Retorna true si la pila dada está vacía, false en otro caso.
- Stack.length : 'a t -> int
Retorna el número de elementos de una pila.
- Stack.iter : ('a -> unit) -> 'a t -> unit
iter f s aplica f en sucesión a los elementos s,
desde el elemento que está en el tope hasta el del fondo.
La pila no es modificada.
Módulo Buffer: Buffer expandible para cadenas
Este módulo implementa un buffer para cadenas que se
expande automáticamente cuando lo necesita. Ofrece concatenación
acumulativa de cadenas en tiempo casi lineal (en lugar del tiempo
cuadrático que requiere la concatenación de un par de cadenas).
-
type Buffer.t
Tipo abstracto de un buffer.
- Buffer.create : int -> t
create n retorna un buffer nuevo, inicialmente vació.
El parámetro n es el tamaño de la cadena interna inicial que
mantendrá el contenido del buffer. Tal cadena es relocalizada
automáticamente cuando se almacenan más de n caracteres en
el buffer, pero vuelve a encogerse a
n caracteres cuando se llama reset.
Para una mejor desempeño, n deb ser del mismo orden de
magnitud que el número de caracteres que se espera almacenar
en el buffer (por ejemplo, 80 para un buffer que mantendrá
una línea de salida).
No ocurrirá nada mal si el buffer crece más alla de este límite.
En caso de duda, tome por ejemplo n = 16.
Si n no está entre 1 y Sys.max_string_length, será
ajustado a ese intervalo.
- Buffer.contents : t -> string
Retorna una copia del contenido del buffer. El buffer mismo
no es modificado.
- Buffer.length : t -> int
Retorna el número de caracters que contien el buffer.
- Buffer.clear : t -> unit
Limpia el buffer.
- Buffer.reset : t -> unit
Limpia el buffer, y deslocaliza la cadena interna que mantiene el
contenido del buffer, remplazandolo con la cadena interna inicial
de longitud n como fue localizada por Buffer.create n.
Para buffers de larga vida que pudieran haber crecido mucho,
reset permite reclamar más rapidamente el espacio empleado por
el buffer.
- Buffer.add_char : t -> char -> unit
add_char b c agrega el caracter c al final del buffer
b.
- Buffer.add_string : t -> string -> unit
add_string b s agrega la cadena s al final del buffer b.
- Buffer.add_substring : t -> string -> int -> int -> unit
add_substring b s ofs long toma long caracteres desde la
posición ofs de la cadena s y los agrega al final del buffer
b.
- Buffer.add_substitute : t -> (string -> string) -> string -> unit
add_substitute b f s agrega la cadena patrón s al final
del buffer b substituyendo.
El proceso de substitución busca variables en el patron y sustituye cada
nombre de variable por su valor, como se obtiene al aplicar la función
f al nombre de la variable. En la cadena patrón, el nombre de
una variable está precedido de un caracter no escapado
$ y es una de las siguientes::
-
una secuencia no vacía de caracteres alfanuméricos o el caracter
_,</li>
- una secuencia arbitraria de caracteres encerrada por un par de
paréntesis o llaves.
- Un caracter $ escapado es un $ precedido por un
caracter backslash; representa un $ plano.
Lanza Not_found si el caracter que cierra una variable entre
parentesis o llaves no se encuentra.
- Buffer.add_buffer : t -> t -> unit
add_buffer b1 b2 agrega el contenido actual del buffer b2
al final del buffer b1. b2 no es modificado.
- Buffer.add_channel : t -> Pervasives.in_channel -> int -> unit
add_channel b ic n lee exactamente n caracteres del canal
de entrada ic y los almacena al final del buffer b.
Lanza End_of_file si el canal tiene menos de n
caracteres.
- Buffer.output_buffer : Pervasives.out_channel -> t -> unit
output_buffer oc b escrite el contenido actual del buffer b
en el canal de salida oc.
Módulo Set. Conjuntos sobre tipos ordenados
Este módulo implementa la estructura de datos conjunto, dada una función
sobre un conjunto de elementos, que lo ordene totalmente. Todas las
operaciones sobre conjuntos son puramente aplicativas (sin efectos
laterales).
La implementación usa árboles binarios balanceados, y por tanto
es razonablemente eficiente: membrecia e inserción toma tiempo
logartimico en el tamañno del conjunto, por ejemplo.
Esta estructura de datos está implementada como un functor (i.e
una función de estructuras en estructuras),
para usarla debe definirse un módulo nuevo instanciando el
functor que instancie la estructura de datos a
un tipo particular. El nombre del functor es
Set.Make, el siguiente ejemplo crea un módulo IdSet1, que es un conjunto de triplas de cadenas:
module IdSet=Set.Make(struct
type t=string*string*string
let compare (s1,s2,s3) (sa,sb,sc) =
if (s1<sa || (s1=sa && s2<sb)
|| (s1=sa && s2=sb && s3<sc)) then
-1
else if (s1=sa && s2=sb && s3=sc) then
0
else
1
end)
;;
Este módulo IdSet define las funciones para conjuntos que se
explicaran a continuación (por ejemplo IdSet.is_empty).
-
type elt
Tipo de los elementos del conjunto.
- type t
Tipo de los conjuntos.
- Set.empty : t
El conjunto vació.
- Set.is_empty : t -> bool
Chequea si un conjunto es vacío o no.
- Set.mem : elt -> t -> bool
mem x s chequea si x pertenece al conjunto s.
- Set.add : elt -> t -> t
add x s retorna uun conjunto que contiene todos los
elementos de s, y a x. Si x ya estaba en s,
s es retornado sin cambios.
- Set.singleton : elt -> t
singleton x retorna el conjunto de un elemento que contiene a x.
- Set.remove : elt -> t -> t
remove x s retorna un conjunto que contiene todos los elementos de
s, excepto x. Si x no estaga en s, s es
retornado sin cambios.
- Set.union : t -> t -> t
Unión de conjuntos.
- Set.inter : t -> t -> t
Intersección de conjuntos.
- Set.diff : t -> t -> t
Diferencia entre conjuntos.
- Set.compare : t -> t -> int
Ordenamiento total entre conjuntos. Puede emplearse como función de
comparación para hacer conjuntos de conjuntos.
- Set.equal : t -> t -> bool
equal s1 s2 chequea si los conjuntos s1 y s2 son
iguales, es decir, si contienen elementos iguales.
- Set.subset : t -> t -> bool
subset s1 s2 chequea si el conjunto s1 es subconjunt del
conjunto s2.
- Set.iter : (elt -> unit) -> t -> unit
iter f s aplica f en sucesión a todos los elementos
de s.
El orden en el que los elementos de s se presentan
a f no está especificado.
- Set.fold : (elt -> 'a -> 'a) -> t -> 'a -> 'a
fold f s a computa (f xN ... (f x2 (f x1 a))...),
donde x1 ... xN son los elementos de s.
El orden en el que los elementos de s se presentan
a f no está especificado.
- Set.for_all : (elt -> bool) -> t -> bool
for_all p s verifica si todos los elementos del conjunto
satisfacen el predicado p.
- Set.exists : (elt -> bool) -> t -> bool
exists p s verifica si al menos un elemento del
conjunto satisface el predicado p.
- Set.filter : (elt -> bool) -> t -> t
filter p s retorna el conjunto de todos los elementos
en s que satisfacen el predicado p.
- Set.partition : (elt -> bool) -> t -> t * t
partition p s retorna un par de conjuntos (s1, s2), donde
s1 es el conjunto de todos los elementos de s que satisface
el predicado p, y s2 es el conjunto de todos los elementos
de s que no satisfacen p.
- Set.cardinal : t -> int
Retorna el número de elementos de un conjunto.
- Set.elements : t -> elt list
Retorna la lista de todos los elementos del conjunto dado.
La lista retornada está ordenada ascendemente con respecto al
orden Ord.compare, donde Ord es el argumento dado
a Set.Make.
- Set.min_elt : t -> elt
Retorna el elemento más pequeño del conjunt dado
(con respecto al orden Ord.compare), o lanza
Not_found si el conjunto es vacío.
- Set.max_elt : t -> elt
Igual que Set.S.min_elt, pero retorna el mayor elemento del
conjunto dado.
- Set.choose : t -> elt
Retorna un elemento del conjunto dado, o lanza Not_found si
el conjunto es vacío. El elemento escogido no está especificado,
pero elementos iguales serán retornados con conjuntos iguales.
Módulo Map: Tablas asociativas sobre tipos ordenado
Este modulo implementa tablas asociativas de forma alicativa, también
se conocen como mapeos finitos o diccionarios, dado una función
que ordene totalmente las llaves.
Todas las operaciones sobre mapeos son puramente aplicativas
(sin efectos laterales). La implementaciónn usa árboles binarios
balanceados, y por tanto la busqueda y la isnerción toman
tiempo logaritimico en el tamaño del mapeo.
De forma análoga a Set, debe instanciarse un mapeo para un
tipo particular con el functor Map.Make. El nuevo módulo
producido podrá emplearse con las siguientes funciones:
- type key
Tipo de las llaves del mapeo.
- type +'a t
Tipo de los mapeos del tipo key al tipo 'a.
- Map.empty : 'a t
El mapeo vacío.
- Map.add : key -> 'a -> 'a t -> 'a t
add x y m retorna un mapeo que contiene las misma asociaciones
de m, además de una asociación de x a y.
Si x ya estaba asociada en m, su asociación anterior
desaparece.
- Map.find : key -> 'a t -> 'a
find x m retorna la asociación actual de x en m,
o lanza Not_found si no existe tal asociación.
- Map.remove : key -> 'a t -> 'a t
remove x m retorna un mapeo que contiene las mismas asociaciones de
m, excepto porque x no está asociado en el mapeo retornado.
- Map.mem : key -> 'a t -> bool
mem x m retorna true si m contiene una asociación
para x, o false en otro caso.
- Map.iter : (key -> 'a -> unit) -> 'a t -> unit
iter f m aplica f a todas las asociaciones en el mapeo
m. f recibe la llave como primer argumento, y el valor asociado
como segundo argumento. El orden en el cual la asociación se pasa a
f no está especificado. Sólo las asociaciones actuales se
presenta a f: asociaciones escondidas por asociaciones más
recientes no son pasadas a f.
- Map.map : ('a -> 'b) -> 'a t -> 'b t
map f m retorna un mapeo con el mismo dominio de m, done
el valor asociado x de cada asociación de m se ha
remplazado por el resultado de la aplicación de
f a x.
El orden en el que los valores asociados se pasan a f
no está especificado.
- Map.mapi : (key -> 'a -> 'b) -> 'a t -> 'b t
Lo mismo que Map.S.map, pero la función recibie como argumentos tanto
la llave como el valor asociado por cada asociación en el mapeo.
- Map.fold : (key -> 'a -> 'b -> 'b) -> 'a t -> 'b -> 'b
fold f m a computa (f kN dN ... (f k1 d1 a)...),
donde k1 ... kN son las llaves de todas las asociaciones de m,
y d1 ... dN son los datos asociados.
El orden en el cual las asociaciones se presenta a f no
está especificado.
Módulos relaciones con aritmética
Módulo Random: Generadores de números seudo-aleatorios
Funciones básicas
- Random.init : int -> unit
Inicializa el generado, usano el argumento como semilla.
La misma semilla siempre producirá la misma secuencia de números.
- Random.full_init : int array -> unit
Lo mismo que Random.init pero recibe más datos como semilla.
- Random.self_init : unit -> unit
Inicializa el generador con una semila más o menos aleatorioa de
una manera dependiente del sistema.
- Random.bits : unit -> int
Retorna 30 bits aleatorios en un entero no negativo.
- Random.int : int -> int
Random.int bound retorna un entero aleatorio entre 0 (inclusive)
y bound (exclusivo). bound debe ser mayor a 0 y
menor que 230.
- Random.int32 : Int32.t -> Int32.t
Random.int32 bound retorna un entero aleatorio entre 0 (inclusive)
y bound (exclusivo). bound debe ser mayor a 0.
- Random.nativeint : Nativeint.t -> Nativeint.t
Random.nativeint bound retorna un entero aleatorio entre 0
(inclusive) y bound (exclusivo). bound debe ser
mayor que 0.
- Random.int64 : Int64.t -> Int64.t
Random.int64 bound retorna un entero aleatorio entre 0
(inclusive) y bound (exclusivo). bound debe ser
mayor que 0.
- Random.float : float -> float
Random.float bound retorna un número de punto f
lotante entre
0 (inclusive) y bound (exclusivo). Si bound es
negativo, el resultado es negativo o cero.
Si bound es 0, el resultado es 0.
- Random.bool : unit -> bool
Random.bool () retorna true o false con probabilidad
0.5 cada uno.
También es posible manipular el estado del generado, con el
modulo Random.State. El estado del generado se obtiene con
Random.get_state: unit -> Random.State.t , y se
establece con
Random.set_state: Random.State.t -> unit .
Módulo Complex : Números complejos
Este módulo ofrece operaciones aritméticas sobre números complejos.
Los números complejos se representan por sus partes real e imaginaria
(representación cartesiana). Cada parte se representa con un
número de punto flontate de doble precisión (tipo float).
Módulo Int32: enteros de 32 bits
Este modulo provee operaciones sobre el tipo int32
de enteros con signo de 32 bits. A diferencia del tipo incorporado
int, el tipo int32 se garantiza que es exactamente de 32
bits de ancho en toda plataforma. Todas las operaciones aritméticas
sobre int32 se toman módulo 231.
Para escribir constantes de este tipo puede emplear el postfijo l.
Por ejemplo 0l se refiere al cero de tipo int32.
Nota sobre desempeño: los valores de tipo int32 ocupan más
memoria que los valores de tipo int, y las operaciones
aritméticas sobre int32 son generalmente más lentas que
las operaciones sobre int. Use int32 sólo
cuando la aplicación requiera aritmética exacta de 32 bits.
-
Int32.zero : int32
El entero 0 de 32 bits.
- Int32.one : int32
El entero 1 de 32 bits.
- Int32.minus_one : int32
El entero -1 de 32 bits
- Int32.neg : int32 -> int32
Negación unaria.
- Int32.add : int32 -> int32 -> int32
Suma.
- Int32.sub : int32 -> int32 -> int32
Resta.
- Int32.mul : int32 -> int32 -> int32
Multiplicación.
- Int32.div : int32 -> int32 -> int32
Divsión entera. Lanza Division_by_zero si el segundo
argumento es cero. Esta división redondea el coociente de su
argumento a cero, como se especifica para
Pervasives..
- Int32.rem : int32 -> int32 -> int32
Residuo entero. Si y no es cero, el resultado de
Int32.rem x y satisface las siguientes propiedades:
Int32.zero <= Int32.rem x y < Int32.abs y y
x = Int32.add (Int32.mul (Int32.div x y) y) (Int32.rem x y).
Si y = 0, Int32.rem x y lanza Division_by_zero.
- Int32.succ : int32 -> int32
Sucesor. Int32.succ x es lo mismo que Int32.add x Int32.one.
- Int32.pred : int32 -> int32
Predecesor. Int32.pred x es lo mismo que Int32.sub x Int32.one.
- Int32.abs : int32 -> int32
Retorna el valor absoluto de su argumento.
- Int32.max_int : int32
El entero de 32 bits más grande que puede representarse, 231-1.
- Int32.min_int : int32
El menor entero de 32 bits que puede representarse,
-231.
- Int32.logand : int32 -> int32 -> int32
Cojunción lógica a nivel de bits.
- Int32.logor : int32 -> int32 -> int32
Disyunción lógica a nivel de bits.
- Int32.logxor : int32 -> int32 -> int32
O exclusivo a nivel de bits.
- Int32.lognot : int32 -> int32
Negación lógica a nivel de bits.
- Int32.shift_left : int32 -> int -> int32
Int32.shift_left x y corre x a la izquerda y bits.
El resultado no está especificado si y < 0 o y >= 32.
- Int32.shift_right : int32 -> int -> int32
Int32.shift_right x y corre x a la derecho y bits.
Este es un corrimiento aritmético: el bit de signo de x es replicado
e insertado en los bits desocupados.
El resultado no está especificado si y < 0 o y >= 32.
- Int32.shift_right_logical : int32 -> int -> int32
Int32.shift_right_logical x y corre x hacia la derecha,
y bits.
Este es un corrimiento lógico: se insertan ceros en los bits
desocupados, independiente del signo de x.
El resultado no está especificado si y < 0 o y >= 32.
- Int32.of_int : int -> int32
Convierte el entero dado (tipo int) a un entero de 32-bits
(tipo int32).
- Int32.to_int : int32 -> int
Convierte el entero de 32-bits dado (tipo int32) a un
entero (tipo int). En plataformas de 32-bits, los enteros
de 32-bits son tomados módulo 231, i.e. el bit del orden más
alto se pierde durante la conversión. En plataformas de 64 bits
la conversión es exacta.
- Int32.of_float : float -> int32
Convierte el número de punto flotanto a un entero de 32 bits,
descartando la parte decimal (trunca hacía 0).
El resultado de la conversión es indefinido si después de
truncar, el número está fuera del rango
[Int32.min_int, Int32.max_int].
- Int32.to_float : int32 -> float
Convierte el entero de 32 bits a un número de punto flotante.
- Int32.of_string : string -> int32
Convierte la cadena dada a un entero de 32 bits.
La cadena se lee en decimal (por defecto) o en hexadecimal o
en octal o en binario si la cadena comienza con 0x,
0o o 0b respectivamente.
Lanza Failure "int_of_string" si la cadena dada no
es una representación válida de un entero.
- Int32.to_string : int32 -> string
Retorna la representación como cadena del argumento, es un decimal
con signo.
- type t = int32
Un álias para el tipo de enteros de 32 bits.
- Int32.compare : t -> t -> int
La función de comparación para enteros de 32 bits, con la misma especificación
de Pervasives.compare. Junto con el tipo t, esta función
compare permite que el módulo Int32 sea pasado como argumento
a los functores Set.Make y Map.Make.
Módulo Int64: enteros de 64 bits
El módulo Int64 permite operar con enteros de 64 bits. Tiene funciones
análogas y con los mismos nombres que las funciones de Int32.
Para escribir constantes de este tipo puede
emplear el postfijo L (e.g. 0L es el cero de tipo int32.
Las funciones novedosas de Int64 con respecto a Int32 son:
-
Int64.of_int32 : int32 -> int64
Convierte el entero de 32 bits dado a un entero de 64.
- Int64.to_int32 : int64 -> int32
Convierte el entero de 64 bits dato a un entero de 32 bits. El
entero de 64 bits se toma módulo 232, i.e. los 32 bits superiores
se pierden durante la conversión.
- Int64.of_nativeint : nativeint -> int64
Convierte el entero nativo dado (tipo nativeint)
a un entero de 64-bits (tipo int64).
- Int64.to_nativeint : int64 -> nativeint
Convierte el entero de 64 bits datos a un entero nativo.
En plataformas de 32 bits, el entero de 64 bits es tomado módulo
232. En plataformas de 64 bits la conversión es exacta.
Módulo NativeInt: enteros nativos para la plataforma de ejecución
Este módulo provee operaciones sobre el tipo nativeint de
enteros de 32 bits con signo (en plataformas de 32 bits) o de
enteros de 64 bits con signo (en plataformas de 64 bits).
Este tipo entero tiene exacatmente el mismo ancho que el del
tipo entero long en el compilador de C.
Todas las operaciones aritméticas sobre nativeint se tomand
módulo 232 o 264 dependiendo del tamaños de la palabra ne
arquitectura.
Nota sobre el desempeño: los valores de tipo nativeint ocupan
más espacio en meoria que los valores de tipo int, y las
operaciones aritméticas sobre nativeint son generalmente
más lentas que las de int. Emplee nativeint
sólo cuando la aplicación requiera el bit extra de precisión
que nativeint da por encima del tipo int.
El módulo NativeInt define funciones con los mismos nombres
del módulo Int32, las novedades con respecto a ese son:
-
NativeInt.size : int
El tamaño en bits de un entero nativo. Es igual a
32 en plataformas de 32 bits y a 64 en
plataformas de 64 bits.
- NativeInt.of_int32 : int32 -> nativeint
Convert the given 32-bit integer (type int32)
to a native integer.
- NativeInt.to_int32 : nativeint -> int32
Convert the given native integer to a
32-bit integer (type int32). On 64-bit platforms,
the 64-bit native integer es taken modulo 2<sup class="superscript">32</sup>,
i.e. the top 32 bits are lost. On 32-bit platforms,
the conversion es exact.
3.2.2 Hacía la práctica
Cada unidad de compilación se llama un módulo, estas unidades constan
de: declaración de funciones, definición. Nombres de archivos deben
comenzar en minúscula.
Pueden usarse
funciones definidas en un módulo desde otro de una de las siguientes formas:
1. Se abre el otro módulo con:
open String;;
de forma que todas las funciones y nombres del módulo quedan
disponibles o
2. Se emplean funciones o nombres con el nombre del módulo como
prefijo.
La excepción a estas reglas es el módulo Pervasives que siempre
está abierto por defecto y que incluye la definición de los
tipos primitivos y de diversdiad de funciones.
3.2.3 Lecturas recomendadas
Referencía de las librerías de [3].
3.2.4 Ejercicios de programación
-
Escriba el módulo sust.ml en el que defina una función
sust:string -> (string*string) -> Buffer.t, que aplique
a una cadena todas las sustituciones que puedan hacerse de
una cadena patron por un objetivo y que retorne la cadena resultante
en un buffer (ver el módulo Buffer de esta guía).
Por ejemplo
sust "la vida, Vida" ("ida","erdad") debe
retornar un buffer con contenido "la verdad, Verdad" . Escriba también la
interfaz sust.mli que sólo debe hacer pública la función
sust. Ayuda: puede basarse en la función
rep_str del módulo lrepasa.ml de repasa.
-
Usando el módulo Sust del punto anterior escriba el programa
proto-sed.ml que después de compilado, pueda ejecutarse con dos
parámetros:
-
Una sustitución por realizar con la sintaxis
s/patron/sustitucion/g.
- El nombre de un archivo tipo texto.
El programa debe presentar por salida estandar el contenido del archivo
texto tras aplicar la sustitución a todas las ocurrencias del patrón
por la sustitución. Por ejemplo si existe un archivo carta.txt,
desde la línea de comandos debe poderse ejecutar:
$ proto-sed s/ida/erdad/g carta.txt
-
En un archivo usoset.ml copie la función ord_words
del archivo defprog.ml de repasa así como otras porciones
que esta función requiera (por ejemplo el tipo
infoword) y las funciones fpol_random,
fpol_e, fpol_w y fpol_p.
Escriba una función pide_datos
que pida al usuario varias 4-tuplas de datos (tantas como el
usuario desee), cada una consta de 3 cadenas y un número flotante,
la misma función debe agregar la información dada por el usuario
a una tabla de Hashing que pueda ser usada por ord_word, después
debe llamar la función ord_word usando
fpol_random como primer parámetro, 1.0 como segundo parámetro,
la tabla de hashing como tercer parámetro y una referencia a una
lista vacía como cuarto parámetro, después debe presentar la lista
de 4-tuplas que recibe de la función.
Experimente cambiando la función que recibe como primer parámetro
(por ejemplo por fpol_e) y con base en sus experimentos
determine para que se usa la función ord_word en repasa.
-
Opcional: En que caso se usa la función fpol_pgm
con la interfaz plan de repasa? Haga un ejemplo de política que
la use.
- 1
-
Este ejemplo es tomado del archivo defprog.ml de repasa
(ver [7])
Capítulo 4 Solución a los ejercicios
Respuestas a ejercicios de 1.1.4
-
3+3
- 7
- 8
- 11
- 14
- 6.7.4
- string->string->string
- fun x->(float_of_int x)+.0.5
- let dif02=fun f x->dif f x 0.3;;
- 0.3
Respuestas a ejercicios de programacion de 1.1.5
-
areas.ml
Respuestas a ejercicios de 2.1.4
-
let cuadrado = fun x -> x*x;;
- let cubo = fun x -> x*(cuadrado x);;
- factorial
- No hay diferencia
- pattern matching
- suma: int -> int -> (int -> int) -> int
serie: (int -> int) -> int -> int -> int
- n>=i
- Char.chr
- 'a list -> 'a -> bool
- .
Respuestas a ejercicios de programacion de 2.1.5
-
let listavacia l= (l=[]);;
let rec invertir = function
| [] -> []
| h::t -> (invertir t)@[h]
;;
let rec esta_elemento x = function
| [] -> false
| h::t -> x=h || (esta_elemento x t)
;;
let rec num_ocurrencias x = function
| [] -> 0
| h::t -> (if h=x then 1 else 0)+(num_ocurrencias x t)
;;
let rec espalindrome l= ((invertir l)=l);;
Respuestas a ejercicios de 2.2.4
-
true
- int
- [1]
- "wq"::"io"::[]
- int*int->int*int*(int*int)
- (0,[])
- el primero cuyo patrón concuerde
- ('a -> 'b) -> 'a list -> 'b list
- let f x=2*x in
map f [3;0;1;8];;
- 4
Respuestas a ejercicios de programacion de 2.2.5
-
-.
- let rec filter p = function
| [] -> []
| h::t -> (if p h then [h] else [])@(filter p t)
;;
Respuestas a ejercicios de 2.3.6
-
23
- .
- .
- .
- .
- .
# List.length (List.filter (fun x -> x='a') ['1';'2';'a';'b';'c';'a';'c']);;
- : int = 2
List.map: ('a -> 'b) -> 'a list -> 'b list\\
List.map2: ('a -> 'b -> 'c) -> 'a list -> 'b list -> 'c list
- .
- .
Respuestas a ejercicios de programacion de 2.3.7
-
.
- .
- .
Respuestas a ejercicios de 2.4.4
-
type trivaluada = Si | No | NoSabe;;
type intinf=Ent of int | InfP | InfN;;
let sumaii e1 e2= match (e1,e2) with
| Ent i1, Ent i2 -> Ent (i1+i2)
| Ent _, InfP -> InfP
| InfP, Ent _ -> InfP
| InfN, Ent _ -> InfN
| Ent _, InfN -> InfN
| _,_ -> raise (Invalid_argument "No puede sumar InfP con InfN");;
- racinf -> racinf o intinf * intinf -> intinf * intinf
let mayor p1 p2 = p1.edad > p2.edad;;
type exprar = Const of int | InvAd of exprar | Suma of exprar * exprar |
Resta of exprar * exprar | Prod of exprar * exprar |
Div of exprar * exprar;;
let rec evalua = function
| Const i -> i
| InvAd e -> -(evalua e)
| Suma (e1,e2) -> (evalua e1)+(evalua e2)
| Resta (e1,e2) -> (evalua e1)-(evalua e2)
| Prod (e1,e2) -> (evalua e1)*(evalua e2)
| Div (e1,e2) -> (evalua e1)/(evalua e2)
;;
evalua (Prod (Suma (Const 3,Const 5),InvAd (Const 4)));;
type 'a narb=Vacio | Nodo of 'a*(('a narb) list);;
let rec npeso2 =
let rec npeso_nnod=function
| Hoja _ -> 1
| Sub (_,l) -> (List.fold_left (fun a s -> a+(npeso_nnod s)) 1 l)
in
function
| Vacio2 -> 0
| Nodo2 n -> npeso_nnod n
;;
exception VacioComoSubarbol;;
let rec narbBueno=function
| Vacio -> ()
| Nodo (_,l) ->
if (List.exists (fun n -> n=Vacio) l) then
raise VacioComoSubarbol
else
List.iter narbBueno l
;;
let npeso3 a=try
narbBueno a;
npeso a
with
VacioComoSubarbol -> -1
;;
Respuestas a ejercicios de programacion de 2.4.5
-
let sublistas l=
let rec sublistas_aux = function
| [] -> ([],[])
| h::t -> let (ht,rt)=sublistas_aux t in
([h]::(List.map (fun l -> h::l) ht)),ht@rt
in
let (l1,l2)=sublistas_aux l in
l1@l2
;;
-
(** Types of heuristics to test answers *)
type heuresp=HCaseSensitive | HCaseInsensitive | HIgnoreSpaces;;
(** [test_equal_answer heur w ans] tests if the answer [ans] is equal to
the word [w] by using the heuristics [heur]. *)
let rec test_equal_answer heur w ans = match heur with
| HCaseInsensitive ->
(String.lowercase w)=(String.lowercase ans)
| HCaseSensitive ->
w=ans
| HIgnoreSpaces ->
trim w = trim ans
;;
Esta nueva definición debe moverse para que quede después de la definición
de la función trim (aún mejor ponerla antes de la función
parse_heur). parse_heur podría ser:
Respuestas a ejercicios de 3.1.2
-
Array.length
- Array.length
- let v = Array.create 5 (-1).
- let c x = Array.create x ' ';;
- el vector que se desea copiar
- abierto
- let x = ref 5;;
- # (!x)+(!z);;
- : int = 0
- Sys_error.open.out.
- open_in "leer.txt";;.
Respuestas a ejercicios de programacion de 3.1.3
-
.
- .
- .
- .
Respuestas a ejercicios de programacion de 3.2.4
-
let sust s (x,y)=
let res=Buffer.create (String.length s) in
let rec aux nl nx =
if (nx>0 && nx=(String.length x)) then
begin
Buffer.add_string res y;
aux (nl+nx) 0 ;
end
else if (nl>=(String.length s)) then
()
else if (nl+nx>=(String.length s)) then
Buffer.add_string res (str_from s nl)
else
begin
let cs=String.get s (nl+nx) in
let cx=String.get x nx in
if (cs=cx) then
aux nl (nx+1)
else
begin
Buffer.add_string res (String.sub s nl 1);
aux (nl+1) 0 ;
end;
end
in aux 0 0 ;
res
;;
Bibliografía
- [1]
-
Emmanuel Chailloux, Pascal Manoury, and Bruno Pagano.
Developing applications with objective caml.
http://caml.inria.fr/oreilly-book/html/index.html, 2002.
- [2]
-
John Harrison.
Introduction to functional programming.
http://www.cl.cam.ac.uk/Teaching/Lectures/funprog-jrh-1996/index.html, 1996.
- [3]
-
Xavier Leroy.
The objective caml system release 3.07.
http://caml.inria.fr/ocaml/htmlman/, 2003.
- [4]
-
Vladimir Támara Patiño.
Manta 3: Formal description.
http://manta.sourceforge.net/doc/formal3/, 1999.
- [5]
-
Basile Starynkevitch.
Ocamljitrun.
http://cristal.inria.fr/ starynke/ocamljit.html, 2004.
- [6]
-
Gerd Stolpmann.
Markup - a library to parse and validate xml in ocaml.
http://ocaml-programming.de/markup/, 2004.
- [7]
-
Vladimir Támara.
repasa - herramientas y formatos para crear y repasar contenidos.
http://structio.sourceforge.net/repasa, 2004.
Index
-
+, 1.1.1
- 0, 3.1.1
- [], 2.2.1
- #trace, 2.4.2
- #use, 1.1.2
- true y false, 2.4.1
- Array.create, 3.1.1
- anónima, 2.3.1
- and, 2.2.2
- atómicos, 2.1.1
- bool, 2.1.1
- Char.chr, 2.1.1
- char, 2.1.1
- exception, 2.4.1
- float, 2.1.1
- float_of_int, 2.1.1
- flotante, 1.1.1
- fold, 2.3.1
- for, 3.1.1
- imperativo, 3.1.1
|
- in, 2.2.1
- let, 1.1.1
- list, 2.1.1
- map, 2.3.1
- match, 2.2.1
- matriz, 3.1.1
- min, 2.3.1
- mutable, 3.1.1
- Orientada a objetos, 3.1.1
- ocamldebug, 2.4.2
- ocamldoc, 2.2.2
- open_in, 3.1.1
- polimórficas, 2.3.1
- print_endline, 2.2.1
- print_string, 3.1.1
- Recursión, 2.1.1
- recolector de basura, 1.1.1
- recursivo, 2.4.1
- Sys_error.open_out, 3.1.1
- sqrt, 2.1.1
- string, 1.1.1
- string*float, 2.4.1
- type, 2.4.1
|
This document was translated from LATEX by
HEVEA.