Introducción esquemática al SML

J. L. Freire Nistal

Contents

1 Un programa sencillo 3 2 Orígenes del ML 5 3 Características del SML 6 4 Expresiones básicas, valores y tipos 7 5 Tipos básicos 8 6 Tipos compuestos 11 7 Identificadores, acotaciones y declaraciones 13 8 SML bajo UNIX 15 9 Valores estructurados 16 10 Patrones (Patterns) 17 11 Coincidencias (Matches) 19 12 Funciones 20 13 Polimorfismo y sobrecarga 24 14 Igualdad estructural 25 15 Igualdad vs. Reconocimiento de patrones 25 16 Definición de tipos 26 17 Ejemplos 32 18 Excepciones 33 19 Aspectos imperativos 34 20 I/O 36

1 Un programa sencillo

Consideremos la siguiente definición de un tipo de dato Diccionario:

- abstype 'a Diccionario = DIC of (int*'a) list
  with
val nulDict = DIC nil
        exception Nada
fun mirar (clave:int)
 (DIC lista_entrada: 'a Diccionario):'a =
          let fun buscar nil = raise Nada
                 |buscar (((k:int),elemento)::entradas)=
                     if clave = k then elemento
     else if (clave<k) then raise Nada
                     else buscar entradas
          in
              buscar lista_entrada
          end

        fun engadir (nueva_entrada as (clave:int,elemento:'a))
                    (DIC lista_entrada):'a Diccionario =
         let fun actualizar nil = [nueva_entrada]
                |actualizar ((entrada as (k,_))::entradas) =
                    if clave = k then nueva_entrada::entradas
                    else if (clave<k) then
                        nueva_entrada::entrada::entradas
                    else entrada::actualizar entradas
        in
           DIC (actualizar lista_entrada)
        end
end;

El resultado de esta declaración es el siguiente conjunto de acotaciones:

type'a   Diccionario
val nulDict = - : 'a Diccionario

exception Nada
val mirar = fn : int -> 'a Diccionario -> 'a
val engadir = fn : int * 'a -> 'a Diccionario -> 'a Diccionario

que puede usarse como sigue:

-val miDicc = engadir (1,["dog","perro"]) nulDict;
val miDicc = - : string list Diccionario
-val miDicc = engadir (4,["process","proceso"]) miDicc;
val miDicc = - : string list Diccionario
-mirar 4 miDicc;
val it = ["process","proceso"] : string list
-mirar 2 miDicc;
uncaught exception Nada

2 Orígenes del ML

• LCF = Logic for Computable Functions (Scott, 1969) – primera implementación: Milner y Weyrauch 1972

– Edinburgh LCF: Milner y otros 1973-1978

• ML= Meta Language del Edinburgh LCF – usado para dirigir procesos de prueba

– las exigencias del LCF sobre ML son:

∗ orden superior

∗ tipos

∗ manejo de excepciones

– tipos básicos:thm, form

    ∗ goal=form*...
    ∗ proof = thm list -> thm
    ∗ tactic=goal -> (goal list)*proof
    ∗ tactical=tactic*tactic->tactic

• Historia del ML – 1978: LCF-ML

– 1981: Cardelli ML

– 1983-85: Definición del Standard ML (SML)

– 1983-86: Implementación del Standard ML (Harper, Mitchell, Mycroft, Scott)

3 Características del SML

• Funcional – las funciones son objetos de primera clase – la llamada de función es por valor

• Interactivo

• acotación estática

• tipos polimórficos

• tipos abstractos

• mecanismo de manejo de excepciones

• módulos (compilación separada con seguridad de tipos)

4 Expresiones básicas, valores y tipos

• En ML, un tipo es una descripción estructural de un valor, por lo

tanto:

– los enteros forman un tipo

– los números primos no

– los pares ordenados son tipos etc.

• Las expresiones en ML denotan valores

• Toda expresión correcta tiene un tipo

• He aquí un ejemplo simple de interacción:

  -3+2;
  val it = 5:int

5 Tipos básicos

Unit :

• valor escrito ()

• se usa como tipo resultante para aquellas expresiones que no tienen tipos interesantes

• se usa también cuando una función no tiene argumentos específicos

Booleanos :

• tipo bool con valores true y false

• expresión condicional if e then e1 else e2 – e debe ser de tipo bool

– los tipos de e1 y e2 deben ser iguales

– la parte else no es opcional

• el tipo de if e then e1 else e2 es el de e1 o e2

• negación not

• conjunción andalso

• disjunción orelse

Enteros :

• tipo int con valores 0,1,~1,2,~2,...

• operadores básicos ~,+,-,*

• división entera y resto div, mod

• operadores relacionales <,<=,>,>=,=,<> con resultados de tipo

  bool

Cadenas (Strings) :

• tipo string con valores rodeados de comillas

  "esto es una cadena"

• \ se usa como carácter de escape

-"esto indica una nueva linea (newline): \n";
val it = "esto indica una nueva
linea (newline): \n" : string

• ^es una operador para concatenar cadenas

-"esto es "^" una cadena";
val it = "esto es  una cadena" : string

• la función size aplicada a una cadena da su longitud:

-size "esto es una cadena";
val it = 18 : int

• normalmente, para manipular una cadena, se transforma en una lista de sus caracteres, se realiza la manipulación, y posteriormente se vuelve a la forma de cadena. Las funciones estándar explode e implode realizan, respectivamente, estas transformaciones. El siguiente ejemplo ,que trata de definir una función que elimine un carácter dado allí donde aparezca dentro de una cadena, ilustra el uso de estas funciones

-local fun borr(c,nil)=nil
         | borr(c,x::xs) = if x = c
                           then borr(c,xs)
                           else x::borr(c,xs)
 in fun borrar(c,s)=implode(borr(c,(explode s)))
end;

Reales :

• tipo real = < entero > . < entero > E < entero >

– la parte exponencial es opcional sólo si se pone el punto decimal

– el punto decimal es opcional sólo si se pone la parte exponencial

– estas reglas son necesarias para distinguir enteros de reales

• ML no soporta ninguna clase de inclusión entre tipos, de modo que los enteros deben ser caracterizados como reales explicitamente.

• ejemplos: 0.7, ~3.32E5, 3E~7

• no-ejemplos: 23, .3, 4.E5, 1E2.0

• operadores básicos ~,+,-,*,/

• operadores relacionales <,<=,>,>=,=,<> con resultados de tipo

  bool

• funciones matemáticas sin, sqrt, exp, etc

• la función real transforma un entero en el correspondiente número real

• la función floor trunca un número real al mayor entero que es menor que él.

6 Tipos compuestos

Tuplas :

• los pares ordenados se escriben (e1,e2), donde e1 y e2 son expresiones

• si e1 tiene tipo t, y e2 tiene tipo s, entonces (e1,e2) tiene tipo

t*s

• las n-uplas ordenadas se escriben (e1,e2,...,en) con tipo ”t1*t2*...*tn” si cada ei tiene tipo ”ti”

• la igualdad entre tuplas es componente a componente:

-(14 mod 3, not false) = (1+1,true);
val it = true : bool
-(true,7)=("abc",());
Error: operator and operand don't agree (tycon mismatch)
        operator domain: (bool * int) * (bool * int)
        operand:         (bool * int) * (string * (unit))
        in expression:
        (true,7) = ("abc",())

Listas :

• el tipo 'a list consiste en sucesiones finitas de valores de tipo

'a

• una 'a list es o vacía, o consiste en un valor de tipo 'a seguido

de una 'a list

• la lista vacía se escribe nil, y tiene tipo 'a list (pronunciado alfa-list), i.e. es un objeto polimórfico

• una lista no vacía se escibe e::l, donde e es una expresión de un cierto tipo 'a y l es una alfa-list.

-nil;
val it = [] : 'a list
-1::2::3::nil;
val it = [1,2,3] : int list

• las listas pueden concatenarse usando el operador @

-[1,2,3]@[4,5,6];
val it = [1,2,3,4,5,6] : int list

• las funciones hd y tl aplicadas a una lista no vacía dan, respectivamente, el primer elemento y la lista restante

        -hd [1,2,3];
        val it = 1 : int
        -tl [1,2,3];
        val it [2,3] : int list

• la igualdad entre listas es elemento a elemento

Records :

• son semejantes a los del PASCAL o C

• un record consiste de un conjunto finito de campos etiquetados, cada uno con un valor de un cierto tipo: {etiq_{1} = e_{1}, etiq_{2} = e_{2}, ..., etiq_{n} = e_{n}} tiene tipo {etiq_{1} : t_{1}, etiq_{2} : t_{2}, ..., etiq_{n} : t_{n}} si cada e_{i} : t_{i}

• el orden de los campos es irrelevante

-{A=3,p=false,otro=[2,3,4]};
val it = {A=3,otro=[2,3,4],p=false} :
{A:int,otro:int list,p:bool}

• el record vacío (o ∅-tupla), {} ≡ () : unit

• una n-tupla (e_{1}, e_{2}, ..., e_{n}) de tipo t_{1} ∗ t_{2} ∗ ... ∗ t_{n}, equivale a {1 = e_{1}, 2 = e_{2}, ..., n = e_{n}} de tipo {1 : t_{1}, 2 : t_{2}, ..., n : t_{n}}

7 Identificadores, acotaciones y declaraciones

• el ML es case-sensitive

• todos los identificadores deben ser declarados antes de ser usados

• los identificadores pueden usarse en varias formas; hay un modo de declaración para cada uno

• empezaremos por considerar identificadores con valor o variables

-val x = 4*5;
val x = 20 : int
-val s = "Abc"^"def";
val s = "Abcdef" : string
-val par = (x,s);
val par = (20,"Abcdef") : int * string

• la frase val x=4*5 se denomina una acotación de valor

• una acotación de valor no es una asignación, i.e una vez que un identificador está acotado a un valor, no hay forma de cambiar ese valor

• acotación estática

• se pueden hacer declaraciones simultaneas (paralelas) usando and

-val x = 17;
val x = 17 : int
-val x = true and y = x;
val x = true: bool
val y = 17: int

• las declaraciones crean o modifican entornos ( o ambientes)

• se pueden hacer composiciones de entornos

-val x = 17
 val x = true and y = x;
val x = 17 : int

val x = true : bool
val y = 17 : int
-x;
val it = true : bool

• declaraciones locales

-local
  val a=10*19
 in
  val u=a*a
 end;
val u = 36100 : int
-1+(let val r=10*19 in r*r end);
val it = 36101 : int

8 SML bajo UNIX

• escriba sml para arrancar el sistema SML

• teclee CTRL-D para salir del sistema

• la función use puede usarse para cargar programas desde archivos

% cat > miprog.sml
local
   val a = 10*198
   in
    val u = a*a
   end;
^D
%sml
Standard ML of New Jersey, Version 0.33, 1 April 1989
val it = () : unit
- use "miprog.sml";
[opening miprog.sml]
val u = 3920400 : int
[closing miprog.sml]
val it = () : unit

9 Valores estructurados

• las tuplas, listas y records son objetos estructurados que se construyen

mediante constructores de tipos

– (1,false,5.6)
– [1,2,3,4,5]
– x::y
– {este=1,aquel=false,otros=([1,2],3)}

• estos objetos son de naturaleza distinta a los valores escalares como los enteros

• ls valores estructurados pueder desectructurarse ( o descomponerse)

mediante destructores de tipos

– first (2,3) = 2 o también #1(2,3)=2
– hd [1,2,3] = 1
– tl [1,2,3] = [2,3]
– #otros {este=1,aquel=false,otros=([1,2],3)} = ([1,2],3)

10 Patrones (Patterns)

• la desectructuración se lleva a cabo mediante el reconocimiento de patrones pattern matching

• los patrones (patterns) se construyen con valores constantes, variables no acotadas, constructores de tipo (como ( , , ), [ , , ], :: , etc.) y comodines (como ... o _{)}

                -val (x,y)=(2,3);
        val x = 2 : int
        val y = 3 : int
                -val [x,y,z]=[1,2,3];
val x = 1 : int
val y = 2 : int
val z = 3 : int

-val x::y = [1,2,3];
val x = 1 : int
val y = [2,3] : int list
\item hay un patr\'on comod\'\i n ({\it wild-card} pattern) ...
que representa varios campos de un registro
                \bv
                -val r = {unos=[], otros=([1,2],3)}
-val{otros=(x,y), ...} = r;
val x = [1,2] : int list
val y = 3 : int

hay un patrón comodín , escrito _

-val[_,y,_]=[1,2,3];
val y = 2 : int

los patrones pueden ser presentados por medio de la partícula as

-val completo as (x::y) = [1,2,3];
val completo = [1,2,3] : int list
val x = 1 : int
val y = [2,3] : int list

la descomposición puede fallar en la etapa de ejecución ( o run time)

-val x::y = [];
uncaught exception Bind

11 Coincidencias (Matches)

• una coincidencia es una sucesión de patrones alternativos

– se usa en la definición de funciones

– se usa en expresiones case

    -val evaldeiro =
       case [1,2,3]
        of x::y => false
         | nil  => true;
    val evaldeiro = false : bool

• el constructor de expresiones if/then/else es justamente una expresión case

-if x > 0 then "Si" else "No";

equivale a

-case x > 0
  of true => "Si"
   | false => "No";

• el compilador advierte de las coincidencias redundantes o incompletas

-val l=[1,2];
val l = [1,2] : int list
- case l of [_,_,y] => y+1;
Warning: match not exhaustive
        _:: _:: y :: nil => ...
uncaught exception Match
-100 + (case (1,2) of (x,_) => x
    | (_,y) => y);
Warning: redundant patterns in match
        (x,_) => ...
  -->   (_,y) => ...
val it = 101 : int

12 Funciones

• la aplicación de funciones se escribe por yustaposición head l

• toda función toma un único argumento

• toda función da un único resultado

• tanto el cuerpo de la función como su argumento son expresiones arbitrarias

• los tipos función se escriben t -> t'

-size;
val it = fn : string -> int

• el tipo función no admite igualdad

• las funciones se definen usando acotaciones funcionales function bindings introducidos por fun

-fun doble x = 2*x;
val doble = fn : int -> int
-fun suma (x,y):int = x+y;
val suma = fn : int * int -> int

• existe recursividad

-fun factorial n =
  if n=0 then 1 else n*factorial(n-1);
val factorial = fn : int -> int
-fun f x = 3*g(x) and g x = 5*x*x;
val f = fn : int -> int
val g = fn : int -> int

• las funciones se definen también usando coincidencias

-fun longitud l =
  if l=nil then 0 else 1+longitud(tl l);
val longitud = fn : ''a list -> int
-fun lonxitude nil=0
    |lonxitude (_::resto)=1+lonxitude resto;
val lonxitude = fn : 'a list -> int

• el reconocimiento de patrones es más claro y evita (algunos) problemas con la igualdad, al tratar con formas y estructuras en lugar de valores

-fun esnula nil = true
    |esnula _= false;
val esnula = fn : 'a list -> bool

• las funciones pueden ser también polimórficas

-fun engade(nil,l)=l
    |engade(h::t,l)=h::engade(t,l);
val engade = fn : 'a list * 'a list -> 'a list

• las declaraciones de funciones pueden ser locales

-fun invertir l =
  let
    fun inv(nil,y)=y
          |inv(h::t,y)=inv(t,h::y)
  in
    inv(l,nil)
  end;
val invertir = fn : 'a list -> 'a list
-fun map(funcion, lista)=
  let fun aplica (x::rest)=(funcion x)::aplica rest
         |aplica nil = nil
  in
    aplica lista
  end;
val map = fn : ('a -> 'b) * 'a list -> 'b list

-map ((let fun incr x = x+1 in incr end),[1,2,3,4]);
val it = [2,3,4,5] : int list

• las funciones pueden ser de orden superior

-fun mapp f =
   let
     fun aplica (x::resto) = f x :: aplica resto
        |aplica nil = nil
   in
     aplica
   end;
val mapp = fn : ('a -> 'b) -> 'a list -> 'b list
-fun incr x = x+1;
val incr = fn : int -> int
-val incrLista = mapp incr;
val incrLista = fn : int list -> int list

• las funciones pueden definirse y aplicarse usando currying y su inverso uncurrying

-fun curry f = fn x => fn y => f(x,y);
val curry = fn : ('a * 'b -> 'c) -> 'a -> 'b -> 'c
-curry suma 3 5;
val it = 8 : int

• λ−expresiones

-mapp (fn x => x+1) [1,2,3,4];
val it = [2,3,4,5] : int list
-val incr = fn x => x+1;
val incr = fn : int -> int

• la última definición es equivalente a

        -fun incr x = x+1;
        val incr = fn : int -> int

• estas funciones de tipo λ pueden definirse también por pattern matching

-fn nil => true
  |_=> false;
val it = fn : 'a list -> bool
-it [4,5];
val it = false : bool

• estas funciones no pueden ser recursivas dado que son anónimas

• en las definiciones

        -val f = fn x => 2*x;
        -val f = fn 0 => 1
                  | n => n*f(n-1);;

se ve que la segunda es correcta porque f ya estaba presente en el entorno asociado a un valor de tipo int -> int, pero

        - f 5;
        val it = 40 : int

sin embargo

        val rec f = fn 0 => 1
                     | n => n*f(n-1);;

es una definición correcta del factorial, aunque f no apareciese en el entorno previo

13 Polimorfismo y sobrecarga

• una función es polimorfa si funciona con varios tipos diferentes de un modo uniforme, por ejemplo la función engade definida antes.

• la sobrecarga es un aspecto sintáctico que permite re-usar identificadores, por ejemplo la suma de enteros y la de reales son distintas funciones que usan el mismo símbolo sobrecargado. No se permite en identificadores definidos pos el usuario.

• si los tipos de los operadores en una operación con símbolo sobrecargado no se especifican, y el compilador no es capaz de deducirlos, en-

tonces se obtiene un mensaje de error Unresolvable overloaded indentifier

• algunas veces la sobrecarga se resuelve por el contexto

-fun fact 0 = 1
            |fact n = n*fact(n-1);
val fact = fn : int -> int
-fun engadarr(x::rest,total)=engadarr(rest,x+total)
    |engadarr(nil,total)=total;
Error: overloaded variable "+" cannot be resolved
-fun engadarr(x::rest,total:int)=engadarr(rest,x+total)
    |engadarr(nil,total)=total;
val engadarr = fn : int list * int -> int

14 Igualdad estructural

• hay un tipo polimorfo especial (eqtype,''a) que es el de aquellos objetos que pueden ser comparados

-fun igual_cabeza(x::_,y::_)=(x=y)
    |igual_cabeza _= false;
val igual_cabeza = fn : ''a list * ''a list -> bool
-igual_cabeza([1,2],[1]);
val it = true : bool
-igual_cabeza([igual_cabeza],[igual_cabeza]);
Error: operator and operand don't agree (equality type required)

15 Igualdad vs. Reconocimiento de patrones

• ”=” compara recursivamente valores para ver su igualdad

• ”=” no compara funciones

• el pattern matching examina la forma y la estructura

• el pattern matching funciona sobre estructuras que contienen funciones

-fun esNil []=true
    |esNil _= false;
val esNil = fn : 'a list -> bool
-fun isNil L = (L = nil);
val isNil = fn : ''a list -> bool

16 Definición de tipos

• el sistema de tipos de ML se puede extender

• hay tres formas de definir tipos

– abreviatura

– declaracion datatype

– declaración de tipo de dato abstracto abstype

• la abreviatura no tiene relevancia semántica

-type entpar=int*int;
type  entpar = int * int
-fun f(par:entpar) =
  let val (x,_)=par in x end;
val f = fn : entpar -> int
-type 'a funcion = 'a -> 'a;
type 'a   funcion = 'a -> 'a
-(fn (x,y) => (y,x)):entpar funcion;
val it = fn : entpar funcion

• usando datatype se pueden definir nuevos tipos compuestos

-datatype Color = ROJO|AZUL|VERDE;
datatype  Color
con AZUL : Color
con ROJO : Color
con VERDE : Color
-[ROJO,ROJO,AZUL];
val it = [ROJO,ROJO,AZUL] : Color list

• los constructores de aridad cero se denominan constantes

• una declaración datatype crea, simultaneamente:

– un nuevo constructor de tipo

– un conjunto de valores constructores para ese tipo

• véanse los siguientes ejemplos:

-type Numero=int;
type  Numero = int
-datatype Numero = int;
datatype  Numero
con int : Numero
-int;
val it = int : Numero

• constructores de tipo usados en pattern matching

– distinción de casos

– desestructuración

• se pueden definir funciones por pattern matching sobre un tipo definido por el usuario, igual que se hace para tipos primitivos

-fun color_favorito ROJO=true
    |color_favorito _= false;
val color_favorito = fn : Color -> bool
-fun igual_color (ROJO,ROJO)=true
    |igual_color (AZUL,AZUL)=true
    |igual_color (VERDE,VERDE)=true
    |igual_color _= false;
val igual_color = fn : Color * Color -> bool

• se pueden usar expresiones case para discriminar entre patrones (o constantes tales como los enteros)

-val micolor = ROJO;
val micolor = ROJO : Color
-case micolor
  of ROJO => 1

    |VERDE => 2
    |AZUL => 3;
val it = 1 : int
-mapp (fn ROJO => "rojo"
        |_=> "no rojo") [ROJO,VERDE,AZUL];
val it = ["rojo","no rojo","no rojo"] : string list

• los constructores no tienen que ser de aridad cero

-datatype Dinero=
   moneda of int
  |papel of int
  |cheque of string*int;
datatype  Dinero
con papel : int -> Dinero
con cheque : string * int -> Dinero
con moneda : int -> Dinero
-fun monto(moneda pesetas) = pesetas
    |monto(papel verdes) = 1000*verdes
    |monto(cheque(_,pelas))=100*pelas;
val monto = fn : Dinero -> int

• dos valores del mismo tipo de dato son iguales si:

– han sido construdidos por el mismo constructor

– y han sido construidos a partir de valores iguales

• Dinero admite igualdad (”=”) porque int y ”string” la admiten

-moneda 5 = moneda (3+2);
val it = true : bool
-val cheque1 = cheque ("Banco Pastor",500)
         and cheque2 = cheque ("Caixa Galicia", 500);
val cheque1 = cheque ("Banco Pastor",500) : Dinero
val cheque2 = cheque ("Caixa Galicia",500) : Dinero

-cheque1=cheque2;
val it = false : bool
-monto cheque1 = monto cheque2;
val it = true : bool;

• tipos polimorfos

-datatype 'a arbol=
    hoja of 'a
   |nodo of 'a arbol * 'a arbol;
datatype 'a   arbol
con hoja : 'a -> 'a arbol
con nodo : 'a arbol * 'a arbol -> 'a arbol

• las funciones que recorren tipos recursivos, deben ser recursivas

-fun frontera (hoja x) = [x]
    |frontera (nodo(i,d))=
     frontera i @ frontera d;
val frontera = fn : 'a arbol -> 'a list
-val abc = nodo (hoja ("a"), nodo (hoja("b"),hoja("c")));
val abc = nodo (hoja "a",nodo (hoja "b",hoja "c"))
: string arbol
-frontera abc;
val it = ["a","b","c"] : string list

• los datatype pueden alterarse por medio de funciones que nos devuelven nuevos objetos

-fun invierte(L as hoja _) = L
    |invierte(nodo(i,d))=
nodo(invierte d, invierte i);
val invierte = fn : 'a arbol -> 'a arbol

• finalmente: un árbol más complejo:

-datatype ('a,'b) arbol =
    Hoja of 'a
   |Nodo of {izquierda:('a,'b) arbol,
            etiqueta: 'b,
            derecha:('a,'b) arbol};
datatype ('a,'b)  arbol
con Hoja : 'a -> ('a,'b) arbol
con Nodo : {derecha:('a,'b) arbol,etiqueta:'b,idquierda:
    ('a,'b) arbol} -> ('a,'b) arbol
-fun hojas(Hoja x)=[x]
    |hojas(Nodo {izquierda=l,derecha=r,...})=
      hojas l @ hojas r
 and nodos(Hoja _)=nil
    |nodos(Nodo {izquierda=l,derecha=r,etiqueta=n})=
nodos l @[n]@ nodos r;
val hojas = fn : ('a,'b) arbol -> 'a list
val nodos = fn : ('a,'b) arbol -> 'b list

• un tipo abstracto es un datatype que oculta su estructura, y con un conjunto de operaciones definidas sobre él (su interface):

-abstype t = .....
  with
   val ... = ...
           fun ... = ...
    ....
         end

• el tipo es abstracto porque la representación está oculta para los usuarios del tipo; solamente el interface está disponible. Por ejmplo:

-abstype Color = MEZCLA of {R:int,V:int,A:int}
 with
   val negro = MEZCLA {R=0,V=0,A=0}
   and rojo = MEZCLA {R=15,V=0,A=0}
   and verde = MEZCLA {R=0,V=15,A=0}
   and azul = MEZCLA {R=0,V=0,A=15}
   fun mezclar(partes, MEZCLA {R=r,V=v,A=a}, partes',

   MEZCLA {R=r',V=v',A=a'})=
     if partes < 0 orelse partes' < 0 then negro
     else
       let val tot=partes+partes'
           val pr = (partes*r+partes'*r') div tot
           and pv = (partes*v+partes'*v') div tot
           and pa = (partes*a+partes'*a') div tot
       in
           MEZCLA {R=pr, V=pv,A=pa}
       end
  end;
type Color
val negro = - : Color
val rojo = - : Color
val verde = - : Color
val azul = - : Color
val mezclar = fn : int * Color * int * Color -> Color

• como se ve, no se declara ningún constructor del tipo como resultado de la daclaración del abstype. O sea, ”MEZCLA” no se exporta

• no se puede usar pattern-matching sobre tipos abstractos

• no está definida la igualdad para tipos abstractos

• si comparamos abstype con datatype tenemos:

– los tipos abstractos son adecuados para objetos que se definen por su comportamiento

– datatypes son apropiados para objetos definidos estructutalmente

17 Ejemplos

• paquete de conjuntos usando listas o usando funciones

-abstype 'a Conj = Conj of 'a list
   with
    val conj_vacio= Conj nil
            fun miembro x (Conj s) =
                exists (fn n => n=x) s
    fun insertar x (conjunto as Conj s) =
if miembro x conjunto then conjunto
else Conj(x::s)
  end;
type'a   Conj
val conj_vacio = - : 'a Conj
val miembro = fn : ''a -> ''a Conj -> bool
val insertar = fn : ''a -> ''a Conj -> ''a Conj
-abstype 'a Conjunto = CONJ of 'a -> bool
 with
   val vacio = CONJ(fn _=> false)
   fun pertenece x (CONJ f) = f x
   fun introducir x (conj as CONJ f) =
       if pertenece x conj then conj
       else CONJ(fn y => x=y orelse f y)
   end;
type'a   Conjunto
val vacio = - : 'a Conjunto
val pertenece = fn : 'a -> 'a Conjunto -> bool
val introducir = fn : ''a -> ''a Conjunto -> ''a Conjunto

18 Excepciones

• el mecanismo de excepciones sirve para que una función pueda interrumpirse de forma segura siempre que no sea capaz de devolver un valor

-hd nil;
uncaught exception Hd

• exception y raise permiten definir funciones que manipulen situaciones no deseadas a través del mecanismo de excepción

        -exception Hd;
        -fun hd nil = raise Hd
           | hd x::_= x;
-fun hd2 l = hd l
     handle Hd => ~1;
val hd2 = fn : int list -> int
-hd2 [1,2,3];
val it = 1 : int
-hd2 nil;
val it = ~1 : int

• si durante la evaluación de una función se produce una excepción, el resultado de la evaluación es la propia excepción, salvo en el caso de que se utilicen los manipuladores

• las excepciones son objetos

• las excepciones tienen tipos

• las excepciones pueden incluir valores que provean de más información

al handler
-Hd;
val it = - : exn
-let exception Foo of int in Foo end;
val it = fn : int -> exn

(son semejantes a los constructores de los tipos de datos)

• los manipuladores de excepciones (handlers) son coincidencias (matches), y contienen patrones

-exception MalaLista of int list;
exception MalaLista
- ... handle MalaLista nil => 0
            |MalaLista [_,x,_] => x
           |MalaLista (h::t) => h;

• un mismo manipulador puede manejar varias excepciones

-exception Rango of bool
 fun f x =
   if x<0 then raise Rango true
   else if x > 100 then raise Rango false
   else x+1;
exception Rango
val f = fn : int -> int
-f(hd nil div 0)
handle
   Rango true => 0
  |Rango false => 101
  |Hd => 50
  |exn => raise exn;
val it = 50 : int

19 Aspectos imperativos

• ML soporta referencias y asignaciones

– las referencias son una forma segura de apuntar a objetos

– las asignaciones proveen de un modo de cambiar el objeto al que apunta una referencia

• el tipo t ref es el tipo de las referencias a valores de tipo t • la función ref:'a -> 'a ref crea (y devuelve) una referencia a un

objeto

• la función !:'a ref -> 'a da el contenido de la referencia

-!(ref 3);
val it = 3 : int

• la función := :'a ref *'a -> unit es la operación de asignación

-val r1 = ref 0
 val r2 = r1 and numero = ! r1;
val r1 = ref 0 : int ref
val r2 = ref 0 : int ref
val numero = 0 : int
-r1:=3;
val it = () : unit
-(! r1, ! r2, numero);
val it = (3,3,0) : int * int * int

20 I/O

• ML posee tipos para canales de entrada y salida (input, output streams)

• existen procedimientos (funciones con efectos colaterales o side-effects)

%cat > miarchivo
12345678
abcdfg
este es un archivo de prueba
^D
%sml
Standard ML of New Jersey, Version 0.33, 1 April 1989
val it = () : unit
-val s1 = open_in "miarchivo";
val s1 = - : instream
-val linea1 = input_line s1;
val linea1 = "12345678\n" : string
-val linea2 = input_line s1;
val linea2 = "abcdfg\n" : string
-output std_out (linea1^linea2);
12345678
abcdfg
val it = () : unit
-end_of_stream s1;
val it = false : bool

• std_in y std_out son la entrada y salida estandar y tienen tipos

instream y outstream respectivamente

• open_in y open_out permiten cambiar la entrada y salida creando nuevos canales

• end_of_stream nos dice si se ha alcanzado el final del archivo

• por ejemplo, para crear un comando que copie un archivo en otro:

-^Z
Stopped
%cat > arch1
12345678
adadasdasd
^D
%cat > arch2
^D
%%1
sml
-val inf=open_in "arch1";
val inf = - : instream
-val ouf=open_out "arch2";
val ouf = - : outstream
-fun copia ar1 ar2 = if end_of_stream ar1
     then ()
     else (output ar2 (input ar1 1);
       copia ar1 ar2);
val copia = fn : instream -> outstream -> unit
-copia inf ouf;
val it () : unit

• con el comando close_out se consigue

– cerrar el canal de salida al que se le aplica

– copiar fisicamente los datos que están en ese canal en el archivo del sistema al que se refiere

-close_out ouf;
val it = () : unit
-^Z
Stopped
%more arch2
23454678
adadasdasd

%%1 sml -

38