Download Especificación y Desarrollo de Sistemas de Software
Document related concepts
no text concepts found
Transcript
Enunciados y materiales de prácticas de la asignatura “Especificación y Desarrollo de Sistemas de Software” Grado en Matemáticas y Grado en Ingeniería Informática Ana Romero Ibáñez Laureano Lambán Pardo Universidad de La Rioja Curso 2011/2012 Todo este material está disponible en la página web: www.unirioja.es/cu/anromero/edss.html Índice Práctica 1 – Dominó………………………………………………….. ..3 Práctica 2 – Juego de la guerra……………………………….……….5 Práctica 3 – Resolución de sudokus…………………………………..7 Materiales para la práctica 3: Colecciones en Java………………..13 Práctica 4 – Expresiones booleanas…………………………………18 Especificación y Desarrollo de Sistemas de Software. Curso: 2011-2012. Práctica 1ª: “Partida de dominó” La realización de esta práctica está previsto que ocupe tres sesiones. Las partes de la asignatura con las que está directamente relacionada son las siguientes: especificación de tipos de datos y diseño orientado a objetos. En particular, el diseño planteado va a necesitar del mecanismo “clase abstracta”. Se pretenden diseñar en esta práctica las clases a las que pertenecen los diferentes objetos que intervienen en una "partida de dominó", aunque en una versión muy simplificada, evitando lo relativo a estrategias de jugador. Las clases que se proponen son: Ficha, Mesa, Jugador y Partida. Ficha. Como guía, una ficha debe proporcionar métodos para: girar, informar de su valor (suma), mostrarse, informar de si contiene un determinado número, informar de si se trata de una doble, etc. Mesa. Una mesa de dominó consta de dos “montones”, uno con las fichas que no están en posesión de ningún jugador y el otro con las fichas ya colocadas (organizadas correctamente). Tendrá que proporcionar métodos para colocar fichas en ambos montones, extraer aleatoriamente fichas del montón de las que no han entrado en juego, informar de si están o no vacíos esos montones, informar de los extremos de las colocadas, mostrar las fichas colocadas, etc. Jugador. Como vamos a permitir jugadores humanos y jugadores gestionados por la máquina, la diseñaremos como clase abstracta. Un jugador posee un conjunto de fichas. Los métodos abstractos van a limitarse a la forma en la que un jugador elige la ficha a jugar (recibiendo como información un objeto de la clase mesa), y la forma en la que se gestiona la acción recibir ficha (cada tipo de jugador organiza sus fichas como quiere). El resto de métodos no son abstractos, proporcionando funcionalidades básicas como: saber si puede o no jugar (según el estado de la mesa), suma de las fichas, número de fichas, saber si ha finalizado, etc. Partida. En una partida intervienen una mesa y un conjunto de jugadores. Otro aspecto importante es el turno (qué jugador debe jugar). Al crear la partida se debe conocer el número de jugadores de cada tipo que van a intervenir en ella. Entre otros, debe proporcionar métodos para: iniciar partida (repartir fichas y generar el turno inicial), realizar una jugada, informar de la posible finalización, dar el ganador, dar la clasificación, etc. Especificación y Desarrollo de Sistemas de Software. Curso: 2011-2012. Práctica 2ª: “Juego de la guerra”. La realización de esta práctica está previsto que ocupe tres sesiones. Las partes de la asignatura con las que está directamente relacionada son las siguientes: especificación de tipos de datos, orientación a objetos y tipos de datos, diseño basado en objetos. Además se hará uso de la definición de clases genéricas en Java. Parte 1. Pilas de enteros. Escribid una especificación del tipo de datos "pila de enteros" y dad una clase Java que implemente el tipo correspondiente. Parte 2. Colas genéricas. Escribid una especificación del tipo de datos "cola" y dad una clase Java que represente “colas genéricas”, es decir, colas cuyos elementos puedan ser objetos de cualquier clase. Parte 3. Un juego de cartas sencillo. Se propone simular el juego de cartas conocido como "La Guerra". A continuación, se explica brevemente la dinámica del juego. Tras repartir todas las cartas entre los jugadores, una jugada consiste en que cada jugador (que no haya sido previamente eliminado) arroja su primera carta sobre la mesa. El ganador de la jugada es el que ha echado la carta más alta (en caso de empate, los jugadores que han arrojado las cartas de mayor valor siguen jugando hasta deshacer el empate). El ganador de la jugada recoge todas las cartas que se han arrojado en ella y las incorpora como últimas cartas de su mazo. Un jugador queda eliminado cuando, siendo su turno para echar carta, su mazo está vacío. El final del juego se produce cuando queda un único jugador sin eliminar. Para programar el juego siguiendo una "metodología orientada a objetos", se recomienda: Desarrollad una clase en Java cuyos objetos representen a "jugadores de guerra". Pensad en cómo gestiona un jugador la recogida y la eliminación (echar sobre la mesa) de cartas. Desarrollad una clase Java cuyos objetos representen a "mesas del juego de la guerra". Desarrollad una pequeña clase cuyos objetos representen a las "barajas españolas". Desarrollad una clase Java cuyas instancias sean “partidas del juego de la guerra”. En ella colaborarán las clases anteriores. Parte 4. Un paso de abstracción. Sólo se pretende que se reflexione en las posibilidades que nos ofrece el diseño orientado objetos. Por ejemplo, parece que se puede generalizar la idea de juego de cartas (mediante clases abstractas), quedando por concretar la forma en la que se desarrolla una jugada, el valor de las cartas en cada juego, la forma en que gestionan sus cartas los jugadores, etc. Así, sería factible realizar software bastante general, que luego se concretara para juegos particulares. Se anima a intentar hacer pequeñas generalizaciones: barajas abstractas, jugadores abstractos, etc. Como asunto relacionado, se recomienda reflexionar sobre cómo diseñar algunos solitarios sencillos, y se anima a que se pase de la reflexión al papel (y del papel al ordenador). Especificación y Desarrollo de Sistemas Software. Curso: 2011-2012. Práctica 3ª: “Resolución de sudokus” La realización de esta práctica está previsto que ocupe tres sesiones. Las partes de la asignatura con las que está directamente relacionada son las siguientes: especificación de tipos de datos y diseño orientado a objetos. En particular, se van a manejar algunos conceptos fundamentales en programación orientada a objetos, como herencia, reescritura de métodos, tipos parametrizados, etc. Además se presentarán algunas clases del marco de colecciones de Java. En la práctica se pretenden desarrollar las clases que luego permitirían construir un entorno sencillo para gestionar y resolver sudokus. La idea se basa en la relación que existe entre encontrar la solución de un sudoku y resolver el problema de colorear el grafo asociado a ese sudoku. El grafo asociado a un sudoku tiene tantos vértices como casillas tiene el sudoku y tiene una arista por cada pareja de casillas relacionadas (están en la misma fila, en la misma columna o en la misma “región”). Por otro lado, colorear un grafo usando un número k de colores diferentes, consiste en asignar un color a cada vértice (un valor entre 1 y k), de forma que dos vértices conectados por una arista tengan siempre color diferente. Así, resolver un sudoku de “tamaño n” (con n2 casillas) equivale a colorear con n colores su grafo asociado. La forma habitual de trabajar con sudokus supone que algunas casillas tienen un valor inicial fijo, luego su grafo asociado ya tiene algunos colores asignados de partida. A continuación pasamos a describir las clases que van a intervenir en el problema: Grafo, GrafoConColores, Sudoku y SudokuConSolucion. Grafo. Nos vamos a limitar a grafos cuyos vértices son números enteros. Las operaciones habituales sobre un grafo son, entre otras: añadir un nuevo vértice, añadir una nueva arista, eliminar un vértice (*) , eliminar una arista (*) , informar del número de vértices, decidir si dos vértices son adyacentes, decidir si un vértice está o no en un grafo, dar la lista de todos los vértices del grafo y dar la lista de vértices adyacentes a un vértice dado. Aunque hay otras representaciones posibles para el estado de un grafo, recomendamos representar un grafo mediante una tabla (las claves son los vértices y el valor asociado a una clave es la lista de sus vértices adyacentes). GrafoConColores. Un grafo con colores es un grafo con información adicional (algunos de sus vértices tienen asignado un color, un entero en nuestro caso). Además de las operaciones de grafo, se incorporan métodos para: dar color a un vértice, eliminar el color de un vértice, informar del color de un vértice, borrar todos los colores, dar la lista de todos los vértices que tienen un color asignado, decidir si un color es válido para un vértice y colorear un grafo. El “método colorear” termina de colorear un “grafo con colores” con un número de colores pasado como argumento, siempre que ello sea posible. Existen métodos refinados para colorear, pero el que se entrega en la documentación resulta sencillo (aunque ineficiente). Ello va a limitar el tamaño de los sudokus que vamos a poder tratar (tamaño 9, el habitual en pasatiempos). Sudoku: Por simplificar, nos limitaremos a sudokus cuadrados. Los representaremos como una matriz cuadrada de enteros, donde las entradas serán 0 (para casillas vacías) o el valor del entero que esté en la casilla (comprendido entre 1 y el tamaño). Respecto de los operadores, se debe disponer de métodos para: informar del tamaño, añadir un número a una casilla (indicando la fila y la columna), eliminar un número, dar el valor inicial de una casilla, volver al estado inicial y decidir si es correcto añadir un determinado valor en una casilla (*). SudokuConSolucion. La visión externa de un objeto de esta clase será la misma que la que nos da un sudoku, pero añadiendo dos nuevos métodos. El primero observa si es posible resolver el sudoku desde el estado actual y el segundo lo resuelve (si es posible). Para ello, además del estado del sudoku, dispondrá de un atributo privado de la clase GrafoConColores. Además de los dos operadores nuevos, será necesario reescribir los métodos heredados de sudoku, modificando adecuadamente el grafo con colores asociado al sudoku. Se incluye en la documentación un método privado para construir el grafo con colores asociado a un sudoku. (*) Los operadores marcados no hace falta que se implementen (no se van a usar ). ALGORITMO PARA EL COLOREADO DE GRAFOS public void colorear(int numColores){ List<Integer> listaVertices; // lista auxiliar en la que colocaré todos los vértices /* Para poder aplicar el algoritmo de coloración de un grafo necesito tener los vértices almacenados en orden. En primer lugar colocaré los vértices que tienen ya un color asignado (este color no podrá modificarse). A continuación colocaré en la lista el resto de vértices, a los que el algoritmo de coloración irá asignando diferentes colores hasta dar con una combinación correcta. */ List<Integer> listaVerticesColoreados=this.listaVerticesColoreados( ); List<Integer> listaVerticesNoColoreados= this.listaVertices( ); //todos listaVerticesNoColoreados.removeAll(listaVerticesColoreados); //quito los //coloreados // vuelco los vértices en el nuevo vector, en el orden correcto listaVertices=new Vector<Integer>( ); listaVertices.addAll(listaVerticesColoreados); listaVertices.addAll(listaVerticesNoColoreados); int k=listaVerticesColoreados.size( ); boolean b=coloreoConRetroceso(listaVertices, k, numColores); if (b== false) { // no se ha podido colorear el grafo // vuelvo a la situación inicial for (int i = 0; i < listaVerticesNoColoreados.size( ); i++) { tablaColores.remove(listaVerticesNoColoreados.get(i)); } } } private boolean aceptable(List<Integer> listaVertices, int color, int posicion){ /* devuelve true si al vértice que ocupa la posición k en listaVertices puedo asignarle el color k de modo que no haya ningún vértice en las posiciones anteriores que sea adyacente y que tenga el mismo color asignado. */ boolean acept=true; for (int i=0; i<posicion && acept; i++){ if (this.contieneArista(listaVertices.get(i), listaVertices.get(posicion)) && getColor(listaVertices.get(i))== color) acept=false; } return acept; } private boolean coloreoConRetroceso(List<Integer> listaVertices, int k, int numColores){ /* Supongo que a los vértices situados en las posiciones 0..k-1 de listaVertices ya les he asignado color. Busco un color para el vértice en la posición k que sea compatible con los anteriores. */ if (k==listaVertices.size( )) return true; else { for (int c=1; c<=numColores; c++){ if (this.aceptable(listaVertices,c, k)) { tablaColores.put(listaVertices.get(k), c); boolean b=coloreoConRetroceso(listaVertices,k + 1, numColores); if (b) return b; } } } // he recorrido todas las combinaciones y ninguna es válida, devuelvo falso. return false; } ALGORITMO PARA CONSTRUIR EL GRAFO INICIAL ASOCIADO A UN SUDOKU private void construirGrafoInicial( ){ int numFilas=this.getNumFilas( ); int numVertices=numFilas*numFilas; for (int v=1; v<=numVertices; v++) gr.anadirVertice(v); //gr es el atributo de Sudoku que contiene el grafo con colores //Añado aristas para todas las parejas de vértices que están en la misma // fila for (int i = 0; i < numFilas ; i++) { for (int j = 0; j < numFilas; j++) { for (int k = j + 1; k < numFilas ; k++) { int v1=numFilas*i + j+1; int v2=numFilas*i + k+1; gr.anadirArista(v1,v2); } } } //Añado aristas para todas las parejas de vértices que están en la misma // columna for (int j = 0; j < numFilas; j++) { for (int i = 0; i < numFilas ; i++) { for (int k = i + 1; k < numFilas ; k++) { int v1=numFilas*i + j+1; int v2=numFilas*k + j+1; gr.anadirArista(v1,v2); } } } //Añado aristas para todas las parejas de vértices que están en la misma // región int n = (int)Math.sqrt(numFilas); for (int i = 0; i < n ; i++) { for (int j = 0; j < n; j++) { int i0 = i * n; int j0 = j * n; // (i0,j0) es la esquina superior izquierda de la región for (int i1 = i0; i1 < i0 + n; i1++) { for (int j1 = j0; j1 < j0 + n; j1++) { for (int i2 =i0; i2<i0+n; i2++){ for (int j2 = j0; j2 < j0 + n; j2++) { int v1 = numFilas * i1 + j1 + 1; int v2 = numFilas * i2 + j2 + 1; if (v1 != v2) gr.anadirArista(v1, v2); } } } } } } // Por último añado los colores a los vértices correspondientes a los // valores iniciales del sudoku for (int i=0; i<numFilas; i++){ for (int j=0; j<numFilas; j++){ if (this.valorInicial(i,j)!=0) gr.setColor(i*numFilas+j+1,this.valorInicial(i,j)); } } } Especificación y Desarrollo de Sistemas de Software. Curso 2011/2012. Prácticas 3 y 4. COLECCIONES EN JAVA El marco de colecciones (Collection Framework) de Java contiene una serie de interfaces y clases que permiten almacenar y organizar objetos de una forma cómoda y sencilla y con un acceso eficiente. La mayor parte de estas clases las encontramos en el paquete java.util, y entre otras cosas nos van a permitir trabajar con conjuntos, listas y aplicaciones. • • • • El marco de colecciones de Java está formado por: interfaces que nos dan los métodos para todas las operaciones comunes; implementaciones de los interfaces (clases); algoritmos para realizar determinadas operaciones habituales sobre colecciones, como ordenaciones o búsquedas; iteradores que nos permiten recorrer las colecciones. Todos los interfaces y clases del marco de colecciones son genéricas, aunque con objeto de mantener la compatibilidad con versiones anteriores de Java estos interfaces y clases genéricos conviven con versiones no genéricas de los mismos. Si no indicamos el parámetro de tipo, se supondrá que estamos trabajando con colecciones/aplicaciones sobre la clase Object, con lo que tendremos que hacer las coerciones necesarias. LOS INTERFACES BÁSICOS A continuación mostramos un diagrama UML con los interfaces básicos del marco de colecciones y las relaciones entre ellos. Se distinguen dos jerarquías diferentes, los interfaces que heredan de Collection<T> y los que lo hacen de Map<K,V> (que llamaremos aplicaciones). Veamos las características y métodos principales de algunos de ellos: Collection<T> Es el interfaz más general; define las operaciones que implementan todas las clases que representan colecciones de objetos. Podemos encontrarnos una gran variedad de colecciones de objetos, con características distintas: unas colecciones serán ordenadas y otras no, unas permitirán elementos repetidos y otras no, etc; pero todas ellas van a disponer de una serie de operaciones comunes, que son las que quedan recogidas en el interfaz genérico Collection<T>. 1 Especificación y Desarrollo de Sistemas de Software. Curso 2011/2012. Prácticas 3 y 4. Sus métodos más importantes son (mirar JavaDoc): int size(): devuelve el número de objetos almacenados en la colección boolean isEmpty(): dice si la colección está vacía boolean add(T elem): añade un elemento a la colección boolean addAll(Collection<? extends T> c): añade un grupo de objetos (otra colección) void clear(): vacía la colección boolean contains(Object elem): chequea si un elemento se encuentra dentro de la colección boolean remove(Object elem): quita un elemento de la colección Iterator<T> iterator(): devuelve un java.util.Iterator que permite recorrer todos los elementos de la colección T[] toArray(T[]a): devuelve un array con los elementos de la colección List<T> Sirve como interfaz para clases que representen listas de datos o vectores cuya longitud es variable. Sus elementos son accesibles por medio de un índice, y tiene los métodos de colección y algunos más, entre ellos: void add(int i, T elem): añade un elemento en una posición determinada T get(int i): devuelve el elemento que ocupa una posición determinada T remove(int i): elimina un elemento que ocupa una posición determinada int indexOf(Object elem): devuelve el índice de la primera aparición del elemento en la lista (o –1 si no está) T set(int i, T elem): cambia el elemento que ocupa una posición determinada por otro elemento Al añadir un objeto con el método add(T elem) de Collection<T>, el elemento se añade al final. Al eliminar un objeto con remove(Object elem), todos los demás se desplazan para no dejar huecos. Los objetos List<T> más habituales son Vector<T> y ArrayList<T>. Sus métodos y características principales se pueden encontrar en la documentación de Java. Set<T> Sirve como interfaz para clases que representen conjuntos de datos donde no puede haber elementos repetidos, y cuyos elementos no se almacenan necesariamente siguiendo un orden particular. Por tanto sus elementos no son accesibles por un índice. Tiene los mismos métodos que Collection<T> (no añade ningún método nuevo). Las clases Set<T> más habituales son HashSet<T> y TreeSet<T> (ver JavaDoc). 2 Especificación y Desarrollo de Sistemas de Software. Curso 2011/2012. Prácticas 3 y 4. Map<K,V> Define las operaciones que implementarán las clases que representan aplicaciones de claves a valores, es decir, representa lo que se denomina tablas de datos. Un mapa se compone de un conjunto de “entradas”, compuestas a su vez de: • una clave, que sirve para recuperar el elemento; • y un valor. Una aplicación no puede contener claves duplicadas, y cada clave se puede emparejar con, a lo sumo, un valor. Los métodos más importantes de este interfaz son (mirar JavaDoc): int size(): devuelve el número de objetos almacenados boolean isEmpty(): dice si el Map está vacío void clear(): vacía la estructura V put(K clave, V valor): almacena un objeto en el mapa con una clave determinada. Si la clave ya está almacenada se sustituye el objeto asociado por el nuevo V get(K clave): permite recuperar un elemento a partir de su clave V remove(Object clave): elimina un objeto de clave determinada boolean containsKey(Object clave): devuelve true si hay algún objeto almacenado con esa clave boolean containsValue(Object valor): devuelve true si el Map contiene ese objeto Set<K> keySet(): devuelve un conjunto con el conjunto de claves del Map Collection<V> values(): devuelve el conjunto de valores almacenados en el Map Una aplicación no es una colección; por eso Map<K,V> no hereda de Sin embargo, a partir de un Map podremos obtener colecciones correspondientes al conjunto de claves, al conjunto de valores y al conjunto de pares <clave, valor>. Los Maps se implementan como tablas Hash; las clases Map<K,V> más habituales son HashMap<K,V> y Hashtable<K,V>. Collection<T>. Ejemplo: java.util.Map<String,Persona> agenda= new java.util.Hashtable<String,Persona>(); Persona p=new Persona(“Pepe”), agenda.put(“16444444E”,p); agenda.put(“55555555L”, new Persona(“Luis”); System.out.println(“Hay alguien con DNI 55555555L?” + agenda.containsKey(“55555555L”)); //true Persona pr=agenda.get(“16444444E”); //Debe ser Pepe System.out.println(“¿Es Pepe?” + pr.equals(p)); // true System.out.println(“El tamaño de la agenda es ”+agenda.size()); 3 Especificación y Desarrollo de Sistemas de Software. Curso 2011/2012. Prácticas 3 y 4. IMPLEMENTACIONES DE LOS INTERFACES BÁSICOS El paquete java.util proporciona implementaciones de los interfaces genéricos Collection, List, Set y Map (entre otros). En la siguiente figura se muestran algunas de estas clases y las relaciones entre ellas. Recordar que no hay relación entre Collection<T> y Map<K,V>. Además, podemos observar que no hay una implementación directa de la interfaz Collection<T>. En nuestro caso será especialmente conveniente conocer el funcionamiento de las clases Vector<T>, HashSet<T> y Hashtable<K,V> (ver JavaDoc). En particular, nos resultarán útiles los métodos de Hashtable<K,V> que devuelven enumeraciones de las claves y los valores de una tabla: Enumeration<V> elements() Enumeration<K> keys() ITERADORES Un iterador es un objeto que nos permite obtener uno a uno todos los elementos de una colección, independientemente de cómo estén organizados. En concreto, los métodos del interfaz Iterator<T> nos proporcionan un modo sencillo para recorrer colecciones como Set en las que no tenemos acceso por índice. Además, vamos a poder gestionar la eliminación de objetos. IMPORTANTE: los iteradores se utilizan para recorrer colecciones en las que no tenemos acceso por índice (como Set), pero NO DEBEN usarse para recorrer objetos de las interfaces o clases List, Vector, ArrayList, etc, en los que podemos acceder a cada una de sus componentes por medio del método get(int i). Los métodos del interfaz Iterator<T> son: boolean hasNext(): devuelve true si el iterador tiene más elementos T next(): devuelve el siguiente elemento de la iteración. Cada vez que se invoca a este método se avanza en el recorrido de la colección void remove(): elimina el último elemento devuelto por la iteración 4 Especificación y Desarrollo de Sistemas de Software. Curso 2011/2012. Prácticas 3 y 4. Ejemplo: Collection<String> c= ....; Iterator<String> iter=c.iterator(); while(iter.hasNext()) System.out.print(iter.next() + “ “); Otro interfaz similar que nos va a permitir recorrer colecciones es el interfaz cuyos métodos son: Enumeration<T>, boolean hasMoreElements() T nextElement() ESTRUCTURA FOR PARA RECORRER COLECCIONES DIRECTAMENTE A partir de Java 1.5 hay una manera simplificada de recorrer una Collection mediante un nuevo uso del keyword for (siempre que no se quiera modificar la propia colección): Collection<String> col= ....; for (String s: col) System.out.print(s); Internamente este código no hace otra cosa que obtener el iterador, pero queda mucho más elegante y legible de esta manera. 5 Especificación y Desarrollo de Sistemas de Software. Curso: 2011-2012. Práctica 4ª: “Expresiones booleanas”. Esta práctica ocupará tres sesiones. En ella se van a manejar algunos conceptos fundamentales en programación orientada a objetos. Por ejemplo, usaremos clases abstractas, herencia, tipos parametrizados, polimorfismo y recursividad. En la implementación aparecerán además algunas de las utilidades de Java (Map, Set, etc.). El problema concreto a resolver es la construcción y evaluación de expresiones booleanas. Por simplificar, nos vamos a limitar a expresiones construidas a partir de expresiones atómicas (constantes booleanas y variables booleanas) mediante los operadores: negación (not), conjunción (and) y disyunción (or). El objetivo final es conseguir objetos con funcionalidad suficiente para realizar evaluaciones de expresiones. El estado de dichos “entornos de evaluación” estará básicamente determinado por una expresión y un contexto (lo que en Lógica se denomina valoración de variables). Se propone diseñar las siguientes clases: Expresión. Una expresión dispondrá de métodos para informar de su número de variables, de si contiene o no una determinada variable, de si es o no es evaluable en un contexto, del conjunto de variables que aparecen en ella, etc. Habrá además métodos abstractos (que se implementarán en las distintas subclases de expresión): evaluar en un contexto, sustituir una variable por un valor booleano y renombrar una variable por otra. Las subclases a las que no referimos son: expresión atómica, expresión unaria (negación de una expresión) y expresión binaria (a su vez con dos subclases: ExpAnd y ExpOr). En expresión atómica se pretende englobar tanto las constantes booleanas (true y false), como las expresiones que son variables booleanas (b, encontrado, esFiesta, etc.). Contexto. Un contexto es una familia de parejas formadas por un nombre de variable y un valor booleano. Debe ofrecer métodos para formar y modificar el contexto (añadir y eliminar valoraciones de variables), informar del valor de una variable, decidir si dos contextos son coherentes, negar un contexto, etc. Evaluador. Debe permitir evaluar una expresión en un contexto. Las operaciones permitirán realizar sustituciones en la expresión, modificarla, modificar el contexto, evaluar, decidir si se puede evaluar, etc.