• No se han encontrado resultados

8 INTRODUCCIÓN AL OBJECTIVE CAML

N/A
N/A
Protected

Academic year: 2021

Share "8 INTRODUCCIÓN AL OBJECTIVE CAML"

Copied!
13
0
0

Texto completo

(1)

8

I N T R O D U CC I Ó N A L O B J E C T I V E C A M L

Objective Caml (Ocaml) es un lenguaje de programación funcional tipificado estáti-camente, aunque los tipos pueden inferirse en tiempo de ejecución; y con un meca-nismos de recolección de basura. Esto significa que no es necesario especificar los tipos de todas las expresiones en nuestros programas, aunque al ser un lenguaje es-trictamente tipificado, Ocaml contribuye a estar más alertas de los tipos de datos que usamos. Si bien estamos ante un lenguaje de propósito general, Ocaml resulta es-pecialmente adecuado para dominios que requiere seguridad y verificación. En este capítulo introduciremos los conceptos básicos de este lenguaje de programación.

�.� ��������

El primer contacto de un usuario con OCaml es interactivo. Cuando se ejecuta OCaml en una computadora, se entra en un bucle llamado topevel, donde la computadora espera a que el usuario le de una expresión a evaluar. Cuando esto sucede el sistema evalúa la expresión e imprime el resultado para el usuario. Este tipo de bucle se conoce también como ciclo read-eval-print y aparece también en lenguajes como Lisp y Prolog.

Para iniciar Ocaml en mi Mac con OS X, hago lo siguiente: 1 justine:~ aguerra$ ocaml

2 Objective Caml version 3.12.0 3

4 #

dondejustine$es el prompt de mi sistema y#es el prompt de OCaml, indicándome que el sistema está en espera.

De aquí en adelante, las frases que inicien con # son entradas de OCaml. Es de

esperar que ustedes puedan reproducir las sesiones de cada sesión, puesto que han sido verificadas en OCaml.

Las líneas que empiezan con>representan mensajes de error de OCaml. Cualquier

otro inicio de liínea, es probable que represente las respuestas de OCaml o una entra-da no válientra-da.

�.� ����������

Usemos OCaml para procesar una expresión aritmética: 1 # 1+2 ;;

2 - : int = 3

(2)

La expresión 1+2 fue teclada, seguida de;; y la tecla enter o return. Cuando se encuentra con la cadena;;OCaml entra en modo de verificación de tipos (o, siendo precisos, síntesis de tipos) e imprime el tipo inferido para la expresión (int en el ejemplo, para indicar que se tata de entero). Luego, el sistema compila el código para la expresión, ejecuta este código e imprime el resultado (3).

El proceso de evaluación de una expresión OCaml puede verse como una serie de transformaciones sobre la expresión, que termina cuando no es posible aplicar más transformaciones. Estas transformaciones deben mantener la semántica de la expre-sión. Por ejemplo, la expresión 1 + 2 tiene como semántica al objeto matemático 3, por lo que el resultado 3 debe tener la misma semántica.

Como se puede observar, la evaluación de las expresiones en OCaml es más com-plicado que en Lisp. Las fases del proceso de evaluación en OCaml incluyen:

1. Parsing (verificación sintáctica); 2. Sintésis de tipo;

3. Compilación; 4. Carga; 5. Ejecución;

6. Impresión de resultados.

Consideremos otro ejemplo: la aplicación de la función sucesor a la expresión 3 + 1. La expresion(function x ->x+1)debe leerse como la función que, dada x, regresa x+ 1. La yuxtaposición de esta expresión a 3 + 1 forma la aplicación de la función. Ejemplo 10 Aplicación de una función sucesor a la expresión (3 + 1):

1 # (function x -> x+1)(3+1) ;; 2 - : int = 5

Existen diversas manera de reducir esta expresión hasta obtener 5. Aquí mostramos dos: 1 (function x -> x+1) (3+1) 2 (function x -> x+1) 4 3 4 + 1 4 5 y: 1 (function x -> x+1) (3+1) 2 (3+1) + 1 3 4 + 1 4 5

(3)

�.� ����� 105

�.� �����

El concepto de tipo, así como su verificación y sintésis, son centrales para la progra-mación funcional. Ellos proveen indicadores sobre lo correcto de un programa.

El universo de OCaml está particionado en tipos. Un tipo representa una colección de valores. Por ejemplo int es el tipo de los números enteros y float es el tipo de los números de punto flotante. Los valores de verdad pertecen al tipo bool. Las cadenas de caracteres pertenecen al tipo string. Los tipos se dividen en dos clases:

1. Tipos básicos (int,float,bool,string, . . . ).

2. Tipos compuestos, como los tipos funcionales. Por ejemplo, el tipo de la función sucesor, (function x ->x+1), que tiene como parámetro forman un entero y regresa un entero es int ->int. El caso de la función “mayor que” que recibe un par de enteros y regresa un entero, tiene el tipoint * int ->int.

Una vez que el usuario envia una expresión a OCaml, éste lleva a cabo un análisis sintáctico de la expresión, que consiste en verificar si la expresión pertenece al lenguaje o no.

Ejemplo 11 Error sintáctico (paréntesis mal balanceados): 1 # (function x -> x+1 (2+3) ;;

2 Syntax error: ’)’ expected, the highlighted ’(’ might be unmatched

El segundo análisis que OCaml lleva a cabo es de tipos. El sistema trata de asignar un tipo a cada subexpresión y de analizar el tipo de la expresión completa. Si el aná-lisis de tipos falla, por ejemplo, si el sistema no es capaz de asignar un tipo coherente a la expresión, se produce un mensaje de error de tipo y OCaml entra en modo de espera por una nueva expresión a evaluar.

Ejemplo 12 Error de tipo (con + no se puede sumar un booleano, sólo enteros): 1 # (function x -> x+1)(2+true) ;;

2 Characters 22-26:

3 (function x -> x+1)(2+true) ;;

4 ^^^^

5 This expression has type bool but is here used with type int

El rechazo de expresiones mal tipificadas se conoce como tipificación fuerte. El compilador de un lenguaje débilmente tipificado, como C, nos daría solo un mensaje de advertencia (warning) y continuaría su trabajo con el riesgo de llegar a un mensaje de bus error o de illegal instruction en tiempo de ejecución.

Una vez que el tipo de la expresión ha sido determinado con éxito, el proceso de evaluación inicia. Este proceso incluye las fases de compilación, carga y ejecución.

La tipificación fuerte obliga a escribir programas claros y bien estructurados. Más aún, aumenta la seguridad e incrementa la velocidad en el desarrollo de un programa, puesto que muchos typos y errores conceptuales son detectados y señalados por el

(4)

análisis de tipos. Finalmente, los programas bien tipificados no necesitan de test diná-micos de tipo (la función suma no necesita verificar todo el tiempo si sus argumentos son números), por lo que la verificación estática de tipos contribuye a obtener código más eficiente.

�.� ���������

El tipo de valores más importantes en la programación funcional son los valores fun-cionales. En matemáticas, una función f de tipo A ! B es una regla de corresponden-cia que asocorresponden-cia a cada elemento de tipo A un único elemento de tipo B.

Si x denota un elemento de A, entonces escribiremos f(x) para la aplicación de f a x. Los paréntesis son sólo para agrupar subexpresiones por lo que también podemos escribir (f(x)) o ((f(x))) o incluso fx. El valor de fx es el único elemento de B asociado a x por la regla de correspondencia para f.

La notación f(x) es la usada normalmente en matemáticas para denotar la aplica-ción funcional. Es importante no confunidr la funaplica-ción con su aplicaaplica-ción. Decimos la función f con parámetro formal x, para expresar que f ha sido definida como f : x ! e, o en OCaml, que el cuerpo de f es una expresión de la forma(function x ->. . .. Da-do que OCaml es un lenguaje funcional, las funciones son valores que se comportan como los demás valores del lenguaje. En partícular las funciones pueden pasarse co-mo argumento a otras funciones y/o ser regresadas coco-mo valores por una función. Ejemplo 13 Una función que toma como parámetro una función de los enteros a los enteros (f) y un entero; regresando un entero que resulta de la aplicación de f al segundo argumento incrementado en uno menos uno:

1 # function f -> (function x -> (f(x+1) - 1)) ;; 2 - : (int -> int) -> int -> int = <fun>

Observen que en los ejemplos anteriores, como es común en la programación fun-ciona, no es necesario nombrar a las funciones. No confundan a f con el nombre la función que estamos definiendo, f es su primer argumento (lo mismo para x).

�.� ������������

Aunque nombrar valores no sea necesario, a veces (y sobre todo en los paradigmas no funcionales) es importante dar nombre a los valores. Hemos visto ya algunos nombres de valores, los llamamos parámetros formales. En la expresión(function x ->x+1), el nombre x es un parámetro formal. Su nombre en cierta forma irrelevante, cambiarlo no cambia el significado de la expresión. Podríamos haber escrito la función como

(function y ->y+1).

Si ahora aplicamos esta función a la expresión 1 + 2, evaluaremos la expresión y + 1 cuando y tiene el valor 1 + 2.

(5)

�.� ������������ 107

1 # let y=1+2 in y+1 ;; 2 - : int = 4

Esta expresión forma parte del lenguaje OCaml y el constructorletes ampliamen-te utilizado. Esampliamen-te constructor introduce ligaduras locales (local bindings) de valores a identificadores. Son locales porque el alcance de y se restringe a la expresión y + 1. El identificador y no conserva su ligadora (la que sea) tras la evaluación delet . . . in

. . ., así que en este caso: 1 # y ;;

2 Characters 0-1:

3 y ;;

4 ^

5 Unbound value y

Las ligaduras locales usandoletpermiten compartir evaluaciones (que posiblemen-te utilizan muchos recursos computacionales). Cuando evaluamoslet x= e1 in e2,

e1 es evaluado sólo una vez. Todas las ocurrencias de x en e2 accesan el valor de x

que ha sido computado sólo una vez.

Ejemplo 15 Compartiendo evaluaciones conlet:

1 let big = suma_factores_primos 35461234 2 in big+(2+big)-(4*(big+1)) ;;

será menos costoso que:

1 (suma_factores_primos 35461234) + 2 (2 + (suma_factores_primos 35461234)) -3 (4 * ((suma_factores_primos 35461234) - 1)) ;;

El constructorlettiene también una forma global para declaraciones en el toplevel. Ejemplo 16 Declaraciones en el toplevel usandolet:

1 # let sucesor = function y -> y+1 ;; 2 val sucesor : int -> int = <fun> 3 # let cuadrado = function x -> x*x ;; 4 val cuadrado : int -> int = <fun>

Cuando un valor es declarado en el toplevel, lo podemos seguir usando en el resto de la sesión:

Ejemplo 17 Aplicaciones usandosucesorycuadrado: 1 # (cuadrado (sucesor 3)) ;;

2 - : int = 16 3 # cuadrado ;;

(6)

Al declarar valores funcionales existen varias alternativas sintácticas. Las siguientes declaraciones decuadrado son equivalentes a la anterior:

1 # let cuadrado x = x*x ;;

2 val cuadrado : int -> int = <fun> 3 # let cuadrado (x) = x*x ;; 4 val cuadrado : int -> int = <fun>

Ejemplo 18 Declaraciones alternativas de suma. Observen que el tipo de ambas definiciones es el mismo, y que ambas permite aplicaciones parciales de la suma de dos números.

1 # let suma1 = function x -> function y -> x+y ;; 2 val suma1 : int -> int -> int = <fun>

3 # let suma2 x y = x+y ;;

4 val suma2 : int -> int -> int = <fun> 5 # suma2 3;;

6 - : int -> int = <fun> 7 # suma1 3;;

8 - : int -> int = <fun>

�.� ������������ ���������

Una aplicación parcial de una función es la aplicación de una función a algunos pero no todos sus argumentos. Consideren la siguiente función:

1 # let f x = function y -> 2*x*y ;; 2 val f : int -> int -> int = <fun>

La expresión f(3) denota la función que cuando se le da un argumento y regresa el valor 2 ⇤ 3 ⇤ y. La aplicación f(x) se conoce como aplicación parcial, porque f espera dos argumentos sucesivos y es sólo aplicada a uno. El valor de f(x) sigue siendo una función.

Ejemplo 19 Aplicación parcial de f a 3: 1 # f 3 ;;

2 - : int -> int = <fun>

Ejemplo 20 Definición desucesorusando aplicaciones parciales: 1 # let suma x y = x + y ;;

2 val suma : int -> int -> int = <fun> 3 # let sucesor = suma 1 ;;

4 val sucesor : int -> int = <fun> 5 # sucesor(sucesor 3);;

(7)

�.� ����� ������� 109

�.� ����� �������

�.�.� Enteros

Caml provee un tipo para los enteros (int) que están comprendidos en el rango -230. . . 230- 1. En una arquitectura de 32 bits, la precisión de los enteros es de 31 bits.

En una arquitectura de 64 bits, su precisión es de 63 bits. Las operaciones (funciones) predefinidas para los enteros son incluyen:

+ Suma

- Resta

* Multiplicación / División

mod Modulo

Ejemplo 21 Algunos ejemplos de expresiones aritméticas con enteros:

# 3 * 4 + 2 ;; - : int = 14 # 3 * (4 + 2) ;; - : int = 18 # 3 - 7 - 2 ;; - : int = -6

Como se puede observar, existen reglas de precedencia que hacen que*ligue más

fuerte que +. En caso de duda sobre la precedencia de un operador, es mejor usar paréntesis.

�.�.� Reales

Los números de punto flotante proveen reales en Caml. La sintáxis de los reales in-cluye ya sea un punto décimal, o un exponente (base 10) denotado pore. Se requiere que al menos un dígito preceda al punto décimal. Existen funciones de coerción para ir de los enteros a los reales y viceversa:int_of_floatyfloat_of_int.

Los operadores para números enteros no funcionan con números reales. Para ellos tenemos+.,-.,*.y/.entre otros.

Ejemplo 22 Algunos ejemplos de expresiones con reales:

# 2e7 ;; - : float = 20000000. # 2e-3 ;; - : float = 0.002 # float_of_int 10 ;; - : float = 10. # 3.1415926 *. 17.2 ;; - : float = 54.03539272 # int_of_float(3.1415926 *. 17.2) ;; - : int = 54

(8)

�.�.� Caracteres

Los caracteres toman su valor del conjunto de símbolos ASCII. Se delimitan por após-trofes. La especificación numérica es en d´cimal (el código 120 corresponde a xy no aP). Los operadores incluyen conversión a código décimal y viceversa, conversión a mayúsculas y minúsculas, etc.

Ejemplo 23 Ejemplos de expresiones con caracteres:

# ’a’ ;; - : char = ’a’ # ’\120’ ;; - : char = ’x’ # Char.code ’a’ ;; - : int = 97 # Char.chr 97 ;; - : char = ’a’ # Char.chr 97 ;; - : char = ’a’ # Char.uppercase ’a’ ;; - : char = ’A’ # Char.lowercase ’A’ ;; - : char = ’a’ # Char.lowercase(Char.uppercase(Char.chr 97)) ;; - : char = ’a’ �.�.� Cadenas

Las cadenas de caracteres en Caml son un tipo predefinido, a diferencia de otros lenguajes (C) donde son arreglos de caracteres. La sintáxis para las cadenas usa ”

como delimitadores. El operadorˆrealiza la concatenación. La funciónString.length

computa el tamaño de una cadena. El operador posfijo .[i] accesa al elemento i-’esimo de la cadena. # "Hola" ;; - : string = "Hola" # "Hola" ^ "Mundo" ;; - : string = "HolaMundo" # String.length "Hola" ;; - : int = 4

# String.length "Hola" ^ "Mundo" ;; Characters 0-20:

String.length "Hola" ^ "Mundo" ;; ^^^^^^^^^^^^^^^^^^^^

This expression has type int but is here used with type string # String.length("Hola" ^ "Mundo") ;;

- : int = 9 # "hola".[2] ;; - : char = ’l’

(9)

�.� ����� ������� 111

�.�.� Valores de verdad

El tipoboolprovee dos valores de verdad:trueyfalse. La negación lógica se compu-ta con la funciónnot. Las relaciones binarias toman argumentos del mismo tipo. Estas incluyen=,>,<,<>,>=,<=. Los operadores booleanos también están predefinidos como

&& y|| en sus versiones short circuit. Esto es, solo evaluan los operandos necesarios para obtener su valor de verdad.

Ejemplo 24 # not true ;; - : bool = false

# not false ;; - : bool = true

# 2 < 3 || 3 > 2 ;; - : bool = true

En el caso general, es imposible verificar la igualdad entre dos valores funciona-les. de ahí que la igualdad se detenga con un error en tiempo de ejecución cuando encuentra este tipo de valores:

# (fun x -> x) = (fun x -> x ) ;;

Exception: Invalid_argument "equal: functional value".

¿Cual es el tipo entonces del operador=? Este toma dos expresiones del mismo tipo y regresa un valor booleano. Su tipo es:

-: ’a -> ’a -> bool = <fun>

Es decir, el tipo de=es polimórfico, es decir, puede tomar diversas formas. Por ejem-plo, si compara dos enteros, su forma esint ->int ->bool, pero si se comparan dos cadentas, su forma es str ->str ->bool. El tipo de la igualdad se expresa entonces usando variables de tipo, escritas como’a,’b, etc. Cualquier tipo puede substituir a una variable de tipo para producir tipos instanciados. Por ejemplo substituitintpor

’aen el ejemplo anterior, nos daint ->int ->bool.

Además de los operadores lógicos y las comparaciones, los valores de verdad nos permiten trabajar con condicionales. Estas son expresiones de la formaifetestthene1 elsee2, donde etestes una expresión de tipobool; e1y e2comparten el mismo tipo. Ejemplo 25 La negación usando condicionales:

# let neg a = if a then false else true ;; val neg : bool -> bool = <fun>

# neg true ;; - : bool = false # neg false ;; - : bool = true # neg (1=2) ;; - : bool = true

(10)

�.�.� Tuplas

Es posible combinar valores en tuplas (pares, tripletes, etc.). El constructor de valores para las tuplas es la coma (,). Generalmente usamos paréntesis alrededor de la tupla por legibilidad, aunque esto no es estrictamente necesario.

Ejemplo 26 Ejemplos de tuplas

# 1,2 ;;

- : int * int = (1, 2) # 1,2,3,4 ;;

- : int * int * int * int = (1, 2, 3, 4) # let p = (1+2, 2<3) ;;

val p : int * bool = (3, true)

El identificador infijo*es el constructor de tipos tupla. Por ejemplo, t1*t2

corres-ponde al concepto matemático de producto cartesiano t1⇥ t2.

Para extraer componentes de una tupla se utilizan las llamadas funciones de proyec-ción. Para los pares tenemos:

# fst (1,2) ;; - : int = 1

# snd (1,2) ;; - : int = 2

Estas proyecciones tienen tipo polimórfico:

# fst (1,2) ;; - : int = 1

# snd (1,2) ;; - : int = 2

Estas proyecciones predefinidas en Ocaml, tienen tipo polimórfico:

# fst ;;

- : ’a * ’b -> ’a = <fun> # snd ;;

- : ’a * ’b -> ’b = <fun>

Por supuesto, uno puede escribir sus propias definiciones de tales funciones:

# let primero (x,y) = x ;;

val primero : ’a * ’b -> ’a = <fun> # let segundo (x,y) = y ;;

val segundo : ’a * ’b -> ’b = <fun> # primero (1,2) ;;

- : int = 1

# segundo (1,2) ;; - : int = 2

Decimos que estas funciones son genéricas porque pueden trabajar de manera uni-forme con muchos tipos de argumentos (siempre y cuando estos sean pares). A veces se confunden los término genérico y polimórfico, por ejemplo, se dice comunmente que tal valor es polimórfico, cuando en realidad se quiere decir genérico.

(11)

�.� ����� ������� 113

�.�.� Patrones y concordancia entre ellos

Los patrones y la concordancia entre patrones (pattern matching) juegan un papel im-portante en la programación funcional. Estos conceptos se utilizan en todo programa real y están fuertemente relacionados con el concepto de tipo.

un patrón representa la forma de un valor. Los patrones pueden verse como moldes con “cavidades” de distintas formas. Un parámetro formal es un patrón. Cuando un valor es comparado contra un patrón, el patrón actúa como un filtro.

Consideren la siguiente definición:

# let f = function true -> false ;; Characters 8-30:

Warning: this pattern-matching is not exhaustive. Here is an example of a value that is not matched: false

let f = function true -> false ;; ^^^^^^^^^^^^^^^^^^^^^^ val f : bool -> bool = <fun>

El compilador nos advierte que la conocordancia de patrones puede fallar, pues no hemos considerado el casofalse.

# f (3>2) ;; - : bool = false

# f (2>3) ;;

Exception: Match_failure ("", 1, 8).

He aquí la definición correcta de esta funciónf, usando concordancia de patrones:

# let negar = function true -> false | false -> true ;;

val negar : bool -> bool = <fun>

La definición denegartiene ahora dos casos separados por el carácter|. Los casos son considerados por Caml en orden descendente. Una definición alternativa:

# let negar = function true -> false | x -> true ;;

val negar : bool -> bool = <fun>

Ahora la función considera cualquier valor booleano x en su segundo caso. En realidad, solo false es aplicable en ese segundo caso, pués true es filtrado en el

primer caso. Debido a que en nuestro ejemplo no utilizamos el argumentox, podemos definir la funciónnegarcomo sigue:

# let negar = function true -> false | _ -> true ;;

val negar : bool -> bool = <fun>

donde _ actua como un parámetro formal que no introduce ligadura alguna. Esto

(12)

Ejemplo 27 Implicación binaria usando concordancia de patrones

# let implicacion = function (true,true) -> true | (true,false) -> false

| (false,true) -> true | (false,false) -> true ;;

val implicacion : bool * bool -> bool = <fun>

¿Puede proponerse una definición alternativa deimplicacion?

# let implicacion = function (true,false) -> false | _ -> true ;;

val implicacion : bool * bool -> bool = <fun>

La concordancia de patrones puede aplicarse a cualquier tipo: Ejemplo 28 Concordancia de patrones usando diversos tipos:

# let cerop = function 0 -> true | _ -> false ;; val cerop : int -> bool = <fun>

# let sip = function "yes" -> true | "oui" -> true

| "ja" -> true | "si" -> true | _ -> false ;;

val sip : string -> bool = <fun>

�.� ��������� �����������

El constructor de tipos->está predefinido en Caml. Exploremos algunos aspectos de las funciones y los tipos funcionales.

�.�.� Composición funcional

La composición funcional es fácil de definir en Caml y es una función polimórfica:

# let componer f g = function x -> f(g(x)) ;;

val componer : (’a -> ’b) -> (’c -> ’a) -> ’c -> ’b = <fun>

Las restricciones para usarcomponerprovienen de su tipo: • El codominio degy el dominio defdebe ser el mismo (’a). • xdebe pertenecer al domino deg(’c).

• compose f g xpertenecera la codominio def(’b). Veamos como funcionacomponer:

(13)

�.� ��������� ����������� 115

# let succ x = x+1 ;;

val succ : int -> int = <fun> # componer succ String.length ;; - : string -> int = <fun>

# (componer succ String.length) "cinco" ;; - : int = 6

�.�.� Currying

Podemos definirsumade dos maneras posibles:

# let suma = function (x,y) -> x + y ;; val suma : int * int -> int = <fun>

# let suma = function x -> function y -> x + y ;; val suma : int -> int -> int = <fun>

Estas dos expresiones difieren sólo en la forma en que toman sus argumentos. La primer definición desumatoma un argumento que pertenece al producto cartesiano int⇥ int. La segunda definición toma como argumento un entero y regresa una fun-ción. Esta última definición se dice ser la versión curry (curryfied, en honor de Haskell Curry). Las transformaciones curry pueden escribirse en Caml usando funciones de orden superior:

# let curry f = function x -> (function y -> f(x,y)) ;; val curry : (’a * ’b -> ’c) -> ’a -> ’b -> ’c = <fun> # let uncurry f = function (x,y) -> f x y ;;

val uncurry : (’a -> ’b -> ’c) -> ’a * ’b -> ’c = <fun>

Podemos asi verificar los tipos de funciones en formato curry y no curry:

# suma ;;

- : int -> int -> int = <fun> # uncurry suma ;;

- : int * int -> int = <fun> # curry (uncurry suma) ;; - : int -> int -> int = <fun>

Referencias

Documento similar

Volviendo a la jurisprudencia del Tribunal de Justicia, conviene recor- dar que, con el tiempo, este órgano se vio en la necesidad de determinar si los actos de los Estados

Se dice que la Administración no está obligada a seguir sus pre- cedentes y puede, por tanto, conculcar legítimamente los principios de igualdad, seguridad jurídica y buena fe,

La Normativa de evaluación del rendimiento académico de los estudiantes y de revisión de calificaciones de la Universidad de Santiago de Compostela, aprobada por el Pleno or-

1. LAS GARANTÍAS CONSTITUCIONALES.—2. C) La reforma constitucional de 1994. D) Las tres etapas del amparo argentino. F) Las vías previas al amparo. H) La acción es judicial en

La oferta existente en el Departamento de Santa Ana es variada, en esta zona pueden encontrarse diferentes hoteles, que pueden cubrir las necesidades básicas de un viajero que

37 El TPI, en los fundamentos jurídicos del 149 al 154 de la sentencia «Virgia- micina», examinó las dos actividades complementarias que integran la evaluación de riesgos:

La heterogeneidad clínica de esta patolo- gía hizo que se considerasen a numerosos genes de pro- teínas de la matriz extracelular (elastina, fibronectina, genes de los colágenos de

En cuarto lugar, se establecen unos medios para la actuación de re- fuerzo de la Cohesión (conducción y coordinación de las políticas eco- nómicas nacionales, políticas y acciones