Manual SQL/DB2

 

 

 

 

 

 

         SSSSSSSSS

         SSS

         SSSSSSS

           SSSSSSS

               SSS

         SSSSSSSSS

 

                 QQQQQQQ

                QQ     QQ

                QQ     QQ

                QQ     QQ

                QQ    QQQ

                 QQQQQQ  QQ

 

                       LL

                       LL

                       LL

                       LL

                       LLLLLLLL

                        LLLLLLLLL

 

 

                                    //

                                   //

                                  //

                                 //

                                //

                               //

 

 

                                    DDDDDDD

                                    DDDDDDDD

                                    DD     DD

                                    DD     DD

                                    DD     DD

                                    DDDDDDDD

 

                                           BBBBBBB

                                           BBBBBBBB

                                           BB     BB

                                           BBBBBBBB

                                           BB     BB

                                           BBBBBBBB

 

                                                  222222222

                                                        222

                                                    2222222

                                                  2222222

                                                  222

         Begoña  Ramos  Barrios                   222222222

 

 

 

 

 

 


                                                             I

 

 

                         INDICE

                        ________

 

 

   CONCEPTOS.

 

       Conceptos …………………………………………  1

       Tablas ……………………………………………  1

       Tipos de datos que puede contener una columna de una tabla  2

       Indices …………………………………………..  2

 

 

   MECANISMOS DE SEGURIDAD DE LOS DATOS.

 

       Vistas ……………………………………………  2

       Racf (Resource Access Control Facility) ………………  3

       Passwords  de ficheros Vsam …………………………  3

       Autorizacion ………………………………………  3

 

 

   MECANISMOS DE INTEGRIDAD DE LOS DATOS.

 

       Concurrencia ………………………………………  3

       Recuperacion de datos ………………………………  3

         - Unidad de recuperacion ………………………….  3

       Ayudas a la programacion ……………………………  4

         - Spufi …………………………………………  4

         - Paneles para preparacion de programas …………….  4

         - Paneles para mantenimiento de planes ……………..  4

         - DCLGEN (Declarations Generator) ………………….  4

         - Online Help ……………………………………  5

 

 

   ESTRUCTURA DE UN PROGRAMA QUE OPERA EN UN ENTORNO CICS.

 

       Estructura de un programa que opera en un entorno cics …  5

       Preparacion del programa para la ejecucion ……………  5

         - Traslacion …………………………………….  5

         - Precompilacion …………………………………  6

         - Compilacion y link-edicion ………………………  6

         - Binding ……………………………………….  6

 

 

   INSTRUCCIONES S.Q.L.

 

       Instrucciones basicas ………………………………  7

       Clausula Where …………………………………….  8

       Select into ……………………………………….  8

       Update ……………………………………………  9

       Delete ……………………………………………  9

       Insert …………………………………………… 10

       Insercion a la vez de varias filas de una tabla en otra .. 10

       Funciones del cursor ………………………………. 11

       Declare cursor ……………………………………. 11

       Open …………………………………………….. 12

       Fetch ……………………………………………. 12

 

 

 

 

 


                                                            II

 

 

       Whenever not found ………………………………… 13

       Update …………………………………………… 13

       Delete …………………………………………… 14

       Close ……………………………………………. 14

       Opciones de la instruccion select …………………… 15

         - Operaciones aritmeticas ………………………… 15

         - Funciones Built-in …………………………….. 16

         - Avg ………………………………………….. 16

         - Max ………………………………………….. 16

         - Min ………………………………………….. 16

         - Sum ………………………………………….. 16

         - Count ………………………………………… 16

         - Clausulas Group by, Having y Order by ……………. 17

         - Group by ……………………………………… 17

         - Having ……………………………………….. 18

         - Order by ……………………………………… 18

         - Union ………………………………………… 19

       Seleccion de datos de varias tablas en una fila (join) … 20

       Opciones de las condiciones de busqueda ……………… 21

         - Between … And ……………………………….. 21

         - In …………………………………………… 21

         - Like …………………………………………. 21

         - Is null ………………………………………. 22

 

 

   VISTAS.

 

       Concepto …………………………………………. 22

       Proceso de una vista ………………………………. 23

       Subquery …………………………………………. 24

       Formas de incluir una subquery en una clausula where o

       having …………………………………………… 24

         - Inmediatamente despues de un operador de comparacion.. 24

         - Despues de un operador de comparacion seguido de all

         o any ………………………………………….. 25

         - Despues de In, para indicar que el valor de la expre-

         sion debe estar entre los valores devueltos por la sub-

         query ………………………………………….. 25

         - Despues de Exists, verificacion de que puede ser en-

         contrada una fila con la condicion de la subquery …… 26

       Subquery correlativa ………………………………. 26

       Otros ejemplos ……………………………………. 26

         - Con una SELECT ………………………………… 26

         - Con una UPDATE ………………………………… 27

         - Con una DELETE ………………………………… 27

 

 

   SPUFI.

 

       Crear y probar instrucciones SQL ……………………. 27

       Creacion de una tabla ……………………………… 28

       Creacion de una vista ……………………………… 28

       Creacion de una tabla ……………………………… 28

       Crear un nombre alternativo o sinonimo para una tabla o

       vista y dar de baja ese sinonimo ……………………. 28

 

 

 

 

 


                                                           III

 

 

   PANELES.

 

       Panel Principal …………………………………… 28

         - Especificar un fichero de entrada ……………….. 28

         - Especificar un fichero de salida ………………… 28

         - Especificar las opciones de proceso ……………… 29

       Panel de opciones por defecto ………………………. 29

         - Especificar otras opciones de proceso ……………. 29

         - Definir caracteristicas del fichero de salida …….. 29

         - Especificar defectos para el formato de salida ……. 30

       Panel Commit/Rollback ……………………………… 30

       Restricciones …………………………………….. 30

 

 

   CODIFICACION DE INSTRUCCIONES SQL EN UN PROGRAMA COBOL.

 

       Definir un area de comunicacion llamada SQLCA ………… 31

       Describir cada tabla o vista a la que acceda el programa . 31

       Codificar instrucciones SQL ………………………… 32

         - Variables y estructuras host ……………………. 32

         - Variables indicador ……………………………. 33

       Manejo de codigos de retorno de errores : LA SQLCA ……. 33

       Manejo de condiciones excepcionales: Instruccion Whenever. 35

         - Tres condiciones que se pueden especificar ……….. 36

         - Dos acciones que se pueden especificar …………… 36

       Sugerencias para la codificacion de instrucciones SQL …. 36

         - Facilitar el uso de indices por el DB2 …………… 36

         - Facilitar la seleccion de datos de dos o mas tablas .. 36

       Notas sobre DB2 / SQL ……………………………… 37

 

 

   APENDICE I.

 

       Ejemplos de instrucciones sql en cobol II ……………. 37

         - Distinct ……………………………………… 37

         - Count ………………………………………… 38

         - Sum ………………………………………….. 38

         - Union ………………………………………… 39

         - Between (not between) ………………………….. 40

         - In (not in) …………………………………… 40

         - Like (not like) ……………………………….. 40

         - Group by ……………………………………… 40

         - Max ………………………………………….. 41

         - Min ………………………………………….. 41

         - Having ……………………………………….. 41

 

 

   RECOMENDACIONES PARA EL DISEÑO DE APLICACIONES EN DB2.

 

       Administracion de datos ……………………………. 42

       Administracion de base de datos …………………….. 42

       Administracion del sistema DB2 ……………………… 43

       Programador del sistema DB2 ………………………… 43

       Operacion del sistema DB2 ………………………….. 44

       Analisis de aplicaciones …………………………… 44

       Programacion de aplicaciones ……………………….. 45

 

 

 

 

 


                                                            IV

 

 

       Soporte de centro de informacion ……………………. 45

       Representantes de los usuarios ……………………… 46

       Macro actividades a desarrollar en un proyecto ……….. 46

         - Planificacion …………………………………. 46

         - Hw/sw instal. …………………………………. 46

         - Diseño y desarrollo aplicacion ………………….. 46

         - Operacion y recuperacion ……………………….. 47

         - Gestion Rdto. …………………………………. 47

 

 

   PLANIFICACION.

 

       Gestion del proyecto ………………………………. 47

       Seleccion de la aplicacion piloto …………………… 47

       Definicion de estandares …………………………… 48

       Identificar los recursos a proteger …………………. 48

 

 

   DISEÑO Y DESARROLLO DE LA APLICACION.

 

       Establecer el entorno de desarrollo de la aplicacion ….. 48

       Analisis de aplicaciones …………………………… 49

       Diseño de Base de Datos ……………………………. 49

       Desarrollo y prueba de programas ……………………. 49

       Paso de desarrollo a produccion …………………….. 50

 

 

   DISEÑO DE BASES DE DATOS.

 

       Introduccion ……………………………………… 51

 

 

   DISEÑO LOGICO DE BASES DE DATOS.

 

       Analisis de datos …………………………………. 51

       Normalizacion …………………………………….. 52

       Integridad referencial …………………………….. 53

 

 

   INTEGRIDAD DE DATOS.

 

       Un adecuado diseño de base de datos …………………. 53

         - Analisis total de los datos …………………….. 53

         - Normalizacion de los datos ……………………… 53

       Un adecuado diseño de modulos, codificacion y pruebas …. 54

 

 

   RENDIMIENTO Y CONCURRENCIA EN BASE DE DATOS.

 

       Concurrencia ……………………………………… 55

       Trabajo realizado por el DB2 para el SQL …………….. 55

       Diseño fisico de bases de datos …………………….. 56

       Seleccion de indices ………………………………. 60

 

 

 


                                                                    1

 

 

      CONCEPTOS

      _________

 

 

      El DB2 es un sistema de manejo de bases de datos basado en un modelo

      relacional de datos. Rueda bajo sistema operativo MVS/SP como un sub-

      sistema de este:

 

 

 

               +————-+

               | TERMINAL    |

               | CICS/OS/VS  |

               +————-+

                     |

           +———|—————————————————+

           |         v                                                   |

           | +————+                              MVS/SD          |

           | | CICS/OS/VS |————-+             Sistema Operativo  |

           | +————+             |                                |

           |         |                  |                                |

           |         v                  v                                |

           | +————+          +——-+           +———–+   |

           | |   IMS/VS   |          |  DB2  |<——-   |  TSO  y   |   |

           | |     DB     |          +——-+           |  BATCH    |   |

           | +————+             |                +———–+   |

           +———|——————|——————————–+

                     |                  |

                     v                  v

               +———-+      +———–+

            +———-+  |  +———-+    |

            | Bases de |  |  | Bases de |    |

            | Datos    |–+  | Datos    |—-+

            | IMS/VS   |     |   DB2    |

            | DL/I     |     |          |

            +———-+     +———-+

 

 

 

      TABLAS

      ______

 

 

      Los datos en las bases de datos DB2 estan disponibles para un programa

      de aplicacion como un conjunto de tablas. Las tablas son estructuras de

      datos de dos dimensiones, compuestas de FILAS y COLUMNAS.

 

      Un nombre de tabla esta formado por un identificador de la persona que

      creo la tabla, y de un nombre descriptivo de esta, separados por punto.

 

 

 

 

 

 

 

 

 

 

 

 


                                                                 2

 

 

      TIPOS DE DATOS QUE PUEDE CONTENER UNA COLUMNA DE UNA TABLA

      __________________________________________________________

 

 

       . Cadenas de caracteres EBCDIC:

             CHAR        - Cadenas de longitud fija

             VARCHAR     - Cadenas de longitud variable

 

 

       . Datos numericos:

             SMALLINT    - Enteros binarios de media palabra

             INTEGER     - Enteros binarios de una palabra

             DECIMAL     - Numeros decimales

             FLOAT       - Numeros de coma flotante

 

 

       . Cadenas de caracteres graficos:

             GRAFHIC     - DBCS (Double Byte Character Data) de longitud fija

             VARGRAPHIC  - DBCS de longitud variable

 

 

 

      INDICES

      _______

 

 

      El DB2 proporciona dos mecanismos de acceso a tablas, acceso secuencial

      y acceso directo mediante indices. El indice debe ser unico.

 

      Estan basados en los valores de los datos de una o mas columnas, y son

      mantenidos automaticamente por el DB2 cuando se produce un cambio en

      los datos contenidos en la tabla. Un programa no se refiere a los indi-

      ces, sino que el DB2 selecciona el indice para acceder a los datos que

      el programa requiere.

 

      El DB2 nunca usa como indice una columna que va a ser actualizada, o

      una columna que esta siendo comparada con otra de la misma fila.

 

 

 

      MECANISMOS DE SEGURIDAD DE LOS DATOS

      ____________________________________

 

 

      VISTAS

      ______

 

 

      Permiten a un usuario acceder solo a ciertas filas o columnas de una

      tabla.

 

 

 

 


                                                                 3

 

 

      RACF (Resource Access Control Facility)

      _______________________________________

 

 

      Proporciona control de acceso al DB2 por usuarios o aplicaciones no au-

      torizados, a los ficheros vsam que contienen las bases de datos, a un

      dispositivo de acceso directo, etc.

 

 

 

      PASSWORDS DE FICHEROS VSAM

      __________________________

 

 

      Se usan en ficheros no protegidos con RACF.

 

 

 

      AUTORIZACION

      ____________

 

 

      Mediante instrucciones GRANT y REVOKE de identificadores de usuarios.

 

 

 

      MECANISMOS DE INTEGRIDAD DE LOS DATOS

      _____________________________________

 

 

      CONCURRENCIA

      ____________

 

 

      El DB2 permite que mas de un programa acceda a los mismos datos al

      mismo tiempo.

 

      La concurrencia es controlada mediante LOCKS. Consisten en asociar un

      recurso DB2 con un programa, de forma que esta asociacion afecta a como

      pueden acceder otros programas al mismo recurso. Existen varios tipos:

      S (Share), U (Update), X (Exclusive), etc.

 

      Ningun programa podra acceder a los datos que hayan sido cambiados por

      otro programa, pero que todavia no esten en la base.

 

 

 

      RECUPERACION DE DATOS

      _____________________

 

 

      Unidad de recuperacion

      ______________________

 

 

      Proceso que ocurre entre dos puntos de COMMIT. Es una secuencia de

      acciones que necesita ser completada para que cualquiera de las accio-

 

 

 

 


                                                                 4

 

 

      nes individuales que la forman pueda considerarse como acabada.

 

      Ejemplo:

 

      Una transaccion que transfiere fondos de una cuenta A a otra B, debera

      primero sustraer la cantidad a transferir de la cuenta A, y luego sumar

      a la cuenta B. Cuando se terminen ambas acciones, y no antes, los datos

      de ambas cuentas seran consistentes.

 

      Una unidad de recuperacion se señala como completa mediante un COMMIT o

      Synchronization (Sync) Point, de las siguientes formas:

 

        1. Implicitamente al final de una transaccion: EXEC CICS RETURN

 

        2. Explicitamente en puntos de la transaccion: EXEC CICS SYNCPOINT

 

        3. Implicitamente                            : EXEC DLI TERM

 

        4. Implicitamente en un programa BATCH-DLI   : EXEC DLI CHKP

 

      Si ocurre una accion y otra no, se dice que la base de datos ha perdido

      su integridad o consistencia. Para lograr que los datos recuperen el

      valor que tenian antes de comenzar la unidad de recuperacion, se usa la

      opcion ROLLBACK para el comando SYNCPOINT.

 

 

 

      AYUDAS A LA PROGRAMACION

      ________________________

 

 

      Spufi:

      _____  Permite usar el SQL sin tener que codificar un programa completo

             mediante instrucciones que pueden ser ejecutadas y mostradas en

             el terminal.

 

 

      Paneles para preparacion de programas:

      _____________________________________  Permiten la precompilacion, com-

             pilacion, link-edicion y binding de un programa. Solo se necesi-

             ta especificar el nombre del fichero que contiene las instruccio-

             nes fuente.

 

 

      Paneles para mantenimiento de planes:

      ____________________________________  Con las opciones BIND, REBIND, y

             FREE. Permiten crear, cambiar o borrar el plan de cualquier pro-

             grama DB2.

 

 

      DCLGEN (Declarations Generator):

      _______________________________  Usa la informacion del catalogo DB2

             para:

 

                . Crear definiciones de las estructuras de tablas o vistas,

                  que van a ser guardadas en librerias de Copys para su pos-

 

 

 


                                                                 5

 

 

                  terior inclusion en los programas.

 

                . Producir instrucciones SQL DECLARE TABLE

 

 

      ONLINE HELP:

      ___________  Proporciona ayuda durante la ejecucion del spufi.

 

 

 

      ESTRUCTURA DE UN PROGRAMA QUE OPERA EN UN ENTORNO CICS

      ______________________________________________________

 

 

           +———————————————–+

           | APLICACION                                    |

           |                                               |

           |          1 – Area de datos <———–+      |

           |          2 – SQLCA                     |      |

           |                                        |      |

           |   +–>   3 – Entrada al programa       |      |

           |   |      4 – Tratar mensaje de entrada |      |

           |   |      5 – Proceso                   |      |

           |   |      6 – Instrucciones SQL —+    |      |

           |   |      7 – Mensaje de salida    |    |      |

           |   |  +– 8 – Terminacion          |    |      |

           |   |  |                            |    |      |

           +—|–|—————————-|—-|——+

               |  |                            |    |

               |  |                            v    |

         +——–|————–+         +———–+  —–>  +—–+

         |  ENTRY |   CICS/OS/VS |         | CICS/OS/VS|          | DB2 |

         |        v              |         +———–+  <—–  +—–+

         |        EXIT           |

         +———————–+

 

 

       1- DB2 pone los valores de las columnas recuperadas en las variables

          del programa.

 

       2- Cada programa que accede a datos DB2 debe tener un area de datos

          llamada SQLCA (SQL Comunication Area), usada por el DB2 para decir

          al programa si la ejecucion de la ultima instruccion SQL ha sido

          correcta, mediante un codigo de retorno , que se puede examinar en

          los campos SQLCODE y SQLWARN0 (deben ser cero y blanco).

 

 

 

      PREPARACION DEL PROGRAMA PARA LA EJECUCION

      __________________________________________

 

 

        a. Traslacion de las instrucciones EXEC por el CICS/OS/VS.

           __________

 

 

 

 

 

 

 


                                                                 6

 

 

        b. Precompilacion del programa.

           ______________

 

           El precompilador busca errores de sintaxis, y prepara cada instru-

           ccion SQL para compilacion o ensamblaje, dando como resultado un

           conjunto de instrucciones fuente (modulo fuente).

           Ademas, el DB2 crea un DBRM (Data Base Request Module), que sera

           usado en el paso d, y contiene informacion acerca de cada instru-

           ccion SQL precompilada.

 

 

        c. Compilacion ( o ensamblaje) y link-edicion.

           ___________                   ____________

 

           Cada modulo fuente es compilado o ensamblado para dar lugar a un

           modulo de carga. Al link-editar, los modulos de carga se enlazan

           en un unico modulo objeto.

 

 

        d. Relacionar el programa y los datos DB2 (binding).

                                                   _______

 

           Se relaciona el programa (el DBRM que representa las peticiones

           SQL hechas por el programa) y las tablas y vistas DB2 que se quie-

           ren procesar. Esto da lugar a un plan de aplicacion, el cual es

           almacenado por el DB2 y usado cuando se ejecute el programa.

           El proceso de binding incluye los siguientes pasos:

 

                  . Examinar la validez de los nombres de columnas, tablas y

                    vistas utilizados en el programa

 

                  . Verificar que la persona esta autorizada a realizar las

                    operaciones de acceso especificadas por las instrucciones

                    SQL del programa

 

                  . Seleccionar los caminos de acceso a los datos DB2 que el

                    programa quiere procesar

 

           Cuando se va a hacer bind de una aplicacion se puede especificar:

 

           - Isolation level:

 

                  . RR el lock afecta a todas las filas que toca el programa

 

                  . CS el lock afecta a todas las filas a las que el programa

                       esta accediendo actualmente

 

           Se puede hacer lock del espacio de tabla entero que contiene la ta-

           bla que se quiere proteger mediante una instruccion lock table:

 

                  . Para permitir a otras aplicaciones recuperar, pero no ac-

                    tualizar, borrar o insertar filas se usa

 

                          EXEC SQL

                                   LOCK TABLE nombre_de_tabla

                                   IN SHARE MODE

                          END-EXEC

 

 

 


                                                                 7

 

 

                  . Para impedir que otras aplicaciones accedan a la tabla de

                    cualquier forma se usa

 

                          EXEC SQL

                                   LOCK TABLE nombre_de_tabla

                                   IN EXCLUSIVE MODE

                          END-EXEC

 

           - Resource adquisition time

 

                  . USE, por defecto

                    indica que se quieren adquirir los locks cuando los espa-

                    cios de tablas asociados sean accedidos por primera vez

 

                  . ALLOCATE

                    indica que se quieren adquirir los locks cuando se aloca

                    el plan de aplicacion

 

           - Resource release time

 

                  . COMMIT, por defecto

                    indica que se quieren liberar los locks en cada punto de

                    commit

 

                  . DEALLOCATE

                    indica que se quieren retener los locks hasta que termine

                    el programa

 

           - Explain pa h selection

 

                  . YES

                    se pide al DB2 informacion acerca de las decisiones que

                    esta tomando en el establecimiento de caminos de acceso

                    a los datos

 

                  . NO

 

           Si cambian las caracteristicas de la tabla o vista a la que accede

           el programa, el plan de aplicacion ya no es valido, y el programa

           no puede ser ejecutado. Cuando se intenta hacerlo, el DB2 automa-

           ticamente hace un rebind intentando crear un nuevo plan.

 

 

 

      INSTRUCCIONES S.Q.L.

      ____________________

 

 

 

      INSTRUCCIONES BASICAS

      ______________________

 

 

      Las instrucciones en lenguaje S.Q.L. (Structured Query Language) permi-

      ten comunicar peticiones de acceso a datos al DB2. Es usado por el ad-

      ministrador de la base de datos para crear y cambiar definiciones de

 

 

 

 

 


                                                                 8

 

 

      datos, por el administrador del sistema en cuanto a autorizaciones a

      usuarios de los datos, y por el programador de aplicaciones para recu-

      perar y actualizar datos.

 

      La primera clausula de una instruccion SQL dice al DB2 que operacion se

      quiere realizar.

 

      Los nombres de variables van precedidos de ‘:’.

 

 

 

      CLAUSULA WHERE

      ______________

 

 

      Especifica una condicion de busqueda que, identificara la fila o filas

      que se quieren recuperar, borrar o actualizar. La condicion de busqueda

      puede estar formada por uno o varios predicados separados por AND y OR.

 

      El DB2 primero evalua las clausulas NOT, luego AND y por ultimo OR. Se

      puede cambiar el orden de evaluacion por medio de parentesis, que son

      examinados primero.

 

      Ejemplos:

 

                  . WHERE NOT WORKDEPT = ‘C01′

                  . WHERE EMPNO = :EMPID

                  . WHERE JOBCODE + EDUCLVL > 70

                  . WHERE SEX = ‘V’ AND (DEPT = ‘C1′ OR DEPT = ‘C2′)

 

 

 

      SELECT INTO

      ___________

 

 

      Funcion

      _______

 

      Recuperar una fila especifica.

 

 

      Formato

      _______

 

      EXEC SQL

           SELECT nombres_de_las_columnas que nos interesan

           INTO nombres_de_vbles usadas para contener los datos recuperados

           FROM nombre_de_la_tabla o vista que contiene los datos

           WHERE condicion_de_busqueda

      END-EXEC

 

      Se pueden especificar hasta 300 columnas en la clausula SELECT. Si se

      quieren recuperar todas las columnas, en el mismo orden en que aparecen

      en la fila, se pondra un asterisco, en lugar del nombre de las columnas.

      el valor de la primera columna especificada se guardara en la primera

 

 

 


                                                                 9

 

 

      variable especificada en la clausula INTO, el de la segunda en la se-

      gunda, etc.

 

      Si ninguna fila satisface la condicion, el DB2 devuelve un codigo de

      NOT FOUND (SQLCODE=100).

 

      Si la clausula WHERE permitiera recuperar los valores de las columnas

      de dos o mas filas, DB2 devuelve un codigo de error en el SQLCODE y no

      recupera nada.

 

      Si varias filas cumplieran la condicion, se usara una instruccion

      DECLARE CURSOR para seleccionar las filas, seguida de una instruccion

      FETCH para mover los valores de las columnas a las variables una fila

      cada vez.

 

 

 

      UPDATE

      ______

 

 

      Funcion

      _______

 

      Cambiar el valor de una o mas columnas en cada fila que satisface la

      condicion de busqueda de la clausula WHERE. Tambien se puede usar para

      borrar un valor de una fila , cambiando el valor de la columna a NULL.

 

 

      Formato

      _______

 

      EXEC SQL

           UPDATE nombre_de_la_tabla o vista

           SET    columnas que se quieren actualizar = nuevo valor

           WHERE  condicion_de_busqueda

      END-EXEC

 

      El nuevo valor especificado para una columna puede ser el nombre de

      otra columna de la misma fila, una constante, una variable, una expre-

      sion aritmetica, un valor nulo, etc.

      Si se omite la clausula WHERE, el DB2 actualiza cada fila de la tabla

      o vista con los valores dados.

 

 

 

      DELETE

      ______

 

 

      Funcion

      _______

 

      Quitar filas enteras de una tabla, no columnas especificas.

 

 

 

 

 

 

 


                                                                10

 

 

      Formato

      _______

 

      EXEC SQL

           DELETE

           FROM   nombre_de_la_tabla o vista

           WHERE  condicion_de_busqueda

      END-EXEC

 

      Si se omite la clausula WHERE, se borraran todas las filas de la ta-

      bla o vista. Para borrar la definicion de una tabla, ademas de su

      contenido, se usa la instruccion DROP.

 

 

 

      INSERT

      ______

 

 

      Funcion

      _______

 

      Añadir nuevas filas a una tabla o vista, se puede:

         . Especificar los valores de las columnas que se quieren insertar

         . Incluir una instruccion SELECT en la INSERT para decir al DB2 que

           los datos para la nueva fila estan contenidas en otra tabla o vista

 

 

      Formato

      _______

 

      EXEC SQL

           INSERT

           INTO nombre_de_la_tabla o vista  (nombres de columnas)

           VALUES (valor_de_cada_columna especificada en la clausula INTO)

      END-EXEC

 

      Si se especifican menos nombres de columnas de los que hay en la fila,

      el DB2 les asigna valores por defecto.

 

      Si se intenta insertar una fila que duplica otra existente en la tabla:

        . Si la tabla tiene un indice unico, la fila no es insertada, y el

          DB2 proporciona un SQLCODE -803

        . Si la tabla no tiene un indice unico, la fila es insertada sin error

 

 

 

      INSERCION A LA VEZ DE VARIAS FILAS DE UNA TABLA EN OTRA TABLA

      _____________________________________________________________

 

 

      Ejemplo:

      Se crea una tabla llamada EMPTIME con las columnas EMPNUMBER,

      PROYNUMBER, STARTDATE y ENDDATE, y se usa la INSERT para llenarla.

 

 

 


                                                                11

 

 

      EXEC SQL

           INSERT INTO USERA.EMPTIME

                        (EMPNUMBER, PROYNUMBER, STARTDATE, ENDDATE)

           SELECT  EMPNO, PROYNO, COMIENZO, FIN

           FROM    DSN.EMP

      END-EXEC

 

      En una SELECT embebida en una INSERT no se puede poner UNION ni ORDER BY.

 

      El numero de columnas de la SELECT debe ser el mismo de la INSERT. Los

      datos que se seleccionan deben ser compatibles con las columnas en las

      que se van a insertar. Para las columnas que no se especifiquen se in-

      sertaran los valores por defecto establecidos cuando se creo la tabla.

      Cuando se inserta una fila en una vista, si esta no contiene todas las

      columnas de la tabla base, el DB2 inserta en ellas valores por defecto.

 

 

 

      FUNCIONES DEL CURSOR

      ____________________

 

 

      El cursor permite a un programa recuperar un conjunto de filas, y luego

      procesar una fila cada vez.

 

      El DB2 construye una ‘tabla de resultados’ para guardar todas las filas

      recuperadas al ejecutar una instruccion SELECT, y utiliza el cursor para

      hacerlas disponibles al programa. Un cursor identifica la fila actual

      de la tabla de resultados, que el programa puede recuperar secuencial-

      mente hasta que alcanza el fin de los datos ( SQLCODE=100, NOT FOUND).

      Un programa puede utilizar varios cursores, para cada uno de ellos se

      utilizan las siguientes instrucciones:

 

 

 

      DECLARE CURSOR

      ______________

 

 

      Funcion

      _______

 

      Se usa para definir e identificar un conjunto de filas que van a ser

      accedidas con un cursor.

 

 

      Formato

      _______

 

      EXEC SQL

           DECLARE          nombre_del_cursor

           CURSOR FOR SELECT columna 1, columna 2 …..

           FROM             nombre_de_la_tabla

           WHERE            columna 1 = condicion_de_busqueda

           FOR UPDATE OF    columna 2 …(columnas de cada fila que se

                                               quieren actualizar)

      END-EXEC

 

 

 

 


                                                                12

 

 

      La instruccion DECLARE CURSOR nombra un cursor. La instruccion SELECT

      define un conjunto de filas, que formaran la tabla de resultados.

 

      Es posible actualizar una columna de la tabla identificada aunque no

      sea parte de la tabla de resultados (que no haya sido nombrada en la

      instruccion SELECT), nombrandola en la clausula FOR UPDATE OF.

 

      Cuando DB2 evalua una instruccion SELECT, puede ocurrir que varias filas

      satisfagan la condicion de busqueda, y que algunas de ellas esten dupli-

      dadas. Para especificar que no se desean, se codificara:

              SELECT DISTINCT columna 1, columna 2, …..

 

 

 

      OPEN

      ____

 

 

      Funcion

      _______

 

      Le dice al DB2 que se esta preparado para procesar la primera fila de

      la tabla de resultados.

 

 

      Formato

      _______

 

      EXEC SQL

           OPEN  nombre_del_cursor

      END-EXEC

 

 

 

      FETCH

      _____

 

 

      Funcion

      _______

 

      Mover a las variables del programa el contenido de la fila seleccionada.

 

 

      Formato

      _______

 

      EXEC SQL

           FETCH  nombre_del_cursor

           INTO   variable 1, variable 2 ….

      END-EXEC

 

      Cuando se utiliza la instruccion FETCH, el DB2 usa el cursor para apun-

      tar a la siquiente fila de la tabla de resultados.

 

 

 


                                                                13

 

 

      WHENEVER NOT FOUND

      __________________

 

 

      Funcion

      _______

 

      Para detectar que ya no tenemos mas filas para procesar se puede:

 

        . Examinar si el SQLCODE tiene valor 100, esto ocurre cuando una ins-

          truccion FETCH ha recuperado la ultima fila de la tabla de resulta-

          dos y se da otra FETCH. Ejemplo: IF SQLCODE = 100 GO TO NO-DATA.

 

        . Codificar la instruccion WHENEVER NOT FOUND bifurcando a otra parte

          del programa.

 

 

      Formato

      _______

 

      EXEC SQL

           WHENEVER NOT FOUND GO TO       direccion_simbolica

      END-EXEC

 

 

 

      UPDATE

      ______

 

 

      Funcion

      _______

 

      Una vez que se ha recuperado la fila actual, se puede actualizar esta

      con UPDATE.

 

 

      Formato

      _______

 

      EXEC SQL

           UPDATE           nombre_de_la_tabla

           SET              columna 1 = valor , columna 2 = valor ….

           WHERE CURRENT OF nombre_del_cursor

      END-EXEC

 

      Cada columna que se quiere actualizar debe haber sido nombrada previa-

      mente en la clausula FOR UPDATE OF de la instruccion DECLARE CURSOR.

      La clausula WHERE identifica el cursor que apunta a la fila que va a

      ser actualizada. Despues de actualizar una fila, la posicion del cursor

      permanece en esa fila hasta que se utilice una instruccion FETCH para

      la siguiente fila.

 

      Con la instruccion UPDATE se actualizan varias filas con una sola ins-

      truccion SQL, mientras que UPDATE … WHERE CURRENT OF, permite obtener

      una copia de la fila, examinarla, y entonces, actualizarla.

 

 

 

 

 


                                                                14

 

 

      DELETE

      ______

 

 

      Funcion

      _______

 

      Una vez que se ha recuperado la fila actual, se puede borrar con DELETE.

 

 

      Formato

      _______

 

      EXEC SQL

           DELETE FROM       nombre_de_la_tabla

           WHERE CURRENT OF  nombre_del_cursor

      END-EXEC

 

      La clausula WHERE identifica el cursor que apunta a la fila que va a ser

      borrada. Despues de borrar una fila, no se puede actualizar o borrar otra

      utilizando ese cursor, hasta que se de una FETCH para la siguiente fila.

      Con la instruccion DELETE se borran varias filas con una sola instru-

      ccion SQL, mientras que DELETE … WHERE CURRENT OF, permite obtener

      una copia de la fila, examinarla, y entonces, borrarla.

 

 

 

      CLOSE

      _____

 

 

      Funcion

      _______

 

      Cerrar el cursor una vez que se ha terminado de procesar las filas de

      la tabla de resultados si se quiere volver a utilizarlo. El DB2 cierra

      el cursor automaticamente cuando termina el programa.

 

 

      Formato

      _______

 

      EXEC SQL

           CLOSE  nombre_del_cursor

      END-EXEC

 

      Si el programa completa una unidad de recuperacion todos los cursores

      abiertos son cerrados automaticamente por el DB2. Si el cursor se vuel-

      ve a abrir, el proceso comenzara al principio de la tabla de resultados,

      y se tendra que restablecer la posicion del cursor.

 

      Ejemplo:

      Supongamos que se quieren actualizar los datos de los empleados de la

      tabla DSN.EMPL dando un SYNCPOINT cada 100 filas.

 

 

 


                                                                15

 

 

         1. Declarar el cursor como conjunto de filas de EMPNO > UPD-EMP

 

            EXEC SQL                                       (valor actual

                    DECLARE EMPLEADO CURSOR FOR             del limite

                    SELECT EMPNO, SALARY                    mas bajo)

                    FROM DSN.EMPL

                    WHERE EMPNO > :UPD-EMP

                    ORDER BY EMPNO

            END-EXEC

 

 

         2. Abrir el cursor y mover los datos de la tabla de resultados una

            fila cada vez, de forma que el limite mas bajo de la tabla se ac-

            tualiza en UPD-EMP.

 

            EXEC SQL                  :  EXEC SQL

                    OPEN EMPLEADO     :       FETCH  EMPLEADO

            END-EXEC                  :       INTO   :UPD-EMP, :UPD-SAL

                                      :  END-EXEC

 

 

         3. Borrar una fila

 

            EXEC SQL

                    DELETE FROM DSN.EMPL

                    WHERE   EMNO = :UPD-EMP

            END-EXEC

 

 

         4. Al abrir el cursor de nuevo, la condicion de busqueda de la SELECT

            procesara la DSN.EMPL basandose en el valor actual de UPD-EMP.

 

 

 

      OPCIONES DE LA INSTRUCCION SELECT

      _________________________________

 

 

 

      OPERACIONES ARITMETICAS

      _______________________

 

 

      La instruccion SELECT se puede usar para realizar operaciones aritmeti-

      cas con los datos antes de que sean proporcionados al programa. Esto se

      consigue mediante operadores aritmeticos en la lista de columnas.

         SELECT JOBCODE + 10 . . .

 

      Los operadores aritmeticos se pueden usar con columnas de datos defini-

      dos como INTEGER, SMALLINT, DECIMAL, o FLOAT, con constantes numericas,

      y variables.

 

      El valor de la expresion aritmetica se asigna a una variable, pero no

      reemplaza al valor de la columna de la tabla o vista.

 

 

 

 

 

 

 


                                                                16

 

 

      FUNCIONES BUILT-IN

      __________________

 

 

      Permiten obtener informacion acerca de las filas que satisfacen la con-

      dicion de busqueda. Se puede incluir mas de una en cada instruccion SE-

      LECT. La funcion BUILT-IN se aplica a cada fila que satisface la condi-

      cion de busqueda, ignorando los valores nulos.

 

 

 

      AVG

      ___

 

 

      Promedio de los valores de una o mas columnas que contengan datos nume-

      ricos.

 

 

 

      MAX

      ___

 

 

      Maximo valor de una o mas columnas.

 

 

 

      MIN

      ___

 

 

      Minimo valor de una o mas columnas.

 

 

 

      SUM

      ___

 

 

      Suma del valor de una o mas columnas.

 

 

 

      COUNT

      _____

 

 

      . Numero de filas que satisfacen la condicion de busqueda:

 

        EXEC SQL

               SELECT COUNT(*)

               INTO EMP-COUNT

               FROM DSN-EMP

               WHERE JOBCODE >= 50

        END-EXEC

 

 

 


                                                                17

 

 

      . Numero de valores unicos de una columna en particular:

 

        EXEC SQL

               SELECT COUNT (DISTINCT DEPTNO)

               INTO DST-COUNT

               FROM DSN-EMP

        END-EXEC

 

 

 

      CLAUSULAS GROUP BY, HAVING Y ORDER BY

      _____________________________________

 

 

      No se pueden especificar en una instruccion SELECT dentro de la DECLARE

      CURSOR, cuando se pretenda actualizar una columna o borrar una fila.

 

 

 

      GROUP BY

      ________

 

 

      Divide las filas que satisfacen la condicion de busqueda en grupos, se-

      gun una o mas columnas especificadas en esta clausula. El resultado de

      la aplicacion de una funcion BUILT-IN, no sera ya un valor unico, sino

      tantos valores como grupos. Si hay valores nulos en una columna especi-

      ficada en GROUP BY, el DB2 los considera en un grupo por separado.

 

      Ejemplo:

      Calculo del salario medio por  departamento  el resultado sera una fila

      por departamento al no especificar WHERE, se procesan todas las filas.

 

                         EXEC SQL

                                 DECLARE XYZ CURSOR FOR

                                 SELECT WORKDEPT, AVG(SALARY)

                                 FROM DSN-EMP

                                 GROUP BY WORKDEPT

                         END-EXEC

                         …………………………………

                         EXEC SQL

                                 FETCH XYZ

                                 INTO :WORK-DEPT, :AVG-SALARY

                         END-EXEC

 

      Es posible agrupar por mas de una columna.

 

      Ejemplo:

 

      Calculo del salario medio por departamento y por sexo. El resultado

      seran dos filas por departamento (una por cada sexo), para las cuales

      se calculara el promedio.

 

 

 

 

 

 

 

 

 


                                                                18

 

 

                         EXEC SQL

                                 DECLARE XYZ CURSOR FOR

                                 SELECT WORKDEPT, SEX, AVG(SALARY)

                                 FROM DSN-EMP

                                 GROUP BY WORKDEPT, SEX

                         END-EXEC

                         …………………………………

                         EXEC SQL

                                 FETCH XYZ

                                 INTO :WORK-DEPT, :SEX, :AVG-SALARY

                         END-EXEC

 

 

 

      HAVING

      ______

 

 

      Se usa seguida de la clausula GROUP BY, para especificar que solo se

      desean los grupos que satisfagan una condicion.

 

      Se codifica como la clausula WHERE, pudiendo contener predicados multi-

      ples, la clausula DISTINCT, HAVING NOT, y funciones BUILT-IN.

 

      Ejemplo:

      Calculo del salario medio de las mujeres por departamento, pero solo

      para los departamentos en los que todos los empleados poseen un nivel

      de educacion igual o superior a 16.

 

                         EXEC SQL

                                 DECLARE XYZ CURSOR FOR

                                 SELECT WORKDEPT, AVG(SALARY), MIN(EDUCLVL)

                                 FROM DSN-EMP

                                 WHERE SEX = ‘F’

                                 GROUP BY WORKDEPT

                                 HAVING MIN(EDUCLVL) >= 16

                         END-EXEC

                         …………………………………

                         EXEC SQL

                                 FETCH XYZ

                                 INTO :WORK-DEPT, :AVG-SALARY, :MIN-EDUC

                         END-EXEC

 

 

 

      ORDER BY

      ________

 

 

      Indica el orden en el que se quieren recuperar las filas, el cual se

      especifica mediante el nombre de la columna o columnas, o un numero

      (ORDER BY 3 especifica que se quiere ordenar por la tercera columna

      de la tabla de resultados) cuando en la SELECT no se ha especificado

      un nombre de columna sino una expresion aritmetida, funcion BUILT-IN,

      etc.

 

 

 


                                                                19

 

 

      Todas las columnas de la claurula ORDER BY se han de haber especificado

      en la SELECT.

 

      Por defecto se toma orden ascendente. Si se encuentran valores nulos

      son tratados como los mas altos. Los caracteres graficos se ordenan

      en secuencia EBCDIC.

 

      Se pueden especificar secuencias de orden secundarias.

 

      Ejemplo:

 

      Recuperar el nombre y numero de departamento de las mujeres empleadas

      ordenado por numero  de departamento  descendente, y dentro  de  cada

      departamento por nombre de empleado.

 

                         EXEC SQL

                                 DECLARE XYZ CURSOR FOR

                                 SELECT EMPNAME, WORKDEPT

                                 FROM DSN-EMP

                                 WHERE SEX = ‘F’

                                 ORDER BY WORKDEPT DESC, EMPNAME

                         END-EXEC

                         …………………………………

                         EXEC SQL

                                 FETCH XYZ

                                 INTO :P GM-NAME, :WORK-DEPT

                         END-EXEC

 

 

 

      UNION

      _____

 

 

      Permite combinar dos o mas instrucciones SELECT para formar una unica

      tabla de resultados, eliminando las filas duplicadas. Se usa para mez-

      clar valores de varias tablas, pero el conjunto de filas seleccionadas

      de una tabla es añadido al conjunto de filas seleccionado de otra.

 

      Los tipos de datos y longitudes de las columnas nombradas en la instru-

      ccion SELECT deben ser identicos. Por ejemplo, SELECT A, B UNION X, Y

      la columna A debe ser identica a la X, y la columna B a la Y.

 

      Para especificar las columnas por las que se quiere ordenar se deben

      usar numeros, y nunca nombres de columnas.

 

      Para identificar de que instruccion SELECT procede cada fila, se inclu-

      ye una constante al final de la lista de cada SELECT.

 

                           SELECT A, B, ‘A1′ UNION X, Y, ‘B1′

 

      El DB2 devolvera la constante correspondiente a cada instruccion SELECT

      en la ultima columna de la fila.

 

 

 

 

 

 

 

 


                                                                20

 

 

      Ejemplo:

 

                         EXEC SQL

                                 DECLARE XYZ CURSOR FOR

                                 SELECT EMPNO

                                 FROM DSN.EMP1

                                 WHERE WORKDEPT = :WORK-DEPT

                         UNION

                                 SELECT DISTINCT EMPNO

                                 FROM DSN-EMP2

                                 WHERE PROJNO = :NUM-PROY

                         END-EXEC

                         …………………………………

                         EXEC SQL

                                 FETCH XYZ

                                 INTO :EMP-NUMBER

                         END-EXEC

 

 

 

      SELECCION DE DATOS DE VARIAS TABLAS EN UNA FILA (JOIN)

      ______________________________________________________

 

 

      Es posible formar una fila de la tabla de resultados para la cual una

      parte de las columnas procedieran de una tabla, y otra parte de otra.

      Si coinciden los nombres de columna de dos tablas, habra que calificar-

      los, poniendo como prefijo el nombre de la tabla.

 

      En la instruccion SELECT se listan los nombres de las columnas que se

      quieren. Si en su lugar se coloca un *, el DB2 devuelve una fila forma-

      da por todas las columnas de la primera fila, unido a todas las de la

      segunda.

 

      Si no se especifica la clausula WHERE cada fila de la primera tabla es

      concatenada a cada fila de la segunda tabla para formar una tabla de

      resultados, cuyo numero de filas sera el producto del numero de filas

      de cada tabla.

 

      Ejemplo:

                       A=3 B=2 AB=6

 

      Este metodo se puede usar para crear una vista, pero esta no puede ser

      procesada mediante instrucciones UPDATE, DELETE o INSERT.

 

      Es posible unir una tabla o vista consigo misma, y hasta 7 tablas.

      Ei se usa la clausula GROUP BY en la definicion de una vista, la vista

      no puede ser unida con otra tabla.

 

      Ejemplo:

 

      Queremos recuperar las columnas EMPNO y LASTNAME de la tabla DSN.EMP1,

      y las columnas ACTNUM y EMPTIME de la DSN.EMP2.

 

 

 


                                                                21

 

 

                       EXEC SQL

                              DECLARE XYZ CURSOR FOR

                              SELECT EMP1.EMPNO, LASTNAME, ACTNUM, EMPTIME

                              FROM DSN.EMP1, DSN-EMP2

                              WHERE EMP1.EMPNO = EMP2.EMPNO

                      END-EXEC

                      …………………………………

                      EXEC SQL

                              FETCH XYZ

                              INTO :EMP-NUMBER, :NAME, :ACTIV, :TIME

                      END-EXEC

 

 

 

      OPCIONES DE LAS CONDICIONES DE BUSQUEDA

      _______________________________________

 

 

      Todas ellas se pueden codificar con NOT.

 

 

 

      BETWEEN … AND

      _______________

 

 

      Especifica que la condicion debe ser satisfecha por cualquier valor que

      este comprendido entre los dos valores especificados.

 

                  WHERE EDUCLVL BETWEEN 13 AND 16

 

 

 

      IN

      __

 

 

      Indica que se esta interesado en las filas para las que el valor de la

      columna especificada esta entre los valores que se listan.

 

                  WHERE WORKDEPT IN (‘A00′, ‘C01′, ‘E02′)

 

 

 

      LIKE

      ____

 

 

      Indica que se esta interesado en las filas para las que el valor de la

      columna especificada es similar al que se proporciona. El grado de pa-

      recido es determinado por dos caracteres especiales usados en la cadena

      que se incluye en la condicion de busqueda:

 

 

 

 

 

 

 

 

 


                                                                22

 

 

         . Un ‘_’ para cualquier caracter simple

         . Un ‘%’ para una cadena de caracteres. A no ser que el % preceda a

           la cadena de caracteres, la cadena buscada puede comenzar en cual-

           quier parte de la columna

 

      Se usan cuando no se conocen todos los caracteres de una columna o no

      importa su contenido.

 

      Ejemplo:

 

      . Para encontrar todos los empleados que viven en San Jose, debemos es-

        tar seguros de que San Jose no es parte del nombre de otra ciudad,

        porque el DB2 nos devolveria tambien esa fila.

 

                  WHERE TOWN LIKE ‘%SAN JOSE%’

 

      . Para encontrar todos los empleados que viven en ciudades que comien-

        den por SAN.

 

                  WHERE TOWN LIKE ‘SAN%’

 

 

 

      IS NULL

      _______

 

 

      Indica que se esta interesado en las filas para las que el valor de la

      columna especificada es nulo.

 

                  WHERE CODIGO IS NULL

 

      Para indicar que se esta interesado en las filas para las que el valor

      de la columna especificada es blancos.

 

                  WHERE CODIGO = ‘ ‘

 

 

 

      VISTAS

      ______

 

 

 

      CONCEPTO

      ________

 

 

      Una vista no contiene datos. Es una definicion almacenada de un conjun-

      to de filas y columnas. Cuando un programa accede a los datos definidos

      por una vista, el DB2 procesa la definicion de la vista. El resultado

      es un conjunto de filas a las que el programa puede acceder mediante

      instrucciones SQL.

 

 

 

 


                                                                23

 

 

      Una vista es como una ventana a traves de la cual un programa puede

      acceder solo a los datos que necesita. Las vistas se pueden usar inter-

      cambiablemente con las tablas cuando se recuperan datos, y pueden deri-

      varse de una o mas tablas. Puede tener nombres de columnas diferentes

      de los nombres de las tablas.

 

      Las funciones de una vista son:

       - Seguridad: No permitir el acceso de algunos usuarios a todos los

                    datos de una tabla.

 

       - Hacer disponible a una aplicacion un subconjunto de una tabla, con

         lo cual es mas facil codificar instrucciones SQL ya que solo se ven

         las columnas y filas de una tabla que se necesitan.

 

         Una vista basada en una tabla de empleados puede contener filas solo

         para un determinado departamento.

 

       - Resumir datos de una tabla y hacerlos disponibles:

 

                  . La suma de valores de una columna

                  . El maximo valor de una columna

                  . El promedio de valores de una columna

                  . El resultado de una expresion aritmetica con una o mas

                    columnas

 

      Cuando se actualizan los datos de una vista, se actualizan los datos

      de la tabla de la que se deriva, y, al contrario, si los datos de una

      tabla cambian, los datos accesibles a traves de vistas basadas en la

      tabla tambien cambian.

 

 

 

      PROCESO DE UNA VISTA

      ____________________

 

 

      Una vista se puede crear via SPUFI.

 

      Para insertar una fila en una tabla por medio de una vista, la fila

      debe tener un valor para cada columna de la tabla que no posee un

      valor por defecto.

 

      Si se crea una vista usando funciones BUILT-IN o las clausulas DISTINCT

      o GROUP BY, solamente es posible usar la instruccion SELECT para ella.

 

      Si una columna de una vista es definida por medio de una expresion arit-

      metica, no se podra actualizar la columna o insertar filas en la vista.

 

      Si se crea una vista especificando ‘WITH CHECK OPTION’, todas las inser-

      ciones y actualizaciones contra la vista son chequeadas antes para com-

      probar que la fila esta conforme con la definicion de la vista.

 

      Si la vista es resultado de un JOIN, los datos de esta pueden ser

      seleccionados, pero no se puede insertar, borrar o actualizar.

 

 

 

 

 

 

 


                                                                24

 

 

      No se puede crear un indice para una vista.

 

      Si la instruccion SELECT usada para crear una vista contiene un * en

      lugar de una lista de columnas, y mas tarde alguien añade otra columna

      a la tabla en la cual se basa la vista, la nueva columna no aparecera

      en la vista a menos que la vista sea creada de nuevo.

 

 

 

      SUBQUERY

      ________

 

 

      En una SELECT anidada en otra SELECT la primera  se  llama SELECT

      OUTER-LEVEL, y la interna SUBQUERY.

 

      Cuando se usa una SUBQUERY el DB2 la evalua, y sustituye el valor que

      resulta de ella en la clausula WHERE o HAVING de nivel superior, ya que

      puede existir una SUBQUERY dentro de otra, hasta cinco, e incluso, pue-

      de haber varias condiciones de busqueda conectadas por operadores rela-

      cionales, cada una con SUBQUERYS.

 

      La instruccion SELECT de una SUBQUERY solo puede especificar 1 columna,

      1 funcion BUILT-IN o 1 expresion aritmetica.

 

      Una SUBQUERY no puede contener una clausula ORDER BY.

 

      Cuando la instruccion de nivel superior es otra SELECT:

 

         . La SUBQUERY puede basarse en la misma tabla o vista o en otra.

         . Se puede usar una SUBQUERY en una instruccion WHERE incluso si la

           SELECT es parte de una DECLARE CURSOR, INSERT o CREATE VIEW.

 

      Cuando la instruccion de nivel superior es UPDATE o DELETE la SUBQUERY

      no puede basarse en la misma tabla o vista.

 

 

 

 

      FORMAS DE INCLUIR UNA SUBQUERY EN UNA CLAUSULA WHERE O HAVING

      _____________________________________________________________

 

 

 

      1) Inmediatamente despues de un operador de comparacion

         ____________________________________________________

 

 

                       EXEC SQL

                              DECLARE XYZ CURSOR FOR

                              SELECT EMPNO, LASTNAME, JOBCODE

                              FROM DSN.EMP1

                              WHERE EDUCLEVEL >

                                       (SELECT AVG (EDUCLEVEL)

                                        FROM DSN.EMP2

                      END-EXEC

 

 

 

 


                                                                25

 

 

                      …………………………………

 

                      EXEC SQL

                              FETCH XYZ

                              INTO :EMP-NUMBER, :NAME, :CODE

                      END-EXEC

 

 

 

      2) Despues de un operador de comparacion seguido de:

         ________________________________________________

 

 

         . ALL, para indicar que el valor proporcionado debe ser comparado de

           una determinada forma con todos los valores que devuelva la SUBQUE-

           RY.

 

           Ej: WHERE EXPRESION > ALL SUBQUERY

 

                   para satisfacer la WHERE, el valor de la expresion debe

                   ser mayor que todos los valores devueltos por la SUBQUERY.

                   Si la SUBQUERY no selecciona ningun valor la condicion es

                   satisfecha.

 

         . ANY, para indicar que el valor proporcionado debe ser comparado de

           una determinada forma con cualquiera de los valores que devuelva

           la SUBQUERY.

 

           Ej: WHERE EXPRESION > ANY SUBQUERY

 

 

 

      3) Despues de IN, para indicar que el valor de la expresion debe estar

         ___________________________________________________________________

         entre los valores devueltos por la SUBQUERY.

         ___________________________________________

 

 

         Ejemplo:

 

                       EXEC SQL

                              DECLARE XYZ CURSOR FOR

                              SELECT EMPNO, LASTNAME, JOBCODE

                              FROM DSN.EMP1

                              WHERE EMPNO IN

                                       (SELECT DISTINCT EMPNO

                                        FROM DSN.EMP2

                                        WHERE PROYNO = ‘M1′)

                      END-EXEC

                      …………………………………

                      EXEC SQL

                              FETCH XYZ

                              INTO :EMP-NUMBER, :NAME, :CODE

                      END-EXEC

 

 

 

 

 

 

 


                                                                26

 

 

      4) Despues de EXISTS para verificar que al menos puede ser encontrada

         __________________________________________________________________

         una fila que cumpla la condicion de la SUBQUERY.

         _______________________________________________

 

 

         Ejemplo:

 

                      WHERE EXISTS SUBQUERY

 

        No es necesario especificar nombres de columna en la SELECT, sino *,

        ya que la SUBQUERY no va a devolver datos.

 

 

 

      SUBQUERY CORRELATIVA

      ____________________

 

 

      En una SUBQUERY normal, el DB2 ejecuta la SUBQUERY una vez, sustituye

      el resultado en la parte derecha de la condicion de busqueda, y evalua

      la SELECT de nivel superior segun el valor de la condicion de busqueda.

 

      Una SUBQUERY correlativa se ejecuta una vez para cada fila de la tabla

      o vista mencionada en la SELECT de nivel superior, de forma que se eva-

      lua la SUBQUERY para cada fila.

 

      Se usa para calcular algun valor que puede ser diferente para cada fila.

 

      Es igual que una normal excepto porque despues de especificar el nombre

      de la tabla en la clausula FROM, proporcionamos un nombre de correla-

      cion, variable que representa la fila actual. Para especificar la corre-

      lacion se prefija el nombre de columna con un nombre de correlacion.

 

 

 

      OTROS EJEMPLOS

      ______________

 

 

        Con UPDATE o SELECT, la SUBQUERY y la instruccion de nivel superior

        deben referirse a diferente tabla.

 

        En una INSERT, ni la SUBQUERY ni la SELECT de nivel superior dentro

        de la INSERT, pueden basarse en la misma tabla en la que se inserta.

 

 

 

      . Con una SELECT

        ______________

 

        Esta SELECT recuperara las columnas especificadas para cada empleado

        cuyo JOBCODE es superior a la media de su departamento.

 

 

 

 


                                                                27

 

 

                       EXEC SQL

                              DECLARE XYZ CURSOR FOR

                              SELECT EMPNO, LASTNAME, WORKDEPT, JOBCODE

                              FROM DSN.EMP ESTAFILA

                              WHERE JOBCODE >

                                       (SELECT AVG (JOBCODE)

                                        FROM DSN.EMP

                                        WHERE WORKDEPT = ESTAFILA.WORKDEPT)

                      END-EXEC

                      …………………………………

                      EXEC SQL

                              FETCH XYZ

                              INTO :EMP-NUMBER, :NAME, :D EPT, :CODE

                      END-EXEC

 

 

 

      . Con una UPDATE

        ______________

 

                       EXEC SQL

                              UPDATE DSN.EMP1 ESTAFILA

                              SET DATE = 9999

                              WHERE 840901 >

                                       (SELECT MAX (ENDATE)

                                        FROM DSN.EMP2

                                        WHERE PROJNO = ESTAFILA.PROJNO)

                      END-EXEC

 

 

 

      . Con una DELETE

        ______________

 

                       EXEC SQL

                              DELETE FROM DSN.EMP1 ESTAFILA

                              WHERE NON EXISTS

                                       (SELECT *

                                        FROM DSN.EMP2

                                        WHERE PROJNO = ESTAFILA.PROJNO)

                      END-EXEC

 

 

 

      SPUFI

      _____

 

 

      SPUFI (SQL PROCESSOR USING FILE INPUT) permite:

 

 

      - Crear y probar instrucciones SQL antes de incluirlas en un programa

        ________________________________

 

 

 

 

 

 

 

 


                                                                28

 

 

      - Creacion de una tabla

        _____________________

 

                              CREATE TABLE TEMPL

                              (EMPNO CHAR(6)     NOT NULL,

                               WORKDEPT CHAR(3)  NOT NULL,

                              JOBCODE DECIMAL(3) )

                              IN TESTTAB.DSN77EMP;

 

 

      - Creacion de una vista

        _____________________

 

        La instruccion CREATE VIEW se usa para establecer la definicion de una

        vista. La vista se crea cada vez que es referenciada por una instru-

        ccion SQL.

 

                              CREATE VIEW EMPDATA AS

                              SELECT *

                              FROM DSN.EMP

                              WHERE WORKDEPT = ‘C1′

 

 

      - Crear un nombre alternativo o sinonimo para una tabla o vista

        _____________________________________________________________

 

                    CREATE SYNONIM MITABLA FOR DSN.TABEMPL

 

 

        y dar de baja ese sinonimo.

        ___________________________

 

                    DROP SYNONIM MITABLA

 

 

 

      PANELES

      _______

 

 

      1) Panel Principal

         _______________

 

 

          . Especificar un fichero de entrada,

            _________________________________  que contiene las instrucciones

            SQL que se quieren ejecutar, cuya longitud de registro sera de 80

 

          . Especificar un fichero de salida

            _________________________________, que recoge cada instruccion y

            el resultado de la ejecucion, que seran los datos recuperados en

            una SELECT, o un SQLCODE en otras. Tambien incluye estadisticas.

 

 

 

 


                                                                29

 

 

          . Especificar las opciones de proceso

            ___________________________________ (YES por defecto)

 

             - CHANGE DEFAULTS: Poniendo YES se accede al panel de opciones

                                por defecto.

 

             - EDIT INPUT: Editar o no el fichero de entrada.

 

             - EXECUTE: Ejecutar o no las instrucciones del fichero de entrada

 

             - AUTOCOMMIT: Dar o no un punto de COMMIT.

 

             - BROWSE OUTPUT: Displayar o no el fichero de salida.

 

 

      2) Panel de opciones por defecto.

         _____________________________

 

 

          . Especificar otras opciones de proceso

            _____________________________________

 

             - ISOLATION LEVEL: Por defecto RR.

 

                  RR (REPEATABLE READ), garantiza que los valores de la base

                      de datos leidos o cambiados por SPUFI no pueden ser cam-

                      biados por otro programa hasta que SPUFI alcance un pun-

                      to de COMMIT.

 

                  CS (CURSOR STABILITY), garantiza que ningun programa puede

                      modificar los datos de una fila en la cual SPUFI tiene

                      posicionado un cursor. Esto proporciona maxima concu-

                      rrencia.

 

             - MAX SELECT LINES: Por defecto 250.

                   Maximo numero de filas que devolvera una SELECT.

 

 

          . Definir caracteristicas del fichero de salida

            _____________________________________________

 

 

             - RECORD LENGTH: Por defecto 4092.

               No puede ser menor que la del fichero de entrada.

 

             - BLOCK SIZE: Por defecto 4096.

 

             - RECORD FORMAT: Por defecto VB.

 

             - DEVICE TYPE: Por defecto SYSDA.

 

 

 

 

 

 

 

 

 

 

 


                                                                30

 

 

          . Especificar defectos para el formato de salida

            ______________________________________________

 

 

             - MAX NUMERIC FIELD: Por defecto 20.

               Anchura maxima para campos numericos.

 

             - MAX CHAR FIELD: Por defecto 80.

               Anchura maxima para campos de caracteres.

 

             - COLUMN HEADING: Por defecto NAMES.

                 NAMES

                 LABELS

                 ANY

                 BOTH

 

 

 

      3) PANEL COMMIT/ROLLBACK

         _____________________

 

 

         Si en el panel principal se especifica NO para AUTOCOMMIT, una vez

         que se completa la ejecucion se displaya este panel, en el que se

         indicara una de estas opciones:

 

          . COMMIT: Salvar los cambios en la base de datos.

          . ROLLBACK : Borrar los cambios en la base de datos.

          . DEFER

 

 

 

      RESTRICCIONES

      _____________

 

 

      Se pueden usar todas las instrucciones excepto:

         CLOSE         EXECUTE              OPEN

         DECLARE       FETCH                PREPARE

         DESCRIBE      INCLUDE              WHENEVER

 

      Las opciones no soportadas por spufi son:

       . INTO          en la SELECT

       . WHERE CURRENT en la UPDATE y DELETE

 

 

 

 


                                                                31

 

 

      CODIFICACION DE INSTRUCCIONES SQL EN UN PROGRAMA COBOL

      ______________________________________________________

 

 

      1) Definir un area de comunicacion llamada SQLCA

         _____________________________________________

 

 

         Es un area de datos en la cual el DB2 devuelve informacion acerca

         del resultado de la ejecucion de cada instruccion SQL.

 

         WORKING-STORAGE SECTION

                EXEC SQL

                        INCLUDE SQLCA

                END-EXEC

 

         Con esta instruccion el precompilador incluira:

 

         01 SQLCA.

           05 SQLCAID           PIC X(8).

           05 SQLCABC           PIC S9(9)  COMP.

           05 SQLCODE           PIC S9(9)  COMP.

           05 SQLERRM.

              49 SQLERRML       PIC S9(4)  COMP.

              49 SQLERRMC       PIC X(70).

           05 SQLERRP           PIC X(8).

           05 SQLERRD OCCURS 6 TIMES PIC S9(9)  COMP.

           05 SQLWARN.

              10 SQLWARN0       PIC X(1).

              10 SQLWARN1       PIC X(1).

              10 SQLWARN2       PIC X(1).

              10 SQLWARN3       PIC X(1).

              10 SQLWARN4       PIC X(1).

              10 SQLWARN5       PIC X(1).

              10 SQLWARN6       PIC X(1).

              10 SQLWARN7       PIC X(1).

           05 SQLEXT            PIC X(8).

 

 

 

      2) Describir cada tabla o vista a la que acceda el programa

         ________________________________________________________

 

 

         Esto puede hacerse de dos formas:

 

         . Codificando una instruccion DECLARE en la data division, especifi-

           cando nombre de la tabla y listando cada columna y su tipo de dato.

 

                  EXEC SQL

                           DECLARE DSN.TDEPT  TABLE

                           (DEPNUM CHAR(3)    NOT NULL,

                            DEPNOM VARCHAR(36) NOT NULL)

                  END-EXEC

 

 

 

 

 

 

 


                                                                32

 

 

         . Previamente a la precompilacion con el DCLGEN.

           Para incluir en el programa las declaraciones producidas por DCLGEN

 

                  EXEC SQL

                           INCLUDE nombre_del_miembro

                  END-EXEC

 

 

 

      3) Codificar instrucciones SQL

         ___________________________

 

 

         Cada instruccion comienza con EXEC SQL y termina con END-EXEC.

         En lugar de instrucciones COPY se codificara INCLUDE.

 

         No se pueden usar nombres de variables que comiencen por SQL, ni

         nombres de entradas externas o de plan que comiencen por DSN, ya

         que estan reservados para el DB2.

 

         No se pueden usar constantes figurativas como ZERO o SPACE en instru-

         cciones SQL.

 

 

 

         VARIABLES Y ESTRUCTURAS HOST

         ____________________________

 

 

         Las variables HOST son campos de datos, definidos en el programa en

         la WORKING STORAGE o en la LINKAGE SECTION, y especificados en la

         opcion INTO de una instruccion SELECT o FETCH, en los cuales se ponen

         los valores de los datos recuperados por el DB2. Las variables HOST

         se codifican en las instrucciones SQL precedidas por dos puntos.

 

         Una estructura HOST es un conjunto de variables HOST definidas en la

         DATA DIVISION del programa. Puede tener un maximo de dos niveles, ex-

         cepto la declaracion de una cadena de caracteres de longitud variable

         que requiere un numero de nivel 49. Para identificar un campo dentro

         de una estructura en una instruccion SQL se codificara el nombre de

         estructura seguido de un punto y el nombre del campo.

 

         La parte entera de un numero nunca es truncada, si un numero no cabe

         en una variable o en una columna, se trunca la parte decimal si tie-

         ne y si no da un error.

 

         La declaracion de variables HOST se hace en niveles 01 o 77 (las de

         nivel 77 no pueden ser usadas en instrucciones SQL).

 

         Una cadena de caracteres de longitud variable debe tener un nivel de

         01 a 48, y contener dos campos elementales a nivel 49:

 

            . Campo de longitud PIC S9(4) COMP.

            . Campo de valor    PIC X(80).     (Como maximo).

 

 

 

 


                                                                33

 

 

         VARIABLES INDICADOR

         ___________________

 

 

         Una variable indicador es un entero de media palabra.

 

         La variable indicador se especifica precedida de dos puntos inmedia-

         tamente despues de la variable HOST.

 

         Ejemplo:

 

                   EXEC SQL

                            SELECT TELFNUM

                            INTO :TELEF:INDNULL

                            FROM DSN.TABEMPL

                            WHERE EMPNO = :EMPID

                   END-EXEC

 

         Se usa para:

 

 

 

       . Indicar si ha sido asignado un valor nulo a su variable asociada.

         ________________________________________________________________

 

         Si el valor de la columna que se esta recuperando es nulo el DB2 po-

         ne un valor negativo en la variable indicador ( si no se usara esta

         daria un error), y el valor de la variable HOST no se alterara.

 

 

       . Verificar que una cadena de caracteres recuperada no ha sido truncada

         _____________________________________________________________________

 

         Si la variable indicador contiene un entero positivo, este especifica

         la longitud original de la cadena.

 

 

       . Colocar un valor nulo en una columna

         ____________________________________

 

         Cuando se procesa una instruccion UPDATE el DB2 chequea la variable

         indicador. Si esta contiene un valor negativo, el valor de la colum-

         na se pondra como nulo. Si contiene un valor mayor que -1 la varia-

         ble asociada contiene un valor para la columna.

 

 

 

      4) MANEJO DE CODIGOS DE RETORNO DE ERRORES : LA SQLCA

         __________________________________________________

 

 

 

         SQLCAID

         _______

 

 

         Cadena de caracteres (long 8) que identifica la SQLCA (valor ‘SQLCA’).

 

 

 

 


                                                                34

 

 

         SQLCABC

         _______

 

 

         Entero de una palabra que especifica la longitud de la SQLCA. Su va-

         lor es siempre 136 (X’88′).

 

 

 

         SQLCODE

         _______

 

 

         Es un entero de una palabra. Puede tener los siguientes valores:

         ZERO la instruccion se ha ejecutado correctamente

 

         -n ha ocurrido un error

 

         +n la instruccion se ha ejecutado correctamente pero ha ocurri-

                 do una condicion excepcional

 

         +100 no existen datos para procesar

 

 

 

         SQLERRM

         _______

 

 

         Cadena de caracteres de longitud variable (maximo 70) que describe

         una condicion de error.

 

 

 

         SQLERRD

         _______

 

 

         SQLERRD(3) indica el numero de filas insertadas, actualizadas o

         borradas en una tabla.

 

 

 

         SQLWARN0

         ________

 

 

         Si es blanco, el resto de las variables SQLWARN tambien lo es. De

         otra forma sera W, y al menos otra SQLWARN sera tambien W.

 

 

 


 

                                                                 35

 

 

         SQLWARN1

         ________

 

 

         Si es W al menos el valor de una columna ha sido truncado al almace-

         narlo en una variable HOST.

 

 

 

         SQLWARN2

         ________

 

 

         Si es W al menos un valor nulo ha sido eliminado de el conjunto de

         argumentos de una funcion.

 

 

 

         SQLWARN3

         ________

 

 

         Si es W el numero de variables HOST especificado en una instruccion

         SQL es distINTO del numero de columnas en la tabla o vista.

 

 

 

         SQLWARN4

         ________

 

 

         Si es W una UPDATE o DELETE dinamica no incluye una clausula WHERE.

 

 

 

      5) MANEJO DE CONDICIONES EXCEPCIONALES: INSTRUCCION WHENEVER

         _________________________________________________________

 

 

         Hace que el DB2 chequee la SQLCA y continue procesando el programa

         o bifurque a otra area del programa si encuentra un error. Puede es-

         tablecerse mas de una WHENEVER para una misma condicion a lo largo

         del programa, de forma que una WHENEVER afecta a todas las instru-

         cciones SQL siguientes hasta que se encuentre otra.

         Ejemplo:

 

                   EXEC SQL

                           WHENEVER CONDICION ACCION

                   END-EXEC

 

 

 

 

 

 

 

 

 

 

 

 


                                                                36

 

 

         Se pueden especificar tres condiciones:

         ______________________________________

 

 

        . SQLWARNING indica que se quiere hacer cuando SQLWARN0 = W o SQLCODE

          contiene un valor positivo distinto de 100.

 

        . SQLERROR indica que se quiere hacer cuando SQLCODE < 0

 

        . NOT FOUND indica que se quiere hacer cuando el DB2 no puede encon-

          trar una fila que satisfaga la instruccion SQL o cuando no hay mas

          filas para hacer una fetch (SQLCODE=100)

 

 

 

         Se pueden especificar dos acciones:

         __________________________________

 

 

        . CONTINUE el programa continua su ejecucion.

 

        . GO TO   el programa bifurca a otra area del programa, cuyo nom-

                  bre ira precedido de dos puntos.

 

 

 

       SUGERENCIAS PARA LA CODIFICACION DE INSTRUCCIONES SQL

       _____________________________________________________

 

 

         Para facilitar el uso de indices por el DB2

         ___________________________________________

 

       . Usar BETWEEN  en lugar de    >= AND <=

 

       . Evitar conversiones numericas especificando los mismos tipos de da-

         tos en las comparaciones, y usar datos de la misma longitud

 

       . Evitar el uso de predicados con LIKE que comiencen por ‘%’ o ‘_’

 

       . Evitar expresiones aritmeticas en un predicado

 

 

         Para facilitar la seleccion de datos de dos o mas tablas

         ________________________________________________________

 

       . Proporcionar datos redundantes en las JOIN de tablas

 

       . Usar JOIN en lugar de SUBQUERYS correlativas o de SUBQUERYS con IN

 

 

 

 


                                                                37

 

 

       NOTAS SOBRE DB2 / SQL.

       _____________________

 

 

       - DECLARE CURSOR.

 

         Esta sentencia SQL no actualiza el SQLCODE.

 

 

       - SQLCODE = 100.

 

         Si se declara un cursor para la SELECT y no hay filas que satisfagan

         las condiciones de busqueda el DB2 nos devuelve un SQLCODE = 100

         en la primera FETCH que se haga.

 

 

       - SELECT SUM(COLUMNA) EN COBOL:

 

         Si no se efectuo la suma -no existen filas para las condiciones de

         busqueda- el valor que se devuelve en el campo resultado de la

         suma sera nulos y en determinados casos se produce un SQL = -305.

         Conclusion: Preveerlo en cobol utilizando indicador variable

                     que devolvera valor negativo si el campo al que esta

                     asociado contien valores nulos.

 

 

         NOTA:       En CSP el campo que recoge la suma queda a ceros

                     y el SQLCODE devuelve ceros.

 

 

 

 

      APENDICE I

      __________

 

 

 

      EJEMPLOS DE INSTRUCIONES SQL EN COBOL II

      ________________________________________

 

 

 

      DISTINCT

      ________

 

 

      *—————————————————————-*

      * LEER TABLA DE ALTAS DE CLIENTE.                                *

      *—————————————————————-*

 

       BUSCA-ALTAS.

           MOVE TVACLTEA TO CVANORAA-A

           EXEC SQL

              SELECT DISTINCT CVANORAA, CVANCTAA

                 INTO :CVANORAA-A, :CVANCTAA-A

                 FROM FVAORALV

                 WHERE CVANORAA = :CVANORAA-A

           END-EXEC.

 

 

 


                                                                38

 

 

      COUNT

      _____

 

 

      *—————————————————————-*

      *CUENTA EL NUMERO DE DOMICILIACIONES DE UNA ENTIDAD EMISORA      *

      *—————————————————————-*

 

       CUENTA-DOMICILI.

            EXEC SQL

               SELECT

                  COUNT (*)

                  INTO :CDOIMPOP-M

                  FROM  FDOMAESV

                  WHERE CDOEMISA = :CDOEMISA-M

            END-EXEC.

 

 

 

      SUM

      ___

 

 

      *—————————————————————-*

      *SUMA EL CONTENIDO DEL CAMPO CVANTITP                            *

      *—————————————————————-*

 

       77  IND-NULL                       PIC S9(4) COMP    VALUE 0.

       SELECT2-FVAORBCV.

             MOVE 0 TO IND-NULL CVANTITP-C SQLCODE.

 

             EXEC SQL

                 SELECT SUM(CVANTITP)

                    INTO :CVANTITP-C :IND-NULL

                    FROM FVAORBCV

                    WHERE CVAENTIA  = :WS-ENTI-DEPOS-1234 AND

                          CVACLTEA  = :CVACLTEA-C    AND

                          CVAPIVAA  = :CVAPIVAA-C    AND

                          CVAOBSTA  = ’03′           AND

                          CVATIOPA  = ’5′

            END-EXEC.

 

           IF SQLCODE = NOENCONTRADO OR IND-NULL < 0

              THEN . . .

 

 

 

 


                                                                39

 

 

      UNION

      _____

 

 

      *——————————————————*

      * SELECCIONAR DE CARTERAS LA SUMA DE TITULOS QUE EL    *

      * CLIENTE POSEE DE LA CLAVE VALOR AMPLIACION Y QUE     *

      * ESTEN EN SITUACION ’0′ O ’1′.                        *

      * SE RECUPERARA UN FILA POR CADA DISTINTO CLTE  EN CADA*

      * CARTERA.                                             *

      *——————————————————*

 

       DECLARE-CURSOR1.

            MOVE CORRECTO TO SQLCODE.

 

            EXEC SQL

                 DECLARE CURSOR1 CURSOR FOR

                     SELECT

                        D.CVACLICA, SUM(CVAUNUMP – CVAPNUMP + 1)

                     FROM

                        FVACARTV D

                     WHERE

                       (D.CVAENTIA = :W-ENTIDAD      AND

                        D.CVACLANA >= :WS-CVACLA00   AND

                        D.CVACLANA <= :WS-CVACLA19   OR

                        D.CVAENTIA = :W-ENTIDAD      AND

                        D.CVACLANA >= :WS-CVACLA40   AND

                        D.CVACLANA <= :WS-CVACLA59)  AND

                        D.CVASITIA IN(’0′, ’1′)

                     GROUP BY

                        D.CVACLICA

                 UNION ALL

                     SELECT

                        P.CVACLICA, SUM(CVANTITP)

                     FROM

                        FVACAPRV P

                     WHERE

                       (P.CVAENTIA = :W-ENTIDAD      AND

                        P.CVACLANA >= :WS-CVACLA00   AND

                        P.CVACLANA <= :WS-CVACLA19   OR

                        P.CVAENTIA = :W-ENTIDAD      AND

                        P.CVACLANA >= :WS-CVACLA40   AND

                        P.CVACLANA <= :WS-CVACLA59)  AND

                        P.CVASITIA IN(’0′, ’1′)

                     GROUP BY

                        P.CVACLICA

                 ORDER BY 1

            END-EXEC.

 

            IF SQLCODE NOT = CORRECTO AND

               SQLCODE NOT = NOENCONTRADO

               THEN

                  MOVE ‘DECLARE-CURSOR1′ TO WS-PARRAFO

                  MOVE ‘FVACARTV’        TO DET8-TABLA

                  PERFORM FIN-DB2.

 

 

 

 

 

 


                                                                40

 

 

      BETWEEN (not between)

      _______

 

 

                 WHERE CAMPO1 BETWEEN 13 AND 16

      igual que  WHERE CAMPO1 >=13 AND CAMPO 1 <= 16

 

 

 

      IN (not in)

      __

 

 

                 WHERE CAMPO1 IN (‘A00′, ‘C01′, ‘E21′)

      igual que  WHERE CAMPO1 = ‘A00′

                    OR CAMPO1 = ‘C01′

                    OR CAMPO1 = ‘E21′

 

 

 

      LIKE (not like)

      ____

 

 

                 WHERE CAMPO1 LIKE ‘TIO %’  (que empienza por “TIO “)

                 WHERE CAMPO1 LIKE ‘%RAMIRO%’  (que lleva “RAMIRO” en

                                                cualquier posicion)

 

 

 

      GROUP BY con  clausulas AVG, MAX, MIN, HAVING

      ________

      (AVG)

 

 

                 EXEC SQL

                      DECLARE XMP1 CURSOR FOR

                         SELECT DEPARTAMENTO, SEXO, AVG(SALARIO)

                           FROM TABLA1

                         GROUP BY DEPARTAMENTO, SEXO

                 END-EXEC.

 

                 EXEC SQL

                      FETCH XMP1

                      INTO :CAMPO1, :CAMPO2, CAMPO3

                 END-EXEC.

 

 

 

 


                                                                41

 

 

      (Nos da la media de salario agupando por departamento y sexo.)

       ___________________________________________________________

      (MAX)

 

 

                 EXEC SQL

                      SELECT MAX(SALARIO)

                           INTO :CAMPO1

                           FROM TABLA1

                           WHERE DEPARTAMENTO = ‘AX21′

                 END-EXEC.

 

 

 

      (Nos da el mayor valor del campo salario del departamento AX21.)

       _____________________________________________________________

      (MIN)

 

 

                 EXEC SQL

                      SELECT MIN(SALARIO)

                           INTO :CAMPO1

                           FROM TABLA1

                           WHERE DEPARTAMENTO = ‘AX21′

                 END-EXEC.

 

 

 

      (Nos da el menor valor del campo salario del departamento AX21.)

       _____________________________________________________________

      (HAVING)

 

 

                 EXEC SQL

                      DECLARE XMP1 CURSOR FOR

                         SELECT DEPARTAMENTO, AVG(SALARIO), MIN(EDLI)

                           FROM TABLA1

                           WHERE SEXO = ‘V’

                         GROUP BY DEPARTAMENTO

                         HAVING MIN(EDLI) >= 16

                 END-EXEC.

 

                 EXEC SQL

                      FETCH XMP1

                      INTO :CAMPO1, :CAMPO2, CAMPO3

                 END-EXEC.

 

      (Nos da la media del salario y el valor minimo del campo EDLI por

      departamento cuando el campo sexo sea = ‘V’ y el minimo de EDLI

      sea mayor o igual que 16).

 

 

 

 

 

 

 

 

 

 

 


                                                                42

 

 

                 RECOMENDACIONES PARA EL DISEÑO DE APLICACIONES EN DB2

                 _____________________________________________________

 

 

        WLM.-IBM

 

                       Son   varias   las   funciones   involucradas   en   la

              implementacion de una aplicacion en DB2,  debiendo  tener  en

              cuenta  el  que  una  misma persona puede realizar mas de una

              funcion.

 

                       Las principales funciones que sera necesario     desarrollar

              son:

 

 

 

                    ADMINISTRACION DE DATOS

                    _______________________

 

 

                  Las  tareas a desarrollar consisten en, sobre la base de

              tener  una  vision  corporativa  de  los  datos,  desarrollar

              modelos logicos y mantener diccionario de datos, ademas de :

 

              *   Establecer  convenciones  para  la  denominacion  de  las

              entidades  de  datos,  las  relaciones  entre  datos  y   los

              atributos importantes.

 

              *   Definir reglas de integridad de datos.

 

              *   Desarrollar vistas de usuario.

 

              *   Soporte  al  Administrador  de  base  de  datos  y  a los

                  analistas de aplicaciones.

 

              *   Participar en las sesiones de diseño de base de datos.

 

 

 

                    ADMINISTRACION DE BASE DE DATOS

                    _______________________________

 

 

                 Es responsabilidad de esta funcion el diseño  y creacion

              de   objetos  DB2,  tales  como  tablas,  vistas  e  indices,

              incluyendo ademas:

 

              * Diseño de modelos fisicos de datos.

 

              * Desarrollo de nombres estandard para objetos  DB2  ,  tales

                como espacios de tablas, tablas, indices y vistas.

 

              * Definicion y creacion de objetos DB2.

 

              * Asistir  a los analistas de aplicaciones en los prototipos

                funcionales.

 

 

 

 


                                                                43

 

 

              * Desarrollar modelos de JCl para utilitis.

 

              * Recomendaciones de procedimiento  de  backup/recovery  para

                las tablas de aplicacion.

 

              * Soporte a los programadores de aplicaciones en la prueba de

                las aplicaciones DB2.

 

              * Participar en las revisiones de diseño de aplicaciones.

 

              * Dar soporte tecnico a miembros del equipo de desarrollo.

 

 

 

                    ADMINISTRACION DEL SISTEMA DB2

                    ______________________________

 

 

                 Es responsabilidad  de  esta  funcion  el  controlar  el

              subsistema  DB2.   Normalmente  esta  funcion  no  existe  al

              inicio, y entonces es compartida por el Administrador  de  la

              Base   de   Datos   y   el   Programador  del  Sistema.   Las

              responsabilidades que incluye son:

 

              * Definir el entorno de operacion (hw.  y sw.).

 

              * Definir las opciones iniciales de instalacion.

 

              * Suministrar la guia de funcionamiento  al  programador  del

                sistema y al operador.

 

              * Suministrar  informacion  a la funcion de planificacion de

                capacidad.

 

              * Establecer directrices de backup y recuperacion.

 

              * Controlar y perfeccionar el funcionamiento  del  subsistema

                DB2.

 

              * Participar en las revisiones de diseño.

 

              * Realizar  diagnostico de errores,tanto del sistema como de

                aplicaiones.

 

              * Establecer directrices para el  uso  de  las  funciones  de

                seguridad del subsistema  DB2  y  coordinar  su  uso con la

                funcion de seguridad de la organizacion de administracion.

 

 

 

                    PROGRAMADOR DEL SISTEMA DB2

                    ___________________________

 

 

                 Esta funcion es similar a la tradicional  de programador

              del sistema, en lo que se refiere al entorno DB2 incluye:

 

 

 

 

 


                                                                44

 

 

              * Instalacion de los productos prerrequisito.

 

              * Instalacion de DB2 y otros productos relacionados.

 

              * Implementar   procedimientos  para  la  determinacion  de

                problemas.

 

              * Aplicar mantenimiento de sw.

 

              * Ejecutar las herramientas  de  medicion  de  rendimiento  y

                control, suministrando la  informacion  a  las funciones de

                ADMINISTRACION DEL SUBSISTEMA DB2  Y  DE  PLANIFICACION  DE

                CAPACIDAD.

 

              * Implementar procedimiento de backup y recovery.

 

              * Suministrar autorizacion inicial.

 

              * Desarrollar procedimientos de operacion y entrenamiento de

                operadores.

 

 

 

                    OPERACION DEL SYSTEMA DB2

                    _________________________

 

 

                 Esta responsabilidad incluye:

 

              * Arrancar, parar y controlar el subsistema DB2.

 

              * Controlar  los  mensajes  del  DB2  y  tomar  las  acciones

                pertientes.

 

              * Comunicacion  con los demas operadores del sistema ( CICS,

                TP).

 

 

 

                    ANALISIS DE APLICACIONES

                    ________________________

 

 

                 Esta funcion tiene la responsabilidad  de  determinar  y

              documentar los requerimientos de la aplicacion, incluyendo la

              definicion de las relaciones entre los datos y los modelos de

              uso.  En concreto, esta responsabilidad incluye:

 

              * Definicion de los requerimientos de la aplicacion.

 

              * Definicion de las relaciones entre datos y modelos usados

 

              * Desarrollo del diseño logico de la aplicacion.

 

              * Definir las especificaciones de los programas.

 

 

 

 


                                                                45

 

 

              * Coordinar las revisiones del diseño.

 

              * Coordinar las pruebas integradas de programas.

 

 

 

                    PROGRAMACION DE APLICACIONES

                    ____________________________

 

 

                 Esta  funcion es responsable del desarrollo de programas

              e incluye:

 

              * Codificar y probar programas.

 

              * Implementar las reglas de integridad de datos.

 

              * Codificar rutinas comunes, tales como  rutinas  de  salida,

                codigos de retorno del SQL,puntos de control logicos,etc.

 

              * Creacion y carga de datos de prueba en tablas.

 

              * Participar en las revisiones del diseño y planificacion de

                pruebas.

 

              * Desarrollar procedimientos de backup y recuperacion para la

                aplicacion.

 

 

 

                    SOPORTE DE CENTRO DE INFORMACION

                    ________________________________

 

 

                 El  requerimiento  de  esta  funcion surgira si se van a

              utilizar herramientas orientadas a  usuarios  finales,  tales

              como  QMF  o  AS.   Las  responsabilidades  que  incluye esta

              funcion son:

 

              * Añadir nuevos usuarios y mantener sus profiles.

 

              * Soporte de consultas a usuarios finales.

 

              * Asistir a los usuarios en la localizacion y acceso  de  los

                datos que requieren.

 

              * Controlar el uso de disco por los usuarios finales.

 

              * Desarrollo de consultas comunes y complejas.

 

 

 

 

 

 

 

 

 

 

 

 


                                                                46

 

 

                    REPRESENTANTES DE LOS USUARIOS

                    ______________________________

 

 

                 Las responsabilidades iniciales de los usuarios son:

 

              * Asesoramiento  en  la  identificacion  y definicion de las

                entidades de la empresa y de sus procesos.

 

              * Definicion de las reglas de integridad.

 

              * Definicion de los objetivos de seguridad, disponibilidad  y

                rendimiento.

 

              * Pudiendo  tambien involucrarse en la fase de desarrollo de

                la aplicacion,para facilitar la creacion de  prototipos  de

                cons4 — e informes.

 

                 En  una fase inicial, las funciones de ADMINISTRACION DE

              DATOS Y ADMINISTRACION DE BASES DE DATOS, pueden ser asumidas

              por una sola persona.

 

                 Igualmente  que  en  el caso anterior, las funcioones de

              PROGRAMACION DEL SISTEMA DB2  y  ADMINISTRACION  DEL  SISTEMA

              DB2, pueden ser tambien asumidas por una sola persona.

 

 

 

                      MACRO ACTIVIDADES A DESARROLLAR EN UN PROYECTO

                      ______________________________________________

 

 

                              |Gestion de Proyecto.

                 PLANIFICACION|Seleccionar la aplicacion piloto.

                              |Definicion de estandares.

                              |Identificar los recursos a proteger.

 

 

                              |Definir el entorno de software.

                              |Verificar requerimientos hw.

                 HW/SW INSTAL.|Verificar requerimiento sw.

                              |Determinar las opciones de instal. del DB2

                              |Instalar DB2.

 

 

                              |Preparacion del entorno.

                              |Desarrollar rutinas y proc. comunes.

                 DISEÑO Y     |Analisis de la aplicacion.

                 DESARROLLO   |Diseño de Bases de Datos.

                 APLICACION   |Desarrollo de programas y prueba.

                              |Paso de desarrollo a produccion.

 

 

 

 


                                                                47

 

 

                              |Procedimientos de arranque y parada.

                              |Definir procedim. Backup y Recovery.

                 OPERACION Y  |Preparar procedim. de control.

                 RECUPERACION |Desarrollar proced. recovery y backup sis.

                              |Desarrollar proced. backup y recovery de

                              |  aplicaciones.

 

 

                              |Estrategia de desarrollo.

                 GESTION RDTO |Definir entorno de herramientas.

                              |Analizar y refinar.

                              |

 

 

                 De  la  lista  de  actividades expuesta, como se observa

              existen algunas que solo es necesario  realizarlas  una  vez,

              otras  que  habra  que  realizarlas ciclicamente y finalmente

              aquellas que son propias de cada aplicacion a desarrollar.

 

                 Del conjunto  de  funciones  expuestas,  sera  necesario

              asignarlas  a personas que formen parte del Proyecto y que al

              menos seran:

 

              *  Una  persona  como  Administrados  de  Base  de  Datos   y

                 Administrador de Datos.

 

              *  Dos analistas de aplicaciones, uno actuando como lider del

                 proyecto.

 

              *  Dos o mas programadores de aplicaciones.

 

              *  Una persona, con el papel de  Programador  del  Sistema  y

                 Administrador de Datos del Sistema.

 

 

 

                        PLANIFICACION.

                       _______________

 

 

                    1.-Gestion del Proyecto.

 

                    Consistira  en  el  desarrollo  de  la planificacion del

              Proyecto , definiendo la duracion de  cada  una  de  las

              tareas  y  la  dedicacion a ellas de los recursos del equipo,

              que en algunos casos no sera del 100%.

 

                    I5 Planificacion de la formacion  y  entrenamiento  de  los

              diferentes miembros del equipo.

 

 

                    2.-Seleccion de la aplicacion piloto.

 

                    En  la  que  participaran el administrador de la base de

              datos y analista de aplicaciones.

 

 

 

 

 

 


                                                                48

 

 

                    Debera ser una aplicacion de bajo riesgo, que  tenga  un

              amplio   espectro   de   aplicacion   de  herramientas,  como

              simulacion  completa  y  no  compleja  ademas  de  facil   de

              demostrar a los usuarios finales.

 

 

                    3.-Definicion de Estandares.

 

                    Participaran  en  administrador  de  base  de  datos, el

              administrador de datos,  el  programador  del  sistema  y  el

              analista de la aplicacion.

 

                    Su actividad consistira en :

 

                   a)  Denominacion  de  los objetos DB2, programas, copys,

                       transacciones, usuarios, jobs.

 

                   b)  Directrices  para  el  diseño  de  tablas  y  de  la

                       aplicacion.

 

                   c)  Directrices para la programacion de la aplicacion.

 

                   d)  Directrices de documentacion.

 

                   e)  Procedimientos especificos de instalacion.

 

 

                    4.-Identificar los recursos a proteger.

 

                    Definir  un  entorno  de  acceso  limitado de datos y de

              autorizaciones y responder a los requerimientos de auditoria.

 

                    Ser  responsable el administrador del sistema,  y  aquel

              personal  que tenga la responsabilidad de mantener el entorno

              de seguridad.  Ademas participarna el administrador de  bases

              de datos y analista de aplicaciones.

 

                    Las tareas a realizar son:

 

                   * Definir los objetivos de seguridad.

 

                   * Identificar los recursos a proteger.

 

                   * Establecer los grupos de autorizacion.

 

 

 

                           DISEÑO Y DESARROLLO DE LA APLICACION

                           ____________________________________

 

 

                    1.-Establecer el entorno de desarrollo de la aplicacion.

 

                    Consistira  en  preparar  el  entorno  de  trabajo  del

              programador de aplicaciones, en cuya actividad participara el

 

 

 

 


                                                                49

 

 

              administrador  del  sistema  y  el  administrador de bases de

              datos.

 

                    El  entorno  de  trabajo estara integrado por librerias,

              procedimientos (jcl’s, utilidades, etc.) y rutinas comunes  (

              verificacion SQLCA, frecuencia de puntos de control,etc).

 

 

                    2.-Analisis de aplicaciones.

 

                    Consistira   en   la   eleccion   y  aplicacion  de  una

              metodologia por parte de los analistas de  aplicaciones,

              para las tareas de:

 

              * Definicion de requerimientos.

 

              * Diseño externo.

 

              * Modelo logico de datos.

 

              * Diseño interno.

 

                    Actividades  importantes,  integradas  en las señaladas,

              son:

 

                   - ANALISIS DE DATOS.

 

                   - DESARROLLAR PROTOTIPOS FUNCIONALES ( QMF, CSP, SQL ).

 

                   - DIRECTRICES PARA EL DISEÑO DE APLICACIONES.

 

 

                    3.-Diseño de Base de Datos.

 

                    Tarea a realizar por el administrador de base de  datos,

              con la colaboracion del analista de aplicaciones.

 

 

                    4.-Desarrollo y prueba de programas.

 

                    Consistira  basicamente  en  la codificacion y prueba de

              los programas de la aplicacion, el responsable  directo  ser

              el  programador  de  aplicaciones  y  tambien  participara el

              analista de aplicaciones  el  el  administrador  de  base  de

              datos.

 

                    Las tareas a realizar son:

 

                   * Crear datos de prueba.

 

                   * probar las llamadas SQL.

 

                   * Desarrollar los programas.

 

                   * Prueba unitaria.

 

                   * prueba integrada.

 

 

 

 


                                                                50

 

 

                    5.-Paso de Desarrollo a Produccion.

 

                    El  responsable ser  el lider del projecto y participara

              todo  el  equipo  en  esta  actividad,   cyuas   tareas   mas

              importantes son:

 

              * Desarrollar el plan de migracion.

 

              * Revisar el rendimiento de la aplicacion.

 

              * Obtener el espacio en disco de produccion.

 

              * Informar al personal de produccion de la carga esperada.

 

              * Desarrollar  una  lista de puntos claves para el manejo de

                problemas.

 

              * Mover definiciones de bases de datos (DDL).

 

              * Crear librerias y mover programas de aplicacion.

 

              * Realizar la integracion de las tareas en CICS, TSO.

 

              * Cargar nuevos datos.

 

              * Nuevos usuarios.

 

              * Establecer autorizaciones.

 

              * Ejecutar RUNSTATS.

 

              * Preprocesar, compilar, link y bind de programas.

 

              * Pasar el EXPLAIN a todos los planes y verificar que se usan

                los caminos esperados.

 

              * Establecer procedimientos de contabilidad.

 

                    El   DB2   suministra   rutinas   de  servicio  llamadas

              “attachement  facilities”  que  conectan   el   programa   de

              aplicacion  al  DB2  para  permitir  usar  sus servicios.  El

              comando DSN y sus interfases asociadas, DSNELI  (para  TSO  o

              Batch  ), DSNCLI ( para CICS ) , son las facilidades standard

              de conexion al DB2.

 

                    Por  tanto  cualquier   programa    de   aplicacion   es

              totalmente  dependiente  de la conexion al DB2.  Por ejemplo,

              no  se  pueden  cambiar  los  nombres  de  planes,  antes  de

              finalizar   la   aplicacion.   Si  el  DB2  termina,  tambien

              terminar  la aplicacion.

 

 

 

 


                                                                51

 

 

                       DISEÑO DE BASES DE DATOS

                       ________________________

 

 

 

                    INTRODUCCION.-

                    ______________

 

 

                    En el proceso de diseño de bases de datos, deber a haber

              una primera  fase  de  diseño  en  la  que  se  definiese  la

              “Arquitectura  de datos” o “Modelo de datos corporativo”, que

              represente de forma general  a  la  entidad,  sin  entrar  en

              detalles,  simplemente  a  nivel  de   U Grupos  de  Datos y las

              Relaciones entre estos U  ( un  empleado,  un  departamento,  un

              cliente,  un prestamo, etc.), lo cual supondra el inicio  del

              diseño del Sistema de Informacion de la entidad.

 

                    Una vez cubierta  la  fase  anterior,  se  iniciara  el

              proceso  de  analisis  de  cada  una  de  las  aplicaciones o

              subsistemas definidos en el Sistema  de  Informacion,  y  por

              consiguiente  al  “analisis  de  los  datos”  propios  de  la

              aplicacion, definiendo entonces el  contenido  de  pantallas,

              informes y otros ficheros que requiera el usuario.

 

 

 

                           DISEÑO LOGICO DE BASES DE DATOS

                           _______________________________

 

 

 

                    A)ANALISIS DE DATOS.

                    ____________________

 

 

                1)Establecer  normas  de  denominacion  de  los  datos para

                evitar sinonimos(elementos con  diferente  nombre  y  mismo

                significado)  y  homonimos (elementos con el mismo nombre y

                diferente significado).

 

 

                2)Durante  la  elaboracion  del  modelo  de  datos,  nuevas

                U entidades U    se   crearan   siempre  que  dos  entidades  se

                U relacionen U  en una forma varios a varios.

 

                Un ejemplo podra ser que un  cliente  ha  prestado  varios

                avales y que un aval ha sido hecho por varios clientes.  Lo

                cual nos llevara a aconsejar  la  creacion  de  una  ta bla

                extra de Avales/clientes.

 

                En  esta  fase  se  definiran  tambien los atributos de las

                relaciones  y  de  las  entidades.   En  consecuencia   los

                diferentes  items  de  datos  que  integran  una  entidad y

                mediante que claves y tipos de  las  mismas  estableceremos

                las relaciones entre las diferentes entidades.

 

 

 

 

 


                                                                52

 

 

                3)En  la  teoria  relacional, clave primaria o candidata no

                implica camino de acceso o secuencia.

 

 

                4)Foreign keys son frecuentemente buenas candidatas para el

                rendimiento de indices.  (se puede usar en joins).

 

                Las Foreign Keys son las llamadas “claves ajenas”, es decir

                columna(s) de una entidad que es clave primaria en otra.

                Siendo muchas veces “claves candidatas” en la primera.

 

 

                5)Un “Clustering index” es aquel que determina el orden

                fisico de las filas de una tabla.

 

 

 

                        B)NORMALIZACION

                        _______________

 

 

                Una vez realizado el analisis  de  datos,  se  procedera  a

                normalizar las entidades (tablas) en tercera forma normal.

 

 

                1)Primera  forma normal.  Consiste en la eliminacion de los

                campos  repetitivos  mediante  la  creacion  de  una  nueva

                entidad.   La  cual  incluye  una  Foreign  Key en la nueva

                entidad,  para  asi  poder  realizar  JOIN  con   las   dos

                entidades.

 

                La  importancia   de   esta   normalizacion,   viene   dada

                fundamentalmente por la eliminacion de campos repetidos que

                dara  lugar a un no deseado  mantenimiento  de  los  campos

                implicados.

 

 

                2)Segunda forma normal.  Consiste en determinar  que  datos

                elementales   son   dependientes  de  campos  de  clave  no

                primaria.  Una nueva entidad se creara  con  estos  campos,

                incluyendo  tambien  una  foreign  key en la nueva entidad,

                para asi poder hacer join con las dos.

 

 

                3)Tercera forma normal.  Consiste en determinar  que  datos

                elementales  son  dependientes de solamente una parte de la

                clave primaria.  Una nueva  entidad  se  creara  con  estos

                campos,  incluyendo  una  foreign  key en la nueva entidad,

                para asi poder hacer join con las dos entidades.

 

                La importancia de la normalizacion  en  segunda  y  tercera

                forma normal, consiste en evitar anomalias de actualizacion

                ( en especial en lo que  se  refiere  a  la  integridad  de

                datos, tal como borrado desintencionado de registros ).  Si

                una tabla contiene  campos  no  dependientes  de  la  clave

                primaria  completa,  entonces  podran darse anomalias en la

                actualizacion.

 

 

 

 


                                                                53

 

 

                        C)INTEGRIDAD REFERENCIAL

                        ________________________

 

 

                La integridad referencial puede ser definida como:

 

                * Siempre que se  necesita  cambiar  una  tabla  (insertar,

                actualizar,  borrar),  que condiciones deben existir en las

                otras tablas?.

 

                * Siempre que se modifica  una  tablas,  que  otras  tablas

                deben ser cambiadas?.

 

                Estos aspectos afectan al desarrollo, a la planificacion de

                la recuperacion, etc.

 

 

                NOTA: En la version 2 del DB2, la integridad referencial es

                una funcion del DB2, la cual asegura la validacion de las

                relaciones entre una y otra tabla. Se pueden definir

                criterios referenciales, por ejemplo el que un empleado no

                pueda ser dado de alta en una departamento si este no

                existe. Tambien que una fila de una tabla de departamentos

                no puede ser borrada, mientras existan empleados asignados

                a ese departamento, etc.

 

 

 

                    INTEGRIDAD DE DATOS:

                    _____________________

 

 

 

              1.-UN ADECUADO DISEÑO DE BASE DE DATOS.

              _______________________________________

 

 

                     * ANALISIS TOTAL DE LOS DATOS.

 

                       Todos los campos que son necesarios y no derivables, se

                   almacenaran en una tabla.

 

 

                     * NORMALIZACION DE LOS DATOS, PARA EVITAR:

 

                       Restricciones artificiales en la aplicacion.

                   Almacenamiento  redundante  de  datos.   (excepto   para

                   Foreign Keys).

 

                   Cualquier posible anomalia en la actualizacion.

 

 

 

 

 

 

 

 

 

 

 


                                                                54

 

 

              2.-UN ADECUADO DISEÑO DE MODULOS, CODIFICACION Y PRUEBAS.

              _________________________________________________________

 

 

                     * Implementar   las  reglas  de  integridad  referencial

                       definidas al diseñar las bases de datos.

 

                     * Definir  e  implementar   las   unidades   logicas   de

                   trabajo.(LUW)

 

                       Para  en cada una de ellas poder establecer los puntos

                   de Commit en los  programas  batch  o  de  Sync  en  los

                   interactivos.

 

                   Esto  es  un aspecto importante en cuanto a integridad y

                   rendimiento.

 

                   -Una orientacion en programas interactivos puede ser:

                   Diseñar la  aplicacion  de  tal  modo  que  una  LUW  se

                   corresponda con cada pantalla que se visualiza.

 

                   Siempre   que   haya  un  CICS  RETURN  supone  un  Sync

                   implicito.

 

                   Evitar cancelaciones, de tal forma que cualquier  codigo

                   de retorno imprevisto, suponga un rollback y mensajes al

                   usuario, ademas de escribir la SQLCA en  un  fichero  de

                   errores.

 

                   Una  LUW  se  corresponde  con el uso de un thread, cuyo

                   tiempo  debe  minimizarse,  por  cuanto  supone  asignar

                   recursos  (  plan,  DBD’s, espacio de tablas, indices );

                   por ello el thread debe  procurar  liberarse  cuando  se

                   dialoga con el terminal.  Debe tambien recordarse que el

                   Rollback actua desde el ultimo commit o sync.

 

                   -Una orientacion en programas batch puede ser:

 

                   En  programas  de significada actividad de actualizacion

                   ser  necesario usar Commit y prever restauracion logica.

 

                   Las Commit deberan hacerse cada cierto numero de LUW, en

                   base a cuantos bloqueos puede tener el IRLM  (  esto  es

                   aplicable  a bloqueos de paginas  o tambien cada 10 o 15

                   minutos.

 

                   No olvidar que todo programa batch  que  realice  Commit

                   periodicos  debe  tener restauracion logica o debe poder

                   volver a ser ejecutado sin afectar a  la  integridad  de

                   los datos.

 

 

 


                                                                55

 

 

                        RENDIMIENTO Y CONCURRENCIA EN BASE DE DATOS

                        ___________________________________________

 

 

 

              ALGUNOS DE LOS FACTORES CLAVES SON :

              ____________________________________

 

 

 

              A)CONCURRENCIA.

              ______________

 

 

                * Esperas  por  liberacion  de bloqueos pueden esconder la

                  causa de un lento rendimiento.

 

                * Pueden  producirse  fallos  del  SQL  debido  a  que  se

                  sobrepasa  el  numero  de  bloqueos concurrentes del IRLM o

                  debido a un lock timeout o a un deadlock.

 

 

 

              B)TRABAJO REALIZADO POR EL DB2 PARA EL SQL.

              ___________________________________________

 

 

                * El factor clave consiste en el numero de paginas que  son

                  leidas y/o escritas por el DB2,lo cual es funcion del plan

                  de acceso y la cardinalidad de las columnas.

 

                   -Informacion sobre la cardinalidad de las  columnas  esta

                   disponible   en    el    Catalogo    del    DB2   (   en

                   SYSIBM.SYSCOLUMNS.COLCARD), para  columnas  que  son  la

                   primera  de un indice. Informacion sobre la cardinalidad

                   de indices  completos  tambien  esta  disponible  en  el

                   Catalogo  del  DB2  (tabla  SYSIBM.SYSINDEXES,  columnas

                   FIRSTKEYCARD Y FULLKEYCARD.

 

                  -Informacion sobre el plan de acceso esta disponible  va

                   DB2 EXPLAIN. Se puede usar para una sentencia SQL o para

                   una Plan completo.  Esta informacion incluye:

 

                   Secuencia en la  que  los  conjuntos  (query-blocks)  de

                   sentencias  son procesadas.  (ejemplos de sentencias SQL

                   que tiene varios query-blocks son subselects,  UNIONs  y

                   JOINs).

 

                   Secuencia de acciones dentro de un query-block.

                   Si  se esta usando un indice.  (se suministra el nombre)

 

                   Como se esta  usando  un  indice  (MATCHCOLS=0  indicara

                   busqueda por indice con mucho tiempo de ejecucion).

 

 

 

 

 

 

 

 


                                                                56

 

 

                  Si  hay  bloqueo  a  nivel de pagina o espacio de tabla.

                  (TSLOCKMODE).  Cuando es igual  a  X  o  S  reducira  la

                  concurrencia.

 

                  Si  se  hace clasificacion y porque.  (hay ocho columnas

                  de indicador de sort).

 

                    * LOS SIGUIENTES ASPECTOS AFECTAN A LA ELECCION DE UN  PLAN

                  DE ACCESO DEL DB2.

 

                  La consulta.

 

                  Los indices definidos.

 

                  Los parametros DDL.  (ejemplo locksize).

 

                  Los parametros bind.  (ejemplo isolation).

 

                  Las  estadisticas en el DB2 para el espacio de tablas, la

                  tablas y los indices, incluyendo:

 

                     -Tamaño de la tabla.

 

                     -Cardinalidad de los indices.Indices con pocos valores

                      distintos son menos usados por el DB2.

 

                     -Como     esta     organizado     el     espacio    de

                      tablas.(clustered?).

 

                     -Como estan organizados los indices,

 

                     -Cual es el nivel de actualidad  de  las  estadisticas

                      respecto al estado actual de los datos(RUNSTATS).

 

                  * DEFINIR SOLAMENTE LOS INDICES IDONEOS (UTILES), LOS EXTRA

                  DEGRADAN PROGRAMAS DE USUARIO Y DE UTILIDAD.

 

                  * TABLAS E INDICES BIEN ORGANIZADOS REDUCEN I/Os.

 

                      El  catalogo  del  DB2  nos  informa  de  este  nivel  de

                  organizacion.

 

                  * IMPRESCINDIBLE EL USO DE PROTOTIPOS.

 

 

 

                            DISEÑO FISICO DE BASES DE DATOS

                            ________________________________

 

 

                A continuacion vamos a ofrecer una serie de recomendaciones

                en la definicion de los diferentes objetos del DB2:  Grupos

                de almacenamiento, Bases  de  datos,  Espacios  de  tablas,

                Tablas, Indices y Vistas.

 

 

 


                                                                57

 

 

                1)Si las tablas usan muy frecuentemente el JOIN, habra  que

                considerar  la  posibilidad  de  desnormalizar  las tablas,

                almacenando los datos duplicados para evitar  la  necesidad

                del JOIN.

 

                Habra  que  analizar  el equilibrio entre el incremento del

                coste del JOIN y el incremento del coste  de  mantenimiento

                de datos duplicados.

 

 

                2)Aconsejar  el  uso  de NOT NULL en las columnas, a no ser

                que exista requerimiento por parte de la aplicacion.   Cada

                columna nula afecta negativamente al rendimiento y requiere

                un byte extra por fila y ademas  complican  la  logica  del

                programa, que debe verificar las nulas,

 

 

                3)Colocar  las columnas VARCHAR al final de la tabla.  Este

                tipo de columnas solo se definiran, cuando la  variabilidad

                del tamaño sea significativo, unos 20 bytes de media.

 

 

                4)El  usar ficheros Vsam definidos por el usuario, en lugar

                de la definicion de  grupos  de  almacenamiento,  da  mayor

                flexibilidad  en  la operacion.  Permitiendonos incrementar

                espacios de tablas o indices y/o mover un fichero  DB2  sin

                tener  que  hacer  descarga,  borrado, y carga de la tabla.

                 se podra  usar el REORG con UNLOAD o DSN1COPY para cambiar

                de lugar ficheros DB2).

 

 

                5)Limitar el nombre de indice a ocho caracteres, para poder

                usar  el  mismo   nombre   cuando   se   usen   grupos   de

                almacenamiento o se definan ficheros DB2.

 

 

                6)Asignar un prefijo a los objetos DB2 por cada aplicacion,

                para facilitar las consultas al catalogo del DB2.

 

 

                7)Se aconseja una tabla por espacio de tablas.

 

 

                8)Usar LOCKSIZE TABLESPACE:

 

                    Cuando una tabla es accedida en modo de solo lectura.

                Cuando  un  solo  usuario  la  actualiza.   (ej.   programa

                batch).

 

                Recordar  que  este  atributo  se  puede  modificar antes y

                despues de ejecutar un programa  mediante  el  bind.   Otra

                solucion sera  codificar LOCK TABLE en el programa.

 

                En  este  caso  tambien es aconsejable usar SUBPAGES = 1 en

                los indices.

 

 

 

 

 

 


                                                                58

 

 

                  9)Usar LOCKSIZE ANY cuando se ha decidido bloqueo de  pagina.

                En este caso el DB2 actua de la siguiente manera:

 

                *  En  el momento del BIND el DB2 elige normalmente bloqueo

                de pagina, a excepcion de que ISOLATION = rr y scan  de  la

                tabla se realize.

 

                * Durante la ejecucion el DB2 escalara el bloque a nivel de

                tabla si, y solamente si, el numero de paginas  actualmente

                bloqueadas  en  la tabla exceda al numero especificado como

                limite en NUMLKTS.

 

                En este caso subpages debe ser > 1 , en base  al  nivel  de

                actividad y al tamaño de la tabla.

 

 

                  10)Usar CLOSE = NO a no ser que el fichero se use muy poco.

 

 

                  11)Todos  los accesos a los datos deben de realizarse siempre

                a traves de VISTAS,  nunca  a  traves  de  las  tablas  base.

                Incluso  en el supuesto de que la VISTA sea igual a la tabla.

                Tambien conviene definir sinonimos para cada  vista,  de  tal

                forma  que  los accesos sean independientes del creador de la

                tabla o vista.Otras ventajas son:

 

                *  Incrementa  la  flexibilidad  de  la  autorizacion  :  Los

                usuarios de la vista tendran acceso solamente a las  columnas

                de la vista.

 

                * Incrementa la simplicidad de las sentencias SQL:  Limitando

                el acceso al conjunto de filas y  columnas  que  integran  la

                vista.

 

                *  Incrementa  el control sobre los programas y sus accesos a

                los datos.

                ( SELECT USER FROM SYSAPPL.USERVIEW )

 

 

                  12)Antes de  cargar  los  datos  crear  siempre  primero  los

                indices.

 

 

                  13)Considerar el particionar grandes tablas.  La eleccion del

                numero de particiones sera en base al tamaño de la tabla,  el

                numero  de  indices,  la  configuracion  de  los  discos y la

                planificacion de utilidades.

 

                * Facilita el REORG , COPY y RECOVER por particion.

 

                * Facilita el reducir de tamaño los ficheros del DB2.

 

                *  Un  aspecto importante es en que medida es particionable

                la clave del cluster.  Puede ocurrir que la  naturaleza  de

                los  datos dificulten una buena o aceptable distribucion de

                las particiones.  En caso  de  que  la  clave  primaria  la

 

 

 

 


                                                                59

 

 

                consideremos como “no particionable”, las soluciones son:

 

                -Tener una gran tabla.

 

                -Crear multiples tablas.

 

                -Definir otro indice primario que facilite la particion.

 

                * En general evitar tener tablas o particiones  mayores  al

                35% del disco.

 

 

                  14)Normalmente  usar solo PCTFREE, para establecer el espacio

                libre.

 

 

                  15)En tablas pequeñas considerar espacio  libre  extra,  para

                reducir  la contencion.  Consideraciones generales entorno al

                espacio libre son las siguientes:

 

                * A mayor espacio libre:

 

                  -menor frecuencia de Reorganizacion.

 

                  -menor contencion.

 

                  -incremento del tiempo de ejecucion de los programas de ut.

 

                  -incremento de I/O  de  una  sentencia  SQL  que  acceda  a

                  multiples filas.

 

                  -mayor ocupacion de disco.

 

 

                  16)Aspectos  generales  de almacenamiento, que pueden afectar

                al rendimiento.

 

                * En tablas de acceso muy frecuente,  procurar  colocarlas

                en los discos mas rapidos y en los de cabecera.

 

                *  La  anterior  recomendacion  aplicarla al Catalogo y al

                 Directorio del DB2.

 

                * Igualmente con lo referente a  los  espacios  de  tablas

                temporales  del  DB2.   (usadas  en join, subselect, order

                by,..).

 

                * Procurar evitar el uso de espacio secundario en datos  e

                indices,  ajustando adecuadamente el tamaño de las tablas.

                El uso de espacios secundarios degrada el rendimiento.

 

                * Cuando se usen grupos de almacenamiento, asignar un solo

                volumen a cada grupo.  Facilitara el control.

 

                * Evitar usar mas del 75% de cada volumen.

 

 

 

 

 

 


                                                                60

 

 

                           SELECCION DE INDICES

                           ____________________

 

 

              La   seleccion   de  indices  es  uno  de  los  aspectos  mas

              importantes en el diseño, siendo la mas notable  consecuencia

              el  rendimiento,  ademas de permitirnos forzar la unicidad de

              registros en donde la clave sea unica.

 

 

              1)Cuando para una tabla se defina mas de un  indice,  definir

              de  forma  explicita  el  indice cluster.  Debiendo ser aquel

              bajo cuyo criterio es normalmente procesado  el  conjunto  de

              datos.

 

 

              2)Evitar  indices  que  empiecen por la misma columna, con la

              excepcion  de  que  las  pruebas  demuestren   una   utilidad

              importante.

 

 

              3)En  indices  de  multiples  columnas,  la  secuencia de las

              columnas  es  un aspecto muy importante,  especialmente en lo

              que se refiere a la primera columna.  Todo ello es critico en

              la  seleccion  del  camino  de  acceso  y  por  tanto  en  el

              rendimiento.  Consultar manual GG24-3004.

 

 

              4)Es necesario analizar la relacion coste-beneficio existente

              en cada indice candidato, antes de decidir su utilizacion.

 

                * Los costes aumentan :

 

                  -Al   realizar   inserciones,    borrados    e    incluso

                  actualizacion.

 

                  -Al procesar utilidades (LOAD, REORG, CHECK y RUNSTATS).

 

                * Los beneficios son:

 

                  -Muy  limitados  si el DB2 tiene ya un indice alternativo

                  que  le  permitira   realizar  de  forma   eficiente   la

                  sentencia SQL.

 

                  -Reduccion del tiempo de acceso a los datos.

 

                  -Reduccion  del  tiempo  de  proceso,  al  poder realizar

                  funciones sin tener que hacer una clasificacion previa.

 

 

              5)Una vez cargadas las tablas, realizado el RUNSTATS y  hecho

              el bound de los planes de la aplicacion, se debe analizar que

              los indices definidos estan siendo  usados  eficientemente  y

              determinar  las  causas de situaciones diferentes, para poder

              decidir  si es necesario anular o crear otros indices.

 

 

 

 


                                                                61

 

 

              Para ello es necesario trabajar sobre un  entorno  de  tablas

              muy similar al que vaya a darse en produccion.

 

                * Consultar las tablas de SYSINDEXES Y SYSCOLUMNS, para :

 

                  -analizar en que medida un indice es unico o no.

 

                  -que   indices   no  estan  siendo  usados  por  las  sql

                  estaticas.

 

                  -si un indice no es usado, puede ser debido  a  unas  sql

                  defectuosas o que sobra el indice.

 

                  -para  las  sql  dinamicas  hacer  un  seguimiento con el

                  Explain.

 

 

 

                           DISEÑO, CODIFICACION Y PRUEBA DE APLICACIONES

                           _____________________________________________

 

 

 

                    ACCESO DE DATOS

                    _______________

 

 

              Hemos señalado anteriormente que los  programas  y  consultas

              almacenadas, accederan siempre a las tablas atraves de VISTAS

              , lo cual hace que las sentencias sql sean independientes del

              creador de las tablas y de los cambios de las mismas.

 

              Otros aspectos importantes a tener en cuenta son:

 

 

              1)Evitar  que  en  las  clausulas WHERE se den las siguientes

              condiciones:

 

                -Forzar  hacer  la  conversion   numerica   para   columnas

                numericas.   Para  ello  se  deben  hacer  las  variables o

                literales host,  del  mismo  tipo  de  datos,  precision  y

                escala.

 

                -Especificar  una serie de caracteres mayor que la longitud

                de la columna.  Procurar siempre que la variable o  literal

                sea menor o igual en tamaño.

 

                -Codificar   argumentos   de   busqueda   que   constituyan

                predicados que el DB2 no pueda decidir su  uso  contra  los

                indices.   Estos  aspectos  son  ampliamente  analizados en

                GG24-3004.

 

 

              2)Si  hay  varias  tecnicas para codificar una sentencia SQL,

              analizar cual de ellas es la mas eficiente.

 

 

 

 

 

 


                                                                62

 

 

              3)Algunas tecnicas que ayudan a elegir  el  mejor  camino  de

              acceso son:

 

                a)Si se va a actualizar una columna de un indice, entonces

                este indice no se considerara por el DB2 en sus criterios

                de busqueda. El procedimiento aconsejable sera:

 

                1)DECLARE y OPEN, una SELECT con el criterio de actualizar.

 

                2)FETCH a fila ( proxima).

 

                3)Cambiar el valor de la columna en el programa.

 

                4)DELETE de fila usando la clave primaria unica.

 

                5)Re-Insertar la fila con el nuevo valor de la columna.

 

                6)Repetir los pasos 2-5 para continuar actualizando filas.

 

 

              4)Siempre que sea posible usar JOIN’s en lugar de Subquery’s.

              Prototipo previo.

 

 

              5)Evitar  el uso del operando OR, a no ser que cada predicado

              se  refiera  a  la misma columna y el operador de comparacion

              sea =.

 

                -Una  alternativa al OR es UNION.  Tener en cuenta que esta

                opcion no presenta registros duplicados.

 

 

              6)Si en el acceso a grandes volumenes de  informacion  se  le

              obliga al DB2 a hacer Sort interno, porque no disponga en ese

              acceso de un indice ( casos de  JOIN,  ORDER  BY,  GROUP  BY,

              DISTINCT), entonces valorar el hacer un DFSORT.

 

 

 

                    DESARROLLO Y PRUEBA DE PROGRAMAS

                    ________________________________

 

 

 

              1)Fase de prueba unitaria.

              ————————–

 

 

                Cada desarrollador debe responsabilizarse  de  precompilar,

                compilar,  linkeditar y hacer bind de sus programas, ademas

                de disponer de sus tablas de prueba, para lo cual cada  uno

                podra   tener   su  propia  base  de  datos  y  grupos   de

                almacenamiento.  Todo ello reducira  ademas  la  contencion

                en el catalogo del DB2.

 

 

 

 

 


                                                                63

 

 

              2)Fase de Prueba de la aplicacion.

              ———————————-

 

 

                Primero   se   debe   determinar   que   usuario   hace  la

                precompilacion, compilacion, linkedita y bind, evitando as

                el  que  si otro usuario hace el bind del plan, el original

                pierda la autoridad sobre el mismo.

 

                Determinar que ficheros se utilizaran en las pruebas para :

 

                -codigo fuente.

 

                -DBRM.

 

                -Modulos.

 

                -JCL’s.

 

                Prever un mecanismo para determinar que  planes  deben  ser

                rebound,  cuando  un  programa  se  modifica.   ( tabla del

                catalogo :  SYSIBM.SYSDBRM ).

 

 

                           |  Programa         |

                           |        Fuente     |

                                    |

                                    v

                           |  Traductor        |

                           |  Comandos Cics    |

                                    |

                                    v

                           |  Precompilador    |

                           |  DB2              |

                                    |

                       |                          |

                       v                          v

                |  DBRM      |            | Compilador  |

                                          | Cobol       |

                       |

                       v                          |

                |  BIND      |                    v

                                          | linkaje     |

                       |

                       v                          |

                |  PLAN      |                    v

                                          | modulo      |

 

 

              El Plan se almacena una tabla del catalogo del DB2, y es

              consecuencia de las sentencias SQL, de las definiciones de

              las tablas e indices sobre las que actua, etc., con objeto de

              optimizar el camino de acceso a los datos.

 

 

 

 

 

 

 

 


                                                                64

 

 

                        GESTION DE CAMBIOS EN BD

                        ________________________

 

 

              Por cambios en :

 

                  -Tablas y/o vistas (DROP, CREATE, ALTER).

 

                  -Adicion o baja de indices.

 

                  -Cambios en el espacio de tablas (ALTER).

 

 

              A)Si una tabla y/o indice se crea y se carga  con  datos,  de

              inmediato se debe:

 

                1)RUNSTATS sobre los objetos afectados.

 

                2)Todos  los  planes  afectados  por  los cambios deben ser

                rebound.

 

 

              B)Si se crea una nueva tabla y/o vista o dropped, y recreada.

              Entonces se debe de hacer inmediatamente:

 

                1)Hacer  un  re-GRANT  de  autorizacion  para  la tabla y/o

                vista.

 

                2)Avisar  a  los  usuarios  afectados  para  re-CREATE  sus

                sinonimos para la tabla y/o vista.

 

                3)Todos  los  planes  afectados  por  el  cambio  debe  ser

                rebound.  (*).

 

                (*) Tambien despues de  ALTER  una  tabla,  un  espacio  de

                tablas o indice y despues de DROP de un indice.

 

 

 

                        OPTIMIZACION DEL BIND

                        ______________________

 

 

              1)Se  hara  un  REBIND de un plan siempre que se desee que el

              DB2 vuelva a determinar sus caminos de acceso.  Algunos casos

              son:

 

                -Despues  de  crear  un  indice para una tabla que ya tenia

                datos.( CREATE —> RUNSTATS —> REBIND ).

 

                -Despues de ALTER un espacio de tablas, tabla o indice.

 

                -En la mayoria de los casos, despues de un RUNSTATS.

 

                -Despues de cambios significativos en los datos:

                REORG —> RUNSTATS —> REBIND

 

 

 

 


                                                                65

 

 

              2)Para reducir el numero de bloqueos de paginas,  hacer  BIND

              con ISOLATION = CS (cursor stability, es aconsejable desde un

              punto de vista de concurrencia, si se va a acceder  una  sola

              vez  a  una  fila  en una transaccion, pero si se accede a la

              misma file varias veces no  se  garantiza  consistencia,  por

              tanto  cuando  existen  razones  de  integridad  se  requiera

              ISOLATION = RR (lectura repetible).

 

 

              3)Para  mejorar  el   rendimiento   :    BIND   con   RELEASE

              (DEALLOCATE)

 

              Para mejorar la concurrencia BIND con RELEASE (COMMIT).

 

              Nota:   En  la  preparacion  de  esta  primera  parte  se  ha

              utilizado,   basicamente,  el  GG24-3004-00  IBM  DATABASE  2

              APPLICATION  DESIGN  AND   TUNING   GUIDE,   el   SC26-4130-2

              Development   Guide   y   la   publicacion   DB2  APPLICATION

              DEVELOPMENT RECOMMENDATIONS.

 

 

 

                       NUEVAS FUNCIONES Y MEJORAS DEL DB2 R3

                       _____________________________________

 

 

                    Resumimos  en  este  apartado   las   mejoras   que   se

              referencian  en el manual GG24-3182 :  IBM DATABASE 2 RELEASE

              3 NOTEBOOK.  En el citado  manual  existen  tambien  ejemplos

              sobre la aplicacion de las mejoras aportadas por el DB2 R3.

 

 

 

                    1.-MEJORAS EN EL LENGUAJE SQL:

 

 

                  A)Funciones escalares para:

 

                  * Convertir valores de un tipo de datos a otro.

 

                  * Extraer sub-series de series.

 

                  *  Sustituir  valores NULL en busqueda de datos, por otro

                  valor especificado por el usuario,

 

                  Las nuevas funciones  escalares  son:   DECIMAL,  DIGITS,

                  FLOAT, HEX, INTEGER, LENGTH, SUBSTR, VALUE Y VARGRAPHIC.

                  Las  anteriores  funciones :  MAX, MIN, COUNT, AVG y SUM,

                  ahora se denomina funciones de columna.

 

 

                  B)Nuevo operador de concatenacion para series.

 

 

 

 

 

 

 

 

 


                                                                66

 

 

                  C)Mejoras en el operador UNION:

 

                  * UNION ALL para retener valores duplicados.

 

                  * Eliminacion de restricciones del UNION.

 

 

                  D)Eliminacion de algunas restricciones en :

 

                  * Operador UNION ALL.

 

                  * Operador LIKE.

 

                  * INSERT con subselect.

 

                  * Clausula SET de la sentencia UPDATE SQL.

 

 

                  E)Posibilidad de manejar los errores de division por cero

                  y   otras   operaciones   de   conversion  y  aritmeticas

                  invalidas.

 

 

                  F)Nuevos tipos de datos :  DATE, TIME y TIMESTAMP.

 

 

                  G)Nuevos tipos de datos para numeros de simple  precision

                  y coma flotante.

 

 

                  H)Eliminacion de algunas restricciones de las VIEW.

 

 

 

                    2.-MEJORAS EN LA OPERACION.

 

 

                  A)Soporte DL/I Batch.

 

 

                  B)Mejoras en programas de utilidad:

 

                  *  LOAD REPLACE de una particion.  Antes solo era posible

                  a nivel de espacio de tablas.

 

                  * MODIFY ….  (*) , que  permite  a  un  usuario  borrar

                  todas  las  entradas  en SYSIBM.SYSCOPY, de un espacio de

                  tablas, particion o fichero.

 

                  * RECOVERY INDEX que permite recuperar de una pasada  una

                  lista  de  indices  o  todos los indices de un espacio de

                  tablas.

 

 

 

 


                                                                67

 

 

                  * RUNSTATS mejorado, al recojer mejores estadisticas para

                  incrementar el rendimiento de las sentencias SQL.

 

 

 

                    3.-MEJORAS DE RENDIMIENTOS.

 

 

                  A)En los programas de utilidad reseñados.

 

 

                  B)en UNION ALL.

 

 

                  C)En :

 

                  * Evitar sort innecesarios en ORDER BY y GROUP BY.

 

                  * Clasifica el resultado de una IN subquery.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


 

                                                                 68

 

 

 _____________________________________________________________________

 _____________________________________________________________________

 

 

 DB2.

 ____

 

 

    NO  SE  DEBEN  UTILIZAR  VARIABLES  DEFINIDAS  EN  NIVEL  01  DE

 LA  FD  PARA  CONDICIONES  DE  BUSQUEDA  DEL  DB2.  LAS  VARIABLES

 DE  LA  CLAUSULA  WHERE  HAN  DE  ESTAR  DEFINIDAS  EN  LA  WORKING,

 PARA  GARANTIZAR  UN  RESULATADO  OPTIMO.

 

 

 _____________________________________________________________________

 _____________________________________________________________________

 

 

 

 

 

Tutorial CGI

 

Javier E. Pérez Delgado

      

 

INTRODUCCIÓN

 

El CGI (Common Gateway Interface) es un estandar para comunicar aplicaciones externas con los servidores de información, tales como servidores HTTP o Web. Un documento en HTML que el daemon del Web se trae es estático, es decir, se mantiene constante: un fichero de texto que no cambia. Un programa CGI, por otro lado es ejecutado en tiempo real, así que puede generar información dinámica.

 

Por ejemplo, supongamos que quieres enganchar tu base de datos de Unix al WWW, para permitir a gente de todo el mundo consultarla. Básicamente se necesitará un programa CGI que el daemon del Web ejecutará para transmitir la información al gestor de base de datos, y recibir los resultados para presentarselos al cliente. Este es un ejemplo de pasarela (gateway), y es lo que CGI, actualmente en su versión 1.1, tiene en sus orígenes.

 

El ejemplo de la base de datos es una idea sencilla, pero la mayoría de las veces dificil de implementar. Realmente no hay límite con lo que tu quieras enganchar al Web. La íunica cosa que debes recordar es que haga lo que haga tu progrma CGI, no deberá tardar mucho tiempo en procesar. De otra manera, el usuario estaría esperando con su navegador a que algo pase.

 

Especificaciones

 

Como un programa CGI es un ejecutable, es equivalente a dejar a el mundo ejecutar un programa en tu sistema, que no es lo mas seguro a hacer. Por ello existen una serie de precauciones de seguridad que son necesarias de implementar cuando se usan programas CGI. Probablemente la que afectará al usuario típico del Web, es que hecho de que los programas CGI necesitan residir en un directorio especial, así el servidor sabe que tiene que ejecutarlo, en vez de simplemente mostrarlo por pantalla. Este directorio está generalmente bajo el control del webmaster, prohibiendo al usuario medio crear programas CGI. Hay otros métodos para permitir el accesos a scripts CGI, pero depende del webmaster que se te de esta posibilidad. Así que deberás contactar con tu webmaster para consultar la factibilidad de permitirte un acceso a los CGI.

 

Si dispones de una versión del servidor HTTPd NCSA, verás un directorio denominado /cgi-bin. Este es el directorio especial antes mencionado, donde todos los programas CGI residen. Un programa CGI  se puede escribir en cualquier lenguaje que permita ser ejecutado en el sistema, como:

 

     C/C++

     Fortran

     PERL

     TCL

     Algún Shell de Unix

     Visual Basic

     AppleScript

 

Simplemente depende de lo que tengas en tu sistema. Si usas un lenguaje de programación como C o Fortran, como ya sabrás, debes compilar el programa antes de poder ejecutarlo. Si miras en el directorio /cgi-src, encontrarás el código fuente de algunos programas CGI del directorio /cgi-bin. Pero, si usas alguno de los lenguajes interpretados, como PERL, TCL, o un shell de Unix, el script simplemente necesita residir en el directorio /cgi-bin, ya que no tiene un código fuente asociado. Mucha gente prefiere escribir scripts CGI en vez de programas, ya que son más fáciles de depurar, modificar y mantener que un programa típico compilado. 

 

¿Qué es el directorio cgi-bin?

 

Este es un directorio especial, que contiene los scripts, configurado dentro del servidor http. El servidor conoce que este directorio contiene ejecutables que deberán ser ejecutados y su salida deberá ser enviada al navegador del cliente. No se puede simplemente crear un directorio cgi-bin, el administrador del servidor deberá configurarlo para su uso. Si no está configurado, los scripst serán cargados como simples ficheros de texto.

 

Algunos servidores están configurados de tal manera que los ficheros con una determinada extensión (generalmente .cgi) son reconocidos como scripts y serán ejecutados como si estuvieran en un directorio cgi-bin.

 

Nota: No deberemos confundirlo con html analizado (generalmente .shtml)

 

La configuración de los directorios, o de la extensión mencionada antes, depende únicamente del servidor. Comprueba la documentación sobre tu servidor, o pregunta a otro usuario que también lo use.

 

¿Qué parte es Perl, y qué parte es html?

 

El formulario que se presenta al usuario está escrito en html, y este llama al script en el servidor escrito en perl . El script devolverá en la mayoría de los casos código html para presentar al usuario.

 

 

VARIABLES DE ENTORNO EN CGI

 

El servidor usa tanto de líneas de comando, como variables de entorno para pasar los datos del servidor al script.

 

Estas variables de entorno se activan cuando el programa ejecuta el programa cgi.

 

Especificación

 

Las siguientes variables no dependen de la información enviada y son activadas en todos los casos:

 

SERVER_SOFTWARE

 

Devuelve el nombre y la versión del software del servidor de información que contesta la petición de usuario (y ejecuta el programa cgi).

Formato: nombre/versión.

 

 

 

SERVER_NAME

 

Devuelve nombre de host del servidor, el alias DNS, o la dirección IP como aparecería en las URL autoreferenciadas.

 

GATEWAY_INTERFACE

 

Devuelve la revisión de la especificación CGI con que el servidor puede trabajar. Formato: CGI/revisión.

 

 

Las siguientes variables de entorno son específicas de la petición de usuario, y es el programa del gateway el que las da el valor:

 

SERVER_PROTOCOL

 

Da el nombre y revisión del protocolo de información con el que la peticion de usuario viene. Formato: protocolo/revisión.

SERVER_PORT

 

Devuelve el número de puerto por el cual fue enviada la petición.

 

REQUEST_METHOD

 

Devuelve el método por el cual la petición fue enviada. Para HTTP serán “GET”, “HEAD”, “POST”, etc.

 

PATH_INFO

 

La información extra sobre el path, tal como es dada por el cliente. En otras palabras, podemos acceder a los scripts por su pathname virtual, seguido de alguna información extra. Esa información extra es enviada como PATH_INFO. La información será decodificada por el servidor si viene de una URL antes de pasarla al script CGI.

 

PATH_TRANSLATED

 

El servidor proporciona una versión traducida del PATH_INFO, que transforma el path virtual al físico.

 

SCRIPT_NAME

 

Path virtual al script que va a ejecutar, usado para autoreferenciar URL.

 

QUERY_STRING

 

La información que sigue al signo ‘?’ en la URL que referencia al script. Es la información de la pregunta. No deberá ser decodificada de ningún modo. Esta variable será activada cuando hay una petición de información, sin hacer caso de la decodificación de la línea de comandos. 

 

REMOTE_HOST

 

El nombre de host que realiza la petición. Si el servidor no posee esta información activará REMOTE_ADDR y dejará esta desactivada.

 

REMOTE_ADDR

 

La dirección IP del host remoto que realiza la petición.

 

AUTH_TYPE

 

Si el servidor soporta autentificación de usuario , y el script está protegido, esta es el método de autentificación específico del protocolo para validar el usuario.

 

REMOTE_USER

 

Si el servidor soporta autentificación de usuario , y el script está protegido, este será el nombre de usuario con el que se ha autentificado.

 

REMOTE_IDENT

 

Si el servidor HTTP soporta autentificación RFC 931 , entonces está variable se activará con el nombre del usuario remoto obtenido por el servidor. Esta varible solo se utilizará durante el login.

 

CONTENT_TYPE

 

Para peticiones que tienen información añadida, como HTTP POST y PUT, este será el tipo de datos contenido.

 

CONTENT_LENGTH

 

La longitud del contenido tal como es dado por el cliente.

 

 

Además, las líneas de la cabecera recibidas por el cliente, si las hay, son colocadas en el entorno con el prefijo HTTP_ seguido del nombre de la cabecera. Cada carácter de el nombre de la cabecera se cambia por caracteres _. El servidor puede excluir algunos caracteres que ya haya procesado, como la autorización. 

El tipo de contenido y la longitud de este, pueden ver suprimidas sus cabeceras si al incluirlos se excede el límite de entorno del sistema.

Un ejemplo de esto es la variable HTTP_ACCEPT que se definió en CGI/1.0. Otro ejemplo es la cabecera USER_AGENT.

 

 

HTTP_ACCEPT

 

Los tipos MIME que el cliente aceptará, como son dados por las cabeceras HTTP. Otros protocolos pueden ser necesarios para obtener esa información de algún otro lugar. Cada elemento de esta lista deberá estar separado por comas por la especificación HTTP.

 

Formato: tipo/subtipo, tipo/subtipo

 

HTTP_USER_AGENT

 

El navegador que el cliente usa para mandar la petición.

 

Formato general: software/versión librería/versión.

 

 

 

LEYENDO EL FORMULARIO DE ENTRADA DE USUARIO

 

Cuando el usuario envía el formulario, el script recibe los datos como pares nombre-valor. Los nombres son lo que definimos en las etiquetas INPUT (o las etiquetas SELECT o TEXTAREA), y los valores aquello que el usuario haya escrito o seleccionado. (Los usuarios también pueden enviar ficheros con los formularios, pero no nos ocuparemos de ello.)

 

Estos pares nombre-valor llegan como una larga cadena que necesitamos formatear. No es muy complicado, hay una gran cantidad de rutinas que lo hacen por tí. En el directorio CGI de Yahoo encontrarás unas cuantas en varios lenguajes.

 

Si aun así prefiere hacerlo usted mismo, aquí esta el formato de la cadena:

 

            “nombre1=valor1&nombre2=valor2&nombre3=valor3”

 

            Así que sólo hay que dividir donde están los signos ‘&’ y ‘=’, y luego hacer dos cosas a cada nombre y valor:

 

            1.Convertir todos los signos ‘+’ a espacios.

            2.Convertir todas las secuencias ‘%xx’ al valor del carácter cuyo valor ASCII sea ‘xx’ en hexadecimal. Por ejemplo convertir ‘%3d’ a ‘=’.

 

            Esto se hace necesario porque la larga cadena original esta codificada según el código URL, para permitir los signos ‘&’, ‘=’, y todo lo que el usuario introduzca.

 

            Pero, ¿de donde se obtiene la cadena de entrada? Esto dependerá del método por el cual el formulario haya sido enviado:

 

Para los envíos con GET, será la variable de entorno QUERY_STRING.

            Para los envíos con POST, habrá que leer del STDIN. El número exacto de bytes a leer estará en la variable de entorno CONTENT_LENGTH.

 

  

 

DEVOLVIENDO LA RESPUESTA AL USUARIO

 

Primero, escribir la linea

 

            Content-Type: text/html

 

más otra línea en blanco en el STDOUT. Después, escribiremos nuestra pagina de respuesta en HTML al STDOUT, y será enviada al usuario cuando el script esté ejecutado.

 

Sí, estamos generando código en HTML en tiempo real. No es difícil, si no más bien directo. El código HTML fue diseñado lo suficientemente simple para poder ser generado por este método.

 

 

GUARDANDO LA ENTRADA DEL USUARIO EN UN ARCHIVO

 

En este apartado vamos a guardar los datos escritos por el usuario en un archivo para poder recuperarlos posteriormente. Como ejemplo utilizaremos un formulario para enviar chistes. Este formulario obtendrá la siguiente información y lo guardará en las variables indicadas entre paréntesis:

 

Nombre del Usuario (nombre)

Un Chiste (chiste)

 

Para ello creamos un formulario simple con el siguiente código:

 

<form method=”POST” action=”http://www.uva.es/cgi-bin/chiste-envio.pl”>

<P>Tu nombre: <input name=”nombre”></P>

<P>El chiste: <textarea cols=60 rows=5 name=”chiste”></textarea></P>

<P><input type=”submit” value=”Enviar”><BR>

<input type=”reset” value=”Borrars”></P>

</form>

 

 

Nuestro programa colocará está información para que la podamos dar el visto bueno. El programa devolverá una nota al usuario indicándole que su envío será revisado más tarde.

 

Por consistencia , llamaremos a este script chiste-envio.pl.

 

Necesitamos iniciar y comentar nuestro script. Utilizaremos la librería cgi-lib.pl de Steve Brenner’s para facilitar la entrada de formularios..

 

Seguiremos con el script que coja la entrada y devuelva e siguiente mensaje:

 

Gracias _nombre_, tu envío será revisado en breve.

 

El script que realiza esto es el siguiente:

 

#!/usr/local/bin/perl

# chiste-envio.pl

# Por Javier Pérez Delgado (jperez@ctv.es)

#

# Este script añade el chiste al archivo de chistes.

# Devuelve una nota al usuario, indicando que su chiste será revisado.

 

push(@INC,”/p/www/httpd/cgi-bin/”);

require(“cgi-lib.pl”);

 

&ReadParse;

 

print &PrintHeader;

print “<HTML><HEAD>\n”;

print “<TITLE>Reconocimiento de chiste</TITLE>\n”;

print “</HEAD><BODY>\n”;

print “Gracias “,$in{‘nombre’},”,Tu envío será revisado lo antes posible. .<P>\n “;

print “</BODY></HTML>\n”;

 

Ahora al tema central, añadir a un archivo. Para hacerlo necesitamos un archivo con permiso de escritura para el daemon http. La creación de este archivo variará entre distintos sistemas, y necesitaras consultar a tu webmaster. En este caso usaremos el archivo:

/users/jperez/www/tutor/chiste.txt

 

Para abrir el fichero para añadir al final usaremos el comando de perl para abrir ficheros, que tiene la siguiente sintaxis:

 

open(FILEHANDLE,”>>nombrearchivo”);

 

FILEHANDLE es como llamaremos al archivo mientras esté abierto. Es similar a una variable. Nombrearchivo es el archivo a abrir. Los signos ‘mayor’ (>>) son importantes, y como en el shell indican ‘añadir a un fichero’.

 

Así la orden de apertura quedará así:

 

open(CHISTE,”>>/users/jperez/www/tutor/chiste.txt”);

 

Nota: Es aconsejable usar letras mayúsculas para FILEHANDLE para ayudar a distinguirlo de las variables.

 

Escribir a un fichero es idéntico a como hicimos previamente. La única diferencia es que el FILEHANDLE es el primer argumento de la sentencia print, y está separado de lo que será imprimido por un espacio. Por ejemplo para escribir la linea “Las rosas son rojas” al FILEHANDLE CHISTE, escribiremos:

 

print CHISTE “Las rosas son rojas\n”;

 

Escribir variables se realiza de la misma manera:

 

print CHISTE “Las rosas son “,$in{‘colorrosas’},”\n”;

 

El código para nuestro ejemplo necesitará escribir al archivo:

 

            1. El nombre del usuario precedido con la cadena “Nombre:”

            2. Una línea en blanco.

            3. El chiste enviado.

            4. Una línea con 50 guiones.

 

El código es el siguiente:

 

print CHISTE “Nombre: “,$in{‘nombre’},”\n”;

print CHISTE “\n”;

print CHISTE $in{‘chiste’},”\n”;

print CHISTE “————————————————–\n”;

 

Ahora solo nos queda cerrar el fichero, y esto lo hacemos con el comando:

 

close FILEHANDLE;

 

 

 

DEVOLVIENDO UNA PÁGINA QUE NO HEMOS GENERADO

 

En este apartado crearemos un libro de visitas sencillito. Durante el proceso de realización, haremos bastantes cosas de forma ordenada.

 

            Devolveremos una página html que nosotros no habremos creado en tiempo real. Esto se hace principalmente para demostrar la directiva de localización.

            Hay que verificar que todos los campos tienen una entrada, y que la direción de correo electrónico tiene un símbolo arroba (@).

            Editar un documento html con un script, insertando información en el medio.

 

Vamos a empezar creando un formulario que coja la siguiente información en las variables indicadas entre paréntesis.

 

            Nombre (nombre)

            email (email)

            comentarios (comentarios)

 

Colocamos un comentario en HTML que contenga la cadena  “INSERTAR AQUÍ” en el formulario donde vayamos a insertar las nuevas entradas.

 

<!–INSERT HERE–>

 

Este formulario llamará al script, guestbook.pl. El formulario guestbook.html será el siguiente:

 

<!DOCTYPE HTML PUBLIC “-//IETF//DTD HTML//EN”>

<html>

<!–Copyright 1996 Javier Pérez (jpereztv.es)–>

 

<head>

<title>Libro de visitas</title>

</head>

 

 

<body>

 

<h1>Firma el formulario</h1>

 

<P>Por favor, rellena todos los campos:</P>

 

<hr>

 

<form method=POST action=”http://www.ctv.es/cgi-bin/guestbook.pl”>

 

<P><input name=”nombre”><BR>

 

Nombre</P>

 

<P><input name=”email”><BR>

 

Dirección e-Mail [Debe tener una arroba @]</P>

 

<P><textarea rows=3 cols=70 name=”commentarios”></textarea><BR>

 

Comentarios:</P>

 

<P><input type=”submit” value=”Añadir tus comentarios”><BR>

 

<input type=”reset” value=”Borrar formulario”></P>

 

</form>

 

<HR>

 

<!–INSERT HERE–>

 

<P>Última modificación 21/9/96

 

</body>

 

</html>

 

Como se puede ver, los nombres serán añadidos al final del formulario.

 

Estos son los pasos para realizar nuestra tarea:

 

1.   Verificar la entrada, devolviendo un mensaje de error si hay un problema.

2.   Uraremos un bloqueo de archivo denominado ‘del pobre’. Creando un nuevo archivo denominado. Si este archivo existe el programa se detendrá durante un segundo y lo volverá a intentar.

3.   Abriremos y leeremos el archivo del libro de firmas actual: guestbook.html.

4.   Borraremos este archivo, y escribiremos un nuevo fichero que consistirá en la antigua información con toda la nueva en el sitio apropiado. Usaremos el comentario <!–INSERTAR AQUÍ–> como marcador donde añadir entradas.

 

            Nota: Puede ser deseable hacer una copia de seguridad del archivo, antes de borrarlo. No lo hacemos en este ejemplo, pero lo recomiendo en cualquier desarrollo. Se podrá hacer por cualquier método, incluido una llamada al sistema para copiar (cp).

 

5.   Retornar una directiva de lugar apuntando a una página de agradecimiento que ya hayamos preparado.

6.   Desbloquear borrando el archivo .guestlock.

 

Obviamente, este no es el mejor libro de visitas, pero servirá como un buen ejemplo, y es fácilmente ampliable.

 

Empecemos:

 

1.   Como es habitual nuestro script deberá empezar con una llamada al Perl, los comentarios apropiados, y una llamada a la rutina ReadParse. No lo detallaremos aquí, ya que se hizo anteriormente.

2.   Para empezar debemos verificar los datos de entrada. Nos queremos asegurar de que no hay campos en blanco, y de que la dirección e-Mail contiene una arroba (@). Para ventaja nuestra, las cadenas nos vienen dadas sin los espacios en blanco, justo igual que los navegadores cuando presentan páginas html. Esto significa que solo debemos comprobar que ninguna de las cadenas es igual a la cadena vacía, “”. En perl, como en muchos lenguajes, las cadenas se comparan de manera diferente a los números. Usaremos ‘eq’ para comprobar la igualdad de cadenas, y el signo ‘==’ para comprobar la igualdad de números. Usearemos ‘ne’ y ‘!=’ respectivamente para las comparaciones ‘no igual’.

 

Podemos hacerlo de la siguiente manera:

 

            if ($in{‘nombre’} eq “”) {

                               # Hay una cadena vacía, devolver mensaje de error

 

                               # salimos al final del script ahora, tenemos un error

                                exit;

                }

 

            Como podrás adivinar, Ahora no ponemos el código para devolver el mensaje de error, en el ejemplo de arriba. Aún así muestra lo facil que es comprobar que un campo está vacío.

Nota: Los paréntesis () y las llaves ({}) son importantes. Los paréntesis contienen lo que se está comprobando como verdadero o falso, y las llaves contienen las sentencias a ejecutar si es verdadero.

            Como vamos a realizar varias comprobaciones de cadenas vacías, y vamos a devolver el mismo error para todas ellas, podemos incluirlas en la misma sentencia if-then uniéndolas por OR logicos ||, en perl. El OR significa que alguna de las comprobaciones tienen que ser verdad, para que toda la sentencia sea cierta.

 

            Un ejemplo de comprobar si tres variables no están vacias podría ser este:

 

            if (($in{‘nombre’} eq “”) || ($in{‘email’} eq “”) || ($in{‘comentarios’} eq “”)) {

                               # algun campo está en blanco, devolver mensaje de error

 

                               # salimos al final del script ahora, tenemos un error

                               exit;

            }

 

            De nuevo he omitido, el  codigo del mensaje de error, ya que es sencillo construir un pequeña tabla con los codigos para devolver un código de error útil para el usuario. El código que he elegido para hacerlo aparece más tarde en el código final de guestbook.pl.

 

            La última verificación que necesitamos realizar es comprobar que la dirección eMail del usuario contiene un signo (@). Lo realizaremos fácilmente con las potentes expresiones regulares que el perl proporciona.

Una expresion regular sencilla para chequear esto sería /\w*@\w*/. Simplemente describe una situación donde una palabra aparece delante y detras de un símbolo @. Las expresiones regulares son una herramienta extremadamente potente, pero no entran dentro del dominio de esta lección.

 

            Para llevar a cabo la comprobación de que la expresión se cumple, simplemente comparamos. Funcionará como las comparaciones normales, pero usando el símbolo ‘=~’ para igualdad y el símbolo ‘!~’ para desigualdad. La expresión regular podría codificarse así:

 

              if ($in{‘email’} !~ /\w*@\w*/) {

                               # La expresión regular no es igual, devolver mensaje de error.

                }

 

            De nuevo, he omitido el mensaje de error para este código.

 

 

            3. Ahora comprobaremos la existencia de nuestro archivo de bloqueo. Si existe, nos detendremos por un segundo para un segundo intento. Si no existe deberemos crearlo, bloqueando de este modo el archivo. Para hacerlo necesitaremos un bucle continuo sobre una sentencia if-then-else que compruebe la existencia del archivo, y que terminará cuando hayamos añadido la entrada.

 

            Para el bucle en perl usaremos la sentencia while. Esta sentencia ejecuta repetidamente lo que está entre llaves hasta que la condición entre paréntesis es falsa. Un ejemplo para clarificarlo:

 

            while (condición) {

                               # Codigo a ejecutar mientras la condición sea cierta

                }

 

            Las condiciones son del mismo tipo de las que pondríamos en una sentencia

if-then. Vamos a usar de hecho una sentencia if-then con la clausula else en este bucle. Como ya sabemos las sentencias if-then-else ejecuta el then cuando la condición es cierta y el else cuando es falsa.

 

           

 

if (condición) {

                               # Código a ejecutar si la condición es CIERTA

                }

                else {

                               # Código a ejecutar si la condición es FALSA

                }

 

            Para comprobar la exiastencia de un archivo, usaremos el operador ‘-e’ del nombre del archivo. No se debe olvidar que el archivo de bloqueo debe estar en un lugar donde el httpd pueda escribir. En este caso lo colocaremos en el directorio users/jperez/www/tutor/. Si el fichero existe, nos dentendremos durante un segundo, y lo volveremos a intentar. El lenguaje perl tiene un comando de espera con la forma:

 

            sleep(# segundos).

 

Aquí tenemos como debemos realizar el chequeo, con el comando de espera. Todo ello queda incluido en un bucle while que comprueba que la variable quitar vale 1 antes de salir.

 

            $quitar = 0;

                while ($quitar != 1) {

                               if (-e “/users/jperez/www/tutor/.guestlock”) {

                                               # El archivo existe. Esperaremos un segundo

                                               sleep(1);

                               }

                               else {

                                               # El archivo no existe, haremos el trabajo para añadir la entrada.

                                               # Aquí deberemos dar a $quitar valor 1.

                               }

                }

 

            Para crear el archivo de bloqueo, simplemente deberemos abrir y cerrar el archivo. Así crearemos un archivo de 0 bytes de longitud, que servirá en nuestro chequeo, y bloqueará otros procesos que la gente pueda realizar en ese momento, hasta que este haya acabado. Para ello esaremos un trozo de código que aquí se detalla:    

 

            open(LOCK,”>/users/jperez/www/tutor//.guestlock”);

                close LOCK;

 

            4. Ahora tnemos un bloqueador del fichero. Ahora deberemos abrir y leer todos los contenisdos del guestbook.html. Esto se hace fácilmente abriendo el archivo para lectura. En el comando open antes empleado usaremos el símbolo (<) donde antes usamos (>>). El resultado será:

 

open(FILEHANDLE,”<nombrefichero”);

 

            El código será::

 

            open(GB,”<users/jperez/www/tutor/guestbook.html”);

 

            Leer del fichero es igual de fácil. Perl perimite usar el FILEHANDLE entre los símbolos (<>) para sustituir a la próxima línea del fichero.

 

            Así pues, podemos poner algo como esto:

 

            $linea = <GB>;

 

            Así leeremos una línea del fichero apuntado por GB, y avanzará el puntero a la siguiente línea automáticamente. Esto significa que la siguiente sentencia como esta leerá la siguiente línea. Podremos leer el archivo entero en un bucle, saliendo solo cuando no quede nada más,. Pero hay un modo más fácil, podemos usar un vector. Un vector es una variable, con mucha información que puede ser accedida individualmente. Ya hemos usado arrays asociativos en la construcción $in{‘variable’}. Recuerda que se tiene el elemento etiquetado como variable en el vector asociativo $in. Usaremos un vector ordenado numericamente. Esto significa que la información es almacenada en el vector en el orden en la que la ponemos, y podemos referenciar el elemento n-ésimo elemento añadido, donde n es un número cualquiera. Estos vectores son referenciados con el signo @, y se usan de manera similar a los vectores asociativos cuando queremos recuperar un solo elemento de él. El siguiente ejemplo lo explica:

 

            @pepe                   # Este es el vector pepe entero, con todos sus elementos

                $pepe[4]              # El el quinto elemento del vector pepe. Notar que

# se empieza a contar desde 0, y que por tanto:

                $pepe[0]              # es el primer elemento del vector. (Igual que en el lenguaje C)

 

            Perl proporciona un modo rápido para llenar un vector con el contenido de un archivo. Podemos usar la sentencia @vector = <FILEHANDLE>. Se leerá cada linea del fichero apuntado por FILEHANDLE, y seran colocados secuencialmente en el vector. Para leer el libro de visitas entero, deberemos usar:

 

            @lineas = <GB>;

 

            No debemos olvidarnos de cerrar el fichero:

 

close GB;

 

5.   Vaciar el archivo es muy sencillo. Simplemente debemos reabrirlo para escritura sin añadir. Esto significa usar un ‘>’ en vez de dos. Una vez hecho esto debemos imprimir cada línea del fichero, sustituyendo lo que el usuario escribió justo delante del marcador.

 

Para llevar a cabo la sustitución usaremos la función s/oldpattern/newpattern/, que reemplaza el patrón antiguo con el nuevo. La buena noticia es que modemos usar metacaracteres como \n para reemplazar varias líneas. Para que esto afecte a una variable que contiene una cadena usaremos un operador. La sentencia quedará así:

 

            $linea =~ s/oldpattern/newpattern/

 

     Realmente haremos una sustitución en los comentarios, para convertir las nuevas líneas en comandos <BR>, de manera que las líneas queden tal y como el visitante las escribió. Para hacerlo pondremos:

 

     $in{‘comentarios’} =~ s/\n/<BR>\n/go;

 

            La g que va detrás del patrón significa que se hará esto para cada línea en la variable, la o significa compilar esta expresión regular de manea que concuerde más rápido. Es bueno hacerlo.

 

            Para realizar la sustitución y añadir los comentarios del usuario para cada línea del vector, usaremos uin bucle foreach. Esto significa para cada elemento de la lista, coloca el elemento en la variable_de_bucle, y ejecuta las sentencias que están entre las llaves ({}). Esto sería:

 

            foreach $variable_de_bucle (lista) {

                               #cosas a hacer

                }

 

            Queremos insertar el nombre de la persona con su dirección e-Mail entre paréntesis, seguido de sus comentarios. Esto lo incluiremos en un parrafo HTML, seguido de un comando <HR>. No tenemos que olvidarnos de incluir un nuevo marcador <!–INSERTAR AQUÍ>. Lo pondremos primero así las nuevas entradas serán añadidas al principio de la página. Mi codigo será: 

 

            open(GB,”>/users/jperez/www/tutor/guestbook.html”);

                foreach $linea (@lineas) {

$linea=~ s/<!–INSERTAR AQUÍ–>/<!–INSERTAR AQUÍ–>\n<P>Nombre: $in{‘nombre’}($in{‘email’})<BR>\nComentarios:<BR>\n$in{‘comentarios’}<\/P>\n<HR>\n/o;

                               print GB $linea;

                }

                close GB;

 

            6. Ahora que hemos reescrito el archivo, debemos desbloquearlo, borrando el archivo .guestlock. Es sencillo, ya que perl tiene un buncion borrar:

 

            unlink(lista of ficheros);

 

            Así tendremos que poner:

 

            unlink(“/users/jperez/www/tutor/.guestlock”);

 

            7. El ultimo paso es apuntar a mi página de agradecimiento. Y poner la variable $quitar = 1;. HTTP nos permite hacer esto fácilmente con la directiva de localización. Simplemente devolvemos la línea:

 

            Location: url

 

 

                Instead of:

 

                Content-Type: text/html

 

 

    

 

Esto lo resolvemos con la siguiente sentencia print:

 

            print “Location: http://www.ctv.es/users/jperez/www/tutor/agradecimiento.html\n\n”;

 

            La nueva segunda línea es significativa. Los códigos de retorno Mime, de los cuales la localización es uno de ellos, requiere dos nuevas lineas siguiéndolo para trazar el fin del código de retorno. Olvidar la nueva segunda línea provocará un daño irrecuperable.

 

            Poner la variable ‘quitar’ a 1 es trivial:

 

            $quitar = 1;

 

El código final para el archivo guestbook.pl será:

 

#!/usr/local/bin/perl

# Por Javier Pérez Delgado (jperez@ctv.es) 6-9-96

#

# guestbook.pl

#             Procesa las entradas a un libro de firmas

#

#Usaremos la librería cgi-lib.pl para manejar la entrada

 

push(@INC,”/p/www/httpd/cgi-bin”);

require(“cgi-lib.pl”);

 

&ReadParse;

 

if (($in{‘nombre’} eq “”) || ($in{‘email’} eq “”) || ($in{‘comentarios’} eq “”)) {

  # algún campo en blanco, devolver mensaje de error

  print &PrintHeader;

  print “<HTML>\n”;

  print “<HEAD>\n”;

  print “<TITLE>Mensaje de error</TITLE>\n”;

  print “</HEAD>\n”;

  print “<BODY>\n”;

  print “\n”;

  print “<H1>Ha ocurrido un error</H1>\n”;

  print “\n”;

  print “<P>No has completado todos los campos Por favor vuelve a intentarlo.</P>\n”;

  print “</BODY>\n”;

  print “</HTML>\n”;

 

  # llamda para salir del script. Tenemos un error

  exit;

  }

 

if ($in{‘email’} !~ /\w*@\w*/) {

  # No es igual a la expresión regular. Enviar mensaje de error

  print &PrintHeader;

  print “<HTML>\n”;

  print “<HEAD>\n”;

  print “<TITLE>Mensaje de error</TITLE>\n”;

  print “</HEAD>\n”;

  print “<BODY>\n”;

  print “\n”;

  print “<H1>Ha ocurrido un error</H1>\n”;

  print “\n”;

  print “<P>Tu dirección email no contiene una @.</P>\n”;

  print “</BODY>\n”;

  print “</HTML>\n”;

 

  # llamada para salir ahora del script, tenemos un error

  exit;

  }

 

$quitar = 0;

while ($quitar != 1) {

  if (-e “/users/jperez/www/tutor/.guestlock”) {

    # El fichero existe, esperamos un momento

    sleep(1);

    }

  else {

 

    # Creamos el fichero de bloqueo, de manera que el libro de firmas queda bloqueado.

    open(LOCK,”>/users/jperez/www/tutor/.guestlock”);

    close LOCK;

 

    # Abrimos y leemos el antiguo libo de firmas

    # Nota: Crear una copia de seguridad no sería una mala idea …

    open(GB,”</users/jperez/www/tutor/guestbook.html”);

    @lineas = <GB>;

    close GB;

 

    # Preparamos los comentarios para  html

    $in{‘comentarios’} =~ s/\n/<BR>\n/go;

 

    # Vaciamos el libro de firmas antiguo, y lo volvemos a imprimir, añadiendo la nueva entrada

    open(GB,”>/users/jperez/www/tutor/guestbook.html”);

    foreach $linea (@lineas) {

$linea =~ s/<!–INSERTAR AQUÍ–>/<!–INSERTAR AQUÍ–>\n<P>Nombre:                $in{‘nombre’}($in{‘email’})<BR>\nComentarios:<BR>\n$in{‘comentarios’}<\/P>\n<HR>\n/o;

      print GB $linea;

      }

    close GB;

 

    # desbloquear el archivo

    unlink(“/users/jperez/www/tutor/.guestlock”);

 

    # Devolver el nuevo libro de firmas, y poner $quitar = 1

    print “Location: http://www.ctv.es/users/jperez/www/tutor/thanks.html\n\n”;

    $quitar = 1;

    }

  }

 

 

Nota sobre el bloqueo de archivos:

 

El mecanismo de bloqueo de archivos usado arriba, no es perfecto ya que requiere varios pasos para bloquear un archivo. Por ello es posible obtener un bloqueo o desbloqueo falso, aunque es altamanete improbable. Una implementación mejot usara el sistema de ficheros nativo bloqueando, vinculando, o algún otro método atómico. Desconozco si perl simula el bloqueo en sistemas que no implementan el bloqueo de forma nativa.

Un método usando vínculos sin comentar sería:

 

$quit = 0;

while ($quit != 1) {

                /users/jperez/www/tutor/guestbook.html

 if (link(/users/jperez/www/tutor/guestbook.html,/users/jperez/www/tutor/.guestlock)) {

                               # Archivo bloqueado, nos detenemos un momento

                               sleep(1);

                }

                 else {

                               # Tenemos un bloqueo, hacemos lo que tengamos que hacer

                               unlink(“/users/jperez/www/tutor/.guestlock”);

                }

}

 

 

SCRIPTS CGI QUE ENVÍAN CORREO

 

En este apartado crearemos un script CGI que envíe correo, y devolvera una página que indique que el correo ha sido enviado. Sin embargo, los conceptos serán bastante geneales para permitir que el script pueda ser adaptado a cualquier proyecto donde sea necesario enviár un correo desde un script.

También veremos brevemente como examinar las areas de texto línea por línea.

 

Como siempre, cuando empezamos, necesitaremos un formulario. 

 

<!DOCTYPE HTML PUBLIC “-//IETF//DTD HTML//EN”>

<html>

<!–Javier Pérez Delgado (jperez@ctv.es)   All Rights Reserved –>

<head>

<link rev=made href=”mailto:jperez@ctv.es”>

<title>Enviar correo desde un script CGI</title>

</head>

<body>

<P>Este formulario encía correo a la dirección de la persona mostrada.</P>

<form method=”POST” action=”http://www.ctv.es/USERS/cgi-bin/mail.pl”>

<P>Tu dirección:  <INPUT NAME=”De” SIZE=36></P>

<P>Tu URL:  <INPUT NAME=”xurl” SIZE=36></P>

<P>Subject:  <INPUT NAME=”subject” SIZE=40></P>

<P>Mensaje:</P>

<P><TEXTAREA name=”body” rows=10 cols=60></TEXTAREA></P>

<P><input type=”submit” value=”Enviar mansaje”>

<input type=”reset” value=”Borrar todo”></P>

</FORM>

 

</P>

</body>

</html>

 

Ahora pasaremos al script. Como siempre deberá empezar con una llamada al perl, algunos comentarios y una llamada a la rutina ReadParse, en la librería cgi-lib.pl. También haremos verificaciones de que el cuerpo del mensaje no está vacío, y de que la dirección contiene una arroba (@). Ya sabemos hacerlo, por que ya lo realizamos en el anterior apartado. Otra validación será para comprobar que de pasan argumentos, lo pondremos porque la gente tiene tendencia a llamar a los scripts de correo sin argumentos (eg: no desde un formulario), no entiendo por qué.

Para comprobar que no hay argumentos, simplemente examinaremos si el vector %in que la librería cgi-lib.pl nos devuelve tiene alguna tecla. Recuerda, que ningún argumento, significa que no nos han pasado nada, sin embargo, alguien puede pasar los argumentos en blanco, de aquí las dos comprobaciones. La manera de comprobar los valores de las teclas del vector es usar la función keys(). Esta función espera un array asociativo como argumento. Simplemante comprobaremos que devuelve algo, imprimiendo un mensaje de error si no hay teclas. El código pude ser este:

 

if (!keys(%in)) {

  # niguna tecla ha sido pasada, imprimir mensaje de error, y si es apropiado salir.

  }

 

Date cuenta de que el signo ‘!’ al principio de la llamada a keys(), significa ‘no’ o negación. Quiere decir que si keys() no devuelve nada (falso), sera negado para que sea cierto y el if se ejecute.

Ahora que hemos validado las entradas, y se pueden realizar más comprobaciones, necesitaremos enviar la carta. Para ello necesitaremos un programa que acepte una carta del stdin. Luego usaremos sendmail en el ejemplo. Si no estás en un entorno Unix, necesitarás otro programa apropiado para hacerlo. Como no conozco otro tipo de sistemas, no puedo hacer ninguna recomendación.

 

Usar este programa será similar a escribir en un fichero. Necesitamos abrir el programa para aceptar la entrada, escribir al FILEHANDLE. Abrir un programa que espera entradas por el stdin es bastante fácil em Perl. Además, es muy fácil pasar el argumento de la línea de comandos. En este ejemplo, abriremos sendmail, y diremos que busque en la carta la dirección de destino.

 

open(MAIL,”|/usr/lib/sendmail -t”);

 

Como se puede observar, es igual que una llamada a fichero, pero los símbolos ‘>’ o ‘<’ han sido sustituidos por un ‘|’ (tubería). Esto indica que lo que hay detrás de la tubería es un ejecutable, y que lo que imprimamos en este FILEHANDLE se deberá pasar como entrada a el programa ejecutable.

 

Nota: No he comprobado si la operación anterior terminó con éxito, pero deberíamos hacerlo. La manera más sencilla de hacerlo, es apoyarnos en que el comando open devuelve true cuando ha tenido éxito. Solo debemos hacer un OR entre el comando open y otro comando conociendo que ese comando será ejecutado si el open falla. El ejemplo de abajo realiza un OR con el comando die. Este comando imprime un mensaje de error y sale del programa.

 

open(MAIL,”|/usr/lib/sendmail -t”) || die “La llamada a sendmail ha fallado”;

 

El imprimir la carta funciona como experamos. Recuerda, sin embargo, que estamos imprimiendo un trozo de e-Mail, por lo que deberemos poner las cabeceras adecuadas, un línea en blanco y el cuerpo del mensage. Puedes echar un vistazo a un e-Mail que hayas recibido y comprobar las cabeceras. Decribiré abajo lo más importante. Los comentarios serán de ayuda.

 

# Esta es la línea ‘para’. Si tenemos el nombre y la dirección, escribiremos

# primero el nombre y lurgo la dirección entre <>

print MAIL “To:  $in{‘De’}\n”;

 

# Esta es la linea ‘De’. Pondremos el mismo nombre en la línea ‘De’ y en la ‘para’

# Recuerda que no hay seguridad comprobando el email, por ello estas líneas

# pueden ser falsificadas. NO FALSIFIQUES EL EMAIL, no es divertido y en algunos lugares

#  es un delito.

print MAIL “From: $in{De}\n”;

 

# Esta es la linea ‘contestar a’. Esta línea se incluye debido a que algunos programas

# son bastante tonotos y no siempre responden a la línea ‘De’. 

# Al menos respetarán esta línea.

print MAIL “Reply-To: $in{De}\n”;

 

# Lo siguiente son cabeceras X. Son creadas por el usuario y pueden contener

# todo lo que desees. Incluyo  una linea de descripción

# tambien he escrito las líneas REMOTE_HOST, REMOTE_ADDR, y REMOTE_USER para

# ayudar al seguimiento (traking) de la carta. 

# Solo escribo la línea X-URL si el usuario ha dado una url. Este tipo

# de comprobación probablemente hecha también en el X-Remote-Host y X-Remote-User

 

print MAIL “X-mailer: Mail.pl, a cgi-bin script at http://www.ctv.es/users/jperez/www/tutor/ /index.html\n”;

print MAIL “X-Remote-Host: $ENV{‘REMOTE_HOST’} ($ENV{‘REMOTE_ADDR’})\n”;

print MAIL “X-Remote-User: $ENV{‘REMOTE_USER’}\n”;

print MAIL “X-disclaimer: La línea De: puede estar falsificada “;

print MAIL “No confiar en un 100% sobre la integridad de este mail. “;

print MAIL “No somos responsables de este correo de ninguna manera\n”;

if ($in{xurl} ne “”) {

  print MAIL “X-URL: $in{xurl}\n”;

  }

 

# Finalmente escribimosly we print the famous subject line.  I have appended a string

#  identifying this as WWW generated email, this is far from a requirement.

#  Notice the second new line.  This is the blank line that will separate the

#  headers from the body.  All mail must have this line.

print MAIL “Subject: $in{subject} (WWW generated email)\n\n”;

 

# Now we are going to print the body.  Because it was input into a TEXTAREA

#  field, it has new lines after each line.  We can just print it like any other

#  field, knowing that the new lines will expand it properly.

print MAIL $in{‘body’};

 

Solo quedan dos cosas. Primero debemos cerrar la conexión con sendmail. Segundo debemos imprimir una página enseñando al usuario la carta que envía.

 

Para cerrar la conexión, usaremos el comando close, justo como en cualquier otro manejador:

 

close(MAIL);

 

Imprimir la respuesta no es diferente a las otras páginas generadas dinámicamente que ya hemos creado anteriormente. Deberemnos dividir el campo TEXTAREA en líneas colocando un <BR> al final de cada una. Esto se hace principalmente para demostrar como se divide un campo de texto. Recuerda sin embargo que html no respeta las nuevas líneas. Con ello se consigue el poder hacer los parrafos de manera más clara, pero el beneficio de hacerlo es cuestionable.

 

El valor del campo body (cuerpo del mensaje), no es más que un conjunto de frases separadas con un retorno de carro. Podemos usar la función split() para separarlas. Esta función necesita dos parámetros: la cadena o carácter para separar y la variable a separar, y retorna un array con los elementos separados. Usaremos split en un bucle foreach: 

 

foreach $l (split(‘\n’,$in{‘body’})) {

  print “$l<BR>\n”;

  }

 

Como se puede ver separa la variable $in{‘body’} en partes cada nueva línea, y la imprime seguida de un <BR>.

 

ESCRIBIENDO SCRIPTS CGI SEGUROS

 

Siempre que un programa interactúa con un cliente por red, existe la posibilidad de que el cliente atcque al programa para conseguir un acceso. El script más inocente puede ser muy peligroso para la integridad de tu sistema.

Teniendo eso en cuenta, me gustaría comentar unos pequeños consejos para conseguir que tu programa no sea atacado.

 

 

 

Cuidado con la sentencia eval

 

            Lenguajes como el Perl y el Bourne shell tienen un comando eval que permiten construir una cadena y dejar al intérprete que la ejecute.

Esto puede resultar peligroso. Observa la siguiente sentencia en Bourne shell:

 

             eval `echo $QUERY_STRING | awk ‘BEGIN{RS=”&”} {printf “QS_%s\n”,$1}’ `

 

     Esta sentencia coge la cadena de entrada y la convierte en un conjunto de comandos de declaración de variables. Desafortunadamente este script puede ser atacado mandando una cadena de entrada que empiece por ‘;’.

 

No confíes en que el cliente haga algo

 

            Un cliente correcto evitará todos los caracteres que tienen un significado especial para el Bourne Shell en una cadena de entrada, y que hega que tu script malinterprete los caracteres. Un cliente malintencionado usará esos caracteres para confundir a tu script y ganar acceso desautorizado.

 

Cuidado con popen() y system()

 

Si usas datos del cliente para construir una llamada a popen() o system(), aseguraté de poner un backslash delante de cada carácter que tenga un significado especial para el Bourne Shell antes de llamar a la función. Lo podrás hacer con una sencilla función en C.

 

Si quieres más información sobre la seguridad en el WWW, consulta WWW Security FAQ en http://www-genome.wi.mit.edu/WWW/faqs/www-security-faq.html.

   

Samsung lanza novedosos celulares "ecológicos"


Noticia: http://www.emol.com/noticias/tecnologia/detalle/detallenoticias.asp?idnoticia=309994

Fecha publicación: martes 24 de junio 2008

Samsung lanza novedosos celulares “ecológicos”

Dos nuevos modelos de teléfonos móviles presentó el fabricante coreano Samsung, en el marco de la celebración del World IT Show en Seúl, feria donde se introdujeron el W510 y el F268, que se caracterizan por ser ecológicos, al estar basados en componentes ecológicos extraídos del maíz.

Desarrollar dispositivos más naturales, amigables con el medio ambiente y eficientes desde el punto de vista energético, usando materiales renovables, es la nueva apuesta de Samsung con estos dos nuevos celulares, tendencia que esperan que se instale en el mercado de los móviles, y que ya está presente en otros como el de los computadores portátiles.

Comentario:

Esta tecnología la encuentro muy interesante ya que aporta una nueva visión de la implementación de tecnologías sin necesidad de generar grandes residuos.

Es importante que la industria se empiece a enfocar en el cuidado y preservación de los recursos naturales de nuestro planeta, ya que la utilización de materiales renovables hace que estos productos se degraden con mayor rapidez que otro que utiliza materiales no renovables.

Los datos confidenciales de 500.000 alemanes circularon en Internet por error

Los datos confidenciales de unos 500.000 ciudadanos de Alemania han estado accesibles durante varios meses en internet tras un error de configuración sobre la contraseña de un programa de las autoridades, informó la cadena pública ARD.
La empresa HSH, encargada de asegurar estos datos informáticos, dejó colgados en una página web el nombre de usuario y la contraseña previstos para un programa que daba acceso a estos datos, informó la cadena.
Los nombres, direcciones, fotografías y confesión religiosa de los ciudadanos de una quincena de ciudades y de 425 municipios alemanes estuvieron accesibles en la web desde el 15 de marzo hasta el pasado viernes.

fuente:http://www.mundoenlinea.cl/noticia.php?noticia_id=13407&categoria_id=54

fecha:25-06-2008

comentario personal: esto se parece al caso sucedido en chile unos meses atras  solo con la diferencia que esa vez los datos fueron hackeados  en el caso aleman la empresa que debia resguardar los datos cometio un error.

los dos casos son muy graves y perjudican a mucha gente pues son datos personales y como tales deberian estar bien resguardados, me extraña que en unpais tan desarrollado como alemania pueda suceder esto.

El primer edificio "dinámico"

Noticia: http://news.bbc.co.uk/hi/spanish/misc/newsid_7473000/7473262.stm
Fecha de publicación: miercoles 25 de Junio
El primer edificio “dinámico”

La construcción del primer rascacielos transformable está por comenzar en Dubai.

La “Torre Dinámica” -que podría estar lista en 2010- contará con 80 pisos que podrán rotar sobre su eje para modificar la forma del edificio de manera independiente.

“Es el primer edificio que se mueve y cambia su forma”, dijo el arquitecto y creador del concepto, David Fisher.

El innovador edificio de apartamentos -que tendrá 420 metros de alto- girará 360 grados alrededor de una columna central, luego de recibir un mando verbal.

El edificio contará con 79 turbinas gigantes que se colocarán en cada piso.

Además, será autosuficiente, debido a que las turbinas producirán la energía necesaria para todo el complejo

.Imagen 3D de las transformaciones en la Torre Dinámica

El rascacieclos cambiará constantemente de forma, por lo que nunca se vera igual.
Comentario:
Este proyecto es innovador y llamativo, pero sera viable crear un edificio que este en constate movimiento, la gente que viva en este edificio no quedara mariada?, aunque lo interesante de este proyecto ademas de la dinamica, es su forma de obtener energia electrica y su facil construccion, quizas mas adelante esta sea la manera de construir edificios (no estaticos).
Para ver mas información hacer click en este enlace (video):
http://www.bbc.co.uk/mediaselector/check/spanish/av/culture/2008/06/?redirect=080625_rascacielos.shtml&news=1&host=www&nbram=1&bbram=1&nbwm=1&bbwm=1&lang=es

Windows XP con soporte hasta el 2014

Windows XP con soporte hasta el 2014

de ZENCOVICH CARLOS FRANCISCO – miércoles, 25 de junio de 2008, 23:38

Microsoft ha confirmado que seguirá dando soporte a Windows XP hasta el 2014. La poca demanda que tiene Windows Vista en el mercado, y el hecho de que los clientes corporativos tampoco se actualicen a Vista, ha hecho que Microsoft tome esta decisión.
Escogí esta noticia porque creo que pese a las actualizaciones de windows vista (ya van en sp1) y a sus múltiples versiones, este sistema operativo aún no conevence al público en general, lo que una vez más confirma que la política de microsoft de sacar software en tpo. record no es la mejor, ya que siempre están parchando sus programas con grandes actualizaciones,lo que a la larga ha ido probocando desconfianza en el público haciendo que se aferre a windows xp o definitivamente cambiandose a la plataforma linux, lo que se puede verificar viendo cuan masivo se ha vuelto ubuntu y sus derivados.Todo esto provoca un gasto enorme en microsoft porque por más esfuerzos en sacar de la oscuridad a vista la gente sigue prefiriendo el viejo y querido xp.
Nota: Cabe destacar que por tal vez por esta razón microsoft anuncio que sacará para el 2010 windows 7
Fuente: www.barrapunto.com

Spray regenerador de heridas

skingun_02.jpgEl Instituto de Medicina Regenerativa de las Fuerzas Armadas de Estados Unidos está invirtiendo USD$250 millones en un proyecto que busca desarrollar un dispositivo para curar heridas instantáneamente.

Este spray funciona en base a células madre, las que al ser aplicadas sobre una herida permitirian su regeneración en horas.

Si bien esto está ideado para soldados en el campo de batalla, es algo que terminará beneficiando a toda la sociedad. Aunque al usar células madre para esto, habrá discusión para rato antes de que lo veamos en una ambulancia o botiquín de emergencia.

http://www.fayerwayer.com/2008/06/spray-regenerador-de-heridas/

Accesos a Dispositivos Independientes del Bus

Matthew Wilcox

matthew@wil.cx

Alan Cox

alan@redhat.com

Tabla de contenidos

1. Introducción …………………………………………………………………………………………………………………………………………..3

2. Bugs Conocidos Y Suposiciones……………………………………………………………………………………………………………….3

3. E/S Mapeada en Memoria……………………………………………………………………………………………………………………….3

3.1. Obteniendo Acceso al Dispositivo …………………………………………………………………………………………………..3

3.2. Accediendo al dispositivo……………………………………………………………………………………………………………….3

3.3. Funciones de Herencia ISA …………………………………………………………………………………………………………….4

4. Accesos al Espacio de Puerto …………………………………………………………………………………………………………………..4

4.1. Espacio de Puerto Explicado …………………………………………………………………………………………………………..4

4.2. Accediendo al Espacio de Puerto …………………………………………………………………………………………………….4

5. Funciones Públicas Suministradas …………………………………………………………………………………………………………..4

6. Sobre la traducción …………………………………………………………………………………………………………………………………4

Accesos a Dispositivos Independientes del Bus

por Matthew Wilcox por Alan Cox

Copyright © 2001 Matthew Wilcox

Esta documentación es software libre; puedes redistrubuirla y/o modificarla bajo los términos de la GNU General Public License tal como ha sido publicada por la Free Software Foundation; por la versión 2 de la licencia, o (a tu elección) por cualquier versión posterior.

Este programa es distribuido con la esperanza de que sea útil, pero SIN NINGUNA GARANTÍA; sin incluso la garantía implicada de COMER- CIABILIDAD o ADECUACCIÓN PARA UN PROPOSITO PARTICULAR. Para más detalles refiérase a la GNU General Public License.

Debería de haber recibido una copia de la GNU General Public License con este programa; si no es así, escriba a la Free Software Foundation, Inc.,

59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

Para más detalles véase el archivo COPYING en la distribución fuente de Linux.

1. Introducción

Linux suministra una API que abstrae la realización de E/S a través de todos los buses y dispositivos, permitiendo a

los controladores de dispositivos ser escritos independientemente del tipo de bus.

2. Bugs Conocidos Y Suposiciones

Ninguno.

3. E/S Mapeada en Memoria

3.1. Obteniendo Acceso al Dispositivo

La forma más ampliamente soportada de E/S es la E/S por mapeo de memoria. Esto es, una parte del espacio de direcciones  de  la  CPU  es  interpretada  no  como  un  acceso  a  la  memoria,  sino  como  un  acceso  a  un  dispositivo. Algunas arquitecturas definen dispositivos para estar en una dirección fija, pero la mayoría tiene algún método para descubrir los dispositivos. El bus PCI es un buen ejemplo de este esquema. Este documento no cubre cómo recibir una dirección, sino que asume que ya estás empezando en una. Las direcciones físicas son del tipo unsigned long.

Estas direcciones no deberían de ser usadas directamente. En vez de esto, para obtener una dirección utilizable para pasar a las funciones de acceso descritas posteriormente, debería de llamar a ioremap. Te será devuelta una dirección utilizable para el acceso del dispositivo.

Después de que hayas finalizado de usar el dispositivo (esto es, en la rutina de salida en tu módulo), llama a ioun- map para retornar el espacio de direcciones al núcleo. La mayoría de las arquitecturas asignan un nuevo espacio de direcciones cada vez que llamas y haces ioremap, y pueden acabarse a menos que llames a iounmap.

3.2. Accediendo al dispositivo

La parte de la interface más usada por los controladores es la lectura y escritura de los registros mapeados en memoria

del dispositivo. Linux suministra interfaces para leer y escribir cantidades de 8-bits, 16-bits, 32-bits y 64-bits. Debido

a un accidente histórico estos son llamados accesos byte, word, long y quad. Ambos accesos de lectura y escritura son soportados; no hay soporte pre-producido en este momento.

Las funciones tienen los nombres readb, readw, readl, readq, writeb, writew, writel y writeq.

A algunos dispositivos (como los framebuffers) les gustaría usar transferencias más grandes de los 8 bytes cada vez. Para estos dispositivos, son suministradas las funciones memcpy_toio, memcpy_fromio y memset_io. No utilices memset o memcpy en direcciones de E/S; no está garantizado que copien los datos en orden.

Las funciones de lectura y escritura están definidas para ser ordenadas. Esto es, al compilador no le está permitido reordenar la secuencia de E/S. Cuando el órden puede ser compilado de forma optimizada, puedes usar     readb y amigos para indicar un órden relajado. Usa esto con cuidado. La rmb suministra una barrera de lectura de memoria.

La wmb suministra una barrera de escritura de memoria.

Mientras las funciones básicas son definidas para ser síncronas y ordenadas con respecto a las otras, los dispositivos que están en los buses quizás sean asíncronos. En particular, algunos autores han sido quemados por el hecho de que

las escrituras en el bus PCI son realizadas de forma asíncrona. El autor de un controlador debe de emitir una lectura

3

Accesos a Dispositivos Independientes del Bus

para el mismo dispositivo para asegurarse de que la escritura ha tenido lugar en los casos específicos que quiere el autor. Este tipo de propiedad no puede ser escondida por los escritores de los controladores en la API.

3.3. Funciones de Herencia ISA

En los núcleos viejos (2.2 y anteriores) el bus ISA podía leer o escribir con estas funciones y sin usar ioremap. Esto

ya no es verdad en Linux 2.4. Un conjunto de funciones equivalentes existen para el cambio de controladores con herencia fácil. Las funciones disponibles son prefijadas con ‘isa_’ y son isa_readb, isa_writeb, isa_readw, isa_writew, isa_readl, isa_writel, isa_memcpy_fromio y isa_memcpy_toio

Estas funciones no deberían de ser usadas en los nuevos controladores, y serán quitadas eventualmente.

4. Accesos al Espacio de Puerto

4.1. Espacio de Puerto Explicado

Otra forma de E/S comúnmente usada en el Espacio de Puerto. Esta es un rango de direcciones separados del espacio normal de direcciones de memoria. El acceso a estas direcciones no es generalmente tan rápido como los accesos a

las direcciones mapeadas en memoria, y por lo tanto tienen potencialmente un espacio de direcciones más pequeño.

A diferencia de la E/S mapeada en memoria, no se requiere preparación para acceder al espacio de puerto.

4.2. Accediendo al Espacio de Puerto

Los accesos a este espacio son suministrados a través de un conjunto de funciones que permiten accesos de 8-bits,

16-bits y 32-bits; también conocidos como byte, word y long. Estas funciones son inb, inw, inl, outb, outw y

outl.

Son suministradas algunas variantes para estas funciones. Algunos dispositivos requieren que los accesos a sus puertos

se realicen más despacio. Esta funcionalidad es suministrada añadiendo una _p al final de la función. También hay equivalentes a memcpy. Las funciones ins y outs copian bytes, words o longs al puerto dado.

5. Funciones Públicas Suministradas

6. Sobre la traducción

Este documento es la traducción de “Bus-Independent Device Accesses”, documento que acompaña al código del núcleo de Linux, versión 2.4.18.

Este  documento  ha  sido  traducido  por  Rubén  Melcón  <melkon@terra.es>;  y  es  publicado  por  TLDP-ES (http://es.tldp.org)

Versión de la tradución 0.04 ( Julio de 2002 ).

4

Accesos a Dispositivos Independientes del Bus

Si tienes comentarios sobre la traducción, ponte en contacto con Rubén Melcón <melkon@terra.es>

5

Escrito en Linux. 1 comentario

Arboles en ALGOL

Indice1. Base De Datos

2. Recursividad

3. Lista

4. Árboles binarios.

5. Variables Constantes

Estructura de datos

1. Base De Datos

Una base de datos de red esta formado por una colección de registros, los cuales están conectados entre sí por medio de enlaces.

Registro.- Es una colección de campos (atributos) Campos.- Contiene almacenado solamente un valor.

Enlace.- Asociación entre dos registros, así que podemos verla como una relación estrictamente binaria. Estructura de datos de red, abarca más que la estructura de árbol porque un nodo “hijo” en la estructura

de red puede tener más de un padre.

Diagramas de estructura de datos.

Es un esquema que representa el diseño de una base de datos de red. Este modelo se basa en representaciones entre registros por medio de ligas, existen relaciones en las que participan solo dos entidades(binarias) y relaciones en las que participan más de dos entidades (generales) ya sea con o sin atributo descriptivo en la relación.

La forma de diagramado consta de dos componentes básicos: Celdas: representan a los campos del registro.

Líneas: representan a los enlaces entre los registros.

su representación gráfica se basa en el acomodo de los campos de un  registro en un conjunto de celdas que se ligan con otro(s) registro(s)

Las estructuras de datos según la cardinalidad se representan en los siguientes casos:

Conceptos básicos.

Algoritmo.- Es un conjunto de reglas que permiten obtener un resultado determinado a partir de ciertas reglas definidas.

Algoritmo.- Es una secuencia finita de instrucciones, cada una de las cuales tiene un significado preciso

y puede ejecutarse con una cantidad finita de esfuerzo en un tiempo finito. Ha de tener las siguientes características: Legible, correcto, modular, eficiente, estructurado, no ambiguo y a ser posible se ha de desarrollar en el menor tiempo posible.

Características de un algoritmo de computador: Correcto

Legible eficiente

Diseño de algoritmos. Fases:

Diseño: se dan las especificaciones en lenguaje natural y se crea un primer modelo matemático apropiado. La solución en esta etapa es un algoritmo expresado de manera muy informal. Implementación: El programador convierte el algoritmo en código, siguiendo alguna de estas 3 metodologías.

* TOP-DOWN se alcanza el programa sustituyendo las palabras del palabras del pseudocódigo por secuencias de proposiciones cada vez mas detalladas, en un llamado refinamiento progresivo.

* BOTTON-UP parte de las herramientas mas primitivas hasta que se llega al programa.

Pruebas: Es un material que se pasa al programa para detectar posibles errores.Esto no quiere decir que el diseño no tenga errores, puede tenerlos para otros datos.

2. Recursividad

Definición.

Hablamos de recursividad, tanto en el ámbito informático como en el ámbito matemático, cuando definimos algo (un tipo de objetos, una propiedad o una operación) en función de sí mismo. La recursividad en programación es una herramienta sencilla, muy útil y potente.

Tipos.

Podemos distinguir dos tipos de recursividad:

Directa: Cuando un subprograma se llama a si mismo una o mas veces directamente. Indirecta: Cuando se definen una serie de subprogramas usándose unos a otros. Características.

Un algoritmo recursivo consta de una parte recursiva, otra iterativa o no recursiva y una condición de terminación. La parte recursiva y la condición de terminación siempre existen. En cambio la parte no recursiva puede coincidir con la condición de terminación.

Algo muy importante a tener en cuenta cuando usemos la recursividad es que es necesario asegurarnos que llega un momento en que no hacemos más llamadas recursivas. Si no se cumple esta condición el programa no parará nunca.

Ventajas e inconvenientes.

La principal ventaja es la simplicidad de comprensión y su gran potencia, favoreciendo la resolución de problemas de manera natural, sencilla y elegante; y facilidad para comprobar y convencerse de que la solución del problema es correcta.

El principal inconveniente es la ineficiencia tanto en tiempo como en memoria, dado que para permitir su uso es necesario transformar el programa recursivo en otro iterativo, que utiliza bucles y pilas para almacenar las variables.

Estructura Representación

Una tabla es una estructura homogénea en la que todos los elementos que la componen son del mismo tipo.Son estáticas, no crecen ni decrecen en tiempo de ejecución y tienen un límite preestablecido antes

de la compilación.

Para acceder a los elementos de una tabla se utilizan los “índices” y estos pueden ser de cualquier tipo escalar de PASCAL (enumerados, INTEGER, CHAR, subrango, BOOLEAN).Por ello las tablas son estructuras de acceso directo o acceso por índice.

Búsqueda secuencial.

Búsqueda secuencial con centinela.

Almacenamiento externo

Usamos espacios fuera de las de la tabla para colocar las colisiones. Dentro del almacenamiento externo hay varios tipos.

Encadenamiento directo y zona de overflow.

Encadenamiento directo.

Esta realización considera la tabla como un vector en el que cada posición contiene un elemento y un campo adicional con el comienzo de la lista de elementos con los que existe colisión.Es decir, las posibles colisiones se resuelven construyendo una lista de elementos cuya imagen hash coincida. Ventajas: eficientes y rápidos.

Inconvenientes: Para cada elemento de la lista se debe reserVAR un espacio para punteros lo que significa un desaprovechamiento de memoria en el “manejo de lista”.

Zona de Overflow.

Se reserva espacio en cierta zona de externa a la propia tabla, de aproximadamente el 10% de su tamaño, para introducir las colisiones.Cada sinónimo se almacena en la primera celda disponible de la zona de overflow.

Inconveniente: Desaprovechamiento de memoria (poco).Es poco eficiente cuando se han producido colisiones, ya que la búsqueda en la zona de overflow es secuencial.

Ventajas: Ocupa menos memoria que el anterior.El algoritmo de búsqueda y de inserción es mas sencillo.

Almacenamiento interno

Cuando el espacio usado para almacenar las colisiones esta dentro de los límites de la tabla.Dentro del almacenamiento interno están:Encadenamiento directo y encadenamiento vacío.

Encadenamiento directo.

Se usa dentro de la tabla un campo de tipo puntero para que apunte al siguiente colisionado, que estará dentro de la tabla.En ese campo se guarda la dirección del siguiente colisionado.

En el encadenamiento directo con zona de overflow podemos sobredimensionar la tabla para almacenar

las colisiones, en esta zona las casillas estarán encadenadas con una variable que apunte al primer

espacio libre de la zona de overflow.Consiste en enlazar todos los elementos cuyas claves generan igual indice primario por medio de enlaces dentro de la tabla a las nuevas posiciones ocupadas por estos elementos.

Inconvenientes: Espacio reservado en cada elemento para el enlace.

Ventajas: Más rápido que el externo con zona de overflow ya que evita la búsqueda secuencial.

Ocupación de memoria: Depende del método usado.El primer caso ocupa menos memoria, y el segundo

es más rápido.

3. Lista

Concepto.

Una lista es una estructura de datos homogénea y dinámica, que va a estar formada por una secuencia

de elementos, donde cada uno de ellos va seguido de otro o de ninguno. Homogénea: Todos los elementos que la forman tienen el mismo tipo base.

Dinámica: Puede crecer o decrecer en tiempo de ejecución según nuestras necesidades. dos listas pueden ser diferentes si:

No tienen el mismo número de elementos: L1: gato, perro.

L2: gato, canario, cerdo.

Cuando, aun teniendo el mismo número de elementos, estos son distintos: L1: gato, perro.

L2: gato, cerdo.

Cuando, aun teniendo el mismo número de elementos y siendo estos los mismos, no están dispuestos

en el mismo orden. L1: gato, perro.

L2: perro, gato.

Hay varios criterios para clasificar las listas: según su modo de acceso o según su información de acceso.

Modo De Acceso.

Atendiendo a este, se dividen en densas y enlazadas. El modo de acceso es independiente de la implementación realizada.

Listas densas

Se caracterizan porque los elementos siguen una secuencia física. Sabemos cuales es el siguiente elemento porque para acceder a él hemos tenido que pasar por todos los anteriores.

La localización de un elemento cualquiera será: El primero si es el primer elemento de la lista.

N-esimo si para llegar a el hemos pasado por N-1 elementos.

Siguen una estructura física secuencial luego se pueden implementar utilizando ficheros, ARRAYS  y punteros.

Listas enlazadas

Son aquellas en las que cada elemento que los compone contiene la información necesaria para acceder al elemento siguiente. La localización de un elemento cualquiera será:

Un elemento de la lista tendrá la dirección K si K es el primero y K es conocido (dirección de inicio). Estará en la dir. J si J está contenida en el elemento anterior.

Informacion de acceso. Listas ordinales

Los elementos se van colocando en la lista a medida que llegan y se identifican por el orden de llegada.El acceso a un elemento es por su orden o posición relativa dentro de la lista.

Listas calificadas

Los elementos se clasifican por una clave y pueden estar ordenados o no estarlo. A un elemento se accede por la información contenida en un campo clave.

Diferencias: En la primera clase importa en orden de llegada, mientras que en la segunda depende de la clave.

Pilas.

Una pila es una lista ordinal en la que el modo de acceso a sus elementos es del tipo LIFO. Los

añadidos y extracciones de elementos de una estructura se realizan solo por un extremo, luego el único elemento accesible de la pila es el que se encuentre en la cima. Esto exigirá que la manipulación sobre

un elemento, necesite que el mismo ocupe la posición de cima.

Sobre una estructura de tipo pila, surgen de forma natural las operaciones que permiten añadir elementos y quitar elementos.

Implementación utilizando tablas

Esta realización consiste en ir guardando consecutivamente los elementos de la pila en un vector de tamaño fijo. Un índice marcará la posición del último elemento que se ha añadido a la pila. Por tanto, las inserciones en la estructura se realizarán en la posición inmediatamente siguiente a la posición marcada como cima, pasando a ser esta nueva posición ocupada la nueva cima de la pila.

El hecho de utilizar un vector para almacenar los elementos, puede conducir a la situación en que la pila esté llena, es decir, que no quepa ningún elemento más. Esto se producirá cuando el índice que señala

la cima de la pila sea igual al tamaño del vector.

Otros Tipos De Listas

Listas reorganizables.- Son aquellas listas en las que el último elemento consultado se sitúa al principio. Listas circulares.- En ellas el último elemento apunta al primero.

Listas doblemente enlazadas.- Cada elemento tiene dos punteros, uno de los cuales apunta al elemento siguiente y otro al anterior.

Listas circulares doblemente enlazadas

4. Árboles binarios.

Los árboles de grado 2 tienen una especial importancia. Se les conoce con el nombre de árboles

binarios. Se define un árbol binario como un conjunto finito de elementos (nodos) que bien está vació o está formado por una raíz con dos árboles binarios disjuntos, llamados subárbol izquierdo y derecho de

la raíz.

En los apartados que siguen se considerarán únicamente árboles binarios y, por lo tanto, se utilizará la palabra árbol para referirse a árbol binario. Los árboles de grado superior a 2 reciben el nombre de árboles multicamino.

Árbol binario de búsqueda.- Los árboles binarios se utilizan frecuentemente para representar conjuntos

de datos cuyos elementos se identifican por una clave única. Si el árbol está organizado de tal manera que la clave de cada nodo es mayor que todas las claves su subárbol izquierdo, y menor que todas las claves del subárbol derecho se dice que este árbol es un árbol binario de búsqueda.

Ejemplo:

Operaciones básicas.- Una tarea muy común a realizar con un árbol es ejecutar una determinada operación con cada uno de los elementos del árbol.Esta operación se considera entonces como un parámetro de una taré más general que es la visita de todos los nodos o, como se denomina usualmente, del recorrido del árbol.

Si se considera la tarea como un proceso secuencial, entonces los nodos individuales se visitan en un orden específico, y pueden considerarse como organizados según una estructura lineal. De hecho, se simplifica considerablemente la descripción de muchos algoritmos si puede hablarse del proceso del siguiente elemento en el árbol, según un cierto orden subyacente.

Hay dos formas básicas de recorrer un árbol: El recorrido en amplitud y el recorrido en  profundidad. Recorrido en amplitud.- Es aquel recorrido que recorre el árbol por niveles, en el último ejemplo sería:

12 – 8,17 – 5,9,15

Recorrido en   profundidad.- Recorre el árbol por subárboles. Hay tres formas: Preorden,  orden central y postorden.

Preorden: Raiz, subárbol izquierdo y subárbol derecho. Orden central: Subarbol izquierdo, raiz, subarbol derecho. Postorden: Subarbol izquierdo, subarbol derecho, raiz. Ejemplo:

Preorden:  20 – 12 – 5 – 2 – 7 – 13 – 15 – 40 – 30 – 35 – 47

Orden central:  2 – 5 – 7 – 12 – 13 – 15 – 20 – 30 – 35 – 40 – 47

Postorden:  2 – 7 – 5 – 15 – 13 – 12 – 35 – 30 – 47 – 40 – 20

Ejemplo:

Preorden:  / + a b * c d Notación polaca

Orden central: a + b / c * d Notación infija

Postorden:  a b + c d * / Notación polaca inversa

Estructura de datos

Variables

Las variables son estructura de datos usados para almacenar información. Hay dos tipos de información que puede ser almacenada: Números y texto. Antes de usar una variable ésta, deberá primero ser

definida:

Dim nombre_de_variable As Tipo

Ejemplo:

Dim  precio  As Long

Dim nombre_de_articulo As String

Tipo                 Rango permitido

Integer             -32,768 a 32,767

Long                -2,147,483,648 a 2,147,483,647

Single

Double

-3.402823E38 a -1.401298E-45

1.401298E-45 a 3.402823E38

-1.79769313486232D308 a -4.94065645841247D-324

4.94065645841247D-324 a 1.79769313486232D308

Currency          -922337203685477.5808 a 922337203685477.5807

String              0 a 65,000 bytes

Valores de fechas: 1/1/0000 a 12/32/9999

Variant

Numérico: igual que Double

Texto: Igual que String

Si una nueva variable es declarada sin especificación VB por default la deberá

tomar como tipo Variant

Una vez que una variable se ha creado, se le puede asignar un valor. Para esto se usa el operador ‘ = ‘.

En el primer ejemplo de abajo a una variable se le asigna un valor constante, mientras que en el segundo se le asigna el contenido de una variable multiplicada por 10.

Ejemplo 1: precio = 29.95

Ejemplo 2: precio_total = precio * 10

El Alcance de una variable es definido como su rango de operación. Hay tres tipos de alcance en una variable:

1.   Local – La variable solo puede ser usada en el procedimiento actual ( use Dim dentro del procedimiento requerido).

2.   Módulo – La variables pueden ser accesadas desde cualquier procedieminento de la forma actual (use Dim dentro de la sección de Declaraciones Generales de la forma).

3.   Global – Pueden ser accesados desde cualquier procedimiento y desde cualquier forma. (usa

Global dentro de la sección de Declaraciones Generales de un módulo).

Variables Estáticas

El declarar variables y arreglos como local en un procedimieneto/función es muy usado, porque esto minimíza los efectos extraños que pueden ocurrir cuando se usan variables globales. Sin embargo, cuando usamos una variable local en un procedimiento VB crea un espacio de memoria para mantener

el valor de esta variable , esto sucede cuando lee el estatuto Dim, pero cuando llega al final del procedimiento (End Sub) VB libera el espacio asigndo para el valor de la variable local. Agrega el siguiente código a un botón de comando y observa que valores son impresos:

Sub Command1_Click ()

Dim numero As Integer  ‘ Crea una variable Local normal numero  = numero + 1

Print numero

End Sub

Después de dar clic varias veces al botón de comando deberás ver una columna de unos en el lado izquierdo de la forma. El valor nunca será arriba de uno a pesar de que el valor de la variable se incrementa en uno cada vez. Esto es porque cada vez que el procediemineto es llamado, haciendo clic

en el botón, VB esta trabajan con una variable diferente. Esta tiene exactamente el mismo nombre en el programa pero es una variable completamente diferente. Para que esto no suceda así, introduce

Staticen el lugar de Dim: Sub Command1_Click ()

Static numero As Integer ‘ Crea una variable estática local numero = numero + 1

Print numero

End Sub

Ahora en vez de que el valor de la variable se pierda cuando el procedimiento termina, con este cambio

(static) su valor permanecerá hasta que todo el programa termine. De esta manera, podemos ver una lista de números que se incrementan en uno cada vez que se le da clic al botón de comando.

Nota: La nueva variable estática es una variable de alcance local, si cualquier procedimiento trata de accesar esta variable no prodrá lograrlo. Agrega a la forma un nuevo botón de comando, el cual deberá tener un nuevo procedimiento ‘Click’, y trata de corregir o imprimir el valor de la variable estática que contiene el primer botón.

El contenido de un arreglo local, también puede mantenerse mientras el programa se ejecute. Para hacer esto agrega el estatuto ‘Static’ en lugar de ‘Dim’ como lo hicimos en el ejemplo de arriba.

Static salarios(199) As Long

5. Variables Constantes

Las constantes son similares a una variable pero tienen un valor determinado que se mantiene igual en toda la ejecución del programa. El contenido de una varible puede cambiar tantas veces sea necesario.

¿Porque usar una constante si no puede cambiar de valor?. Hacemos esto cuando deseamos usar un mismo número o una palabra (string) varias veces. Por ejemplo, en un programa para calcular los impuestos de todo el año, deberá hacer referencia a un valor en varias partes del programa, que puede

ser el por ciento de impuesto mensual con respecto a las ganancias. Par ello podemos usar una variable llamada IMP, que mantendrá el valor en el evento Form_Load.

En el siguiente ejemplo definimos una contante llamada ‘ IMP ‘ y le asignamos el valor de 1.175. Ese es usado en estatuto Print con la variable pago_total para calcular la cantidad total a pagar. Note que en

lugar de escribir 1.175 en la fórmula nos referimos a el nombre de la constante. Ejemplo:

Const IMP = 1.175           ‘ Declara y asigna un valor  a la constante

Dim pago_total As Currency   ‘ Declara una variable local para almacenar el total a pagar pago_total = 560.95

Print “Total = “; pago_total * IMP

Como las variables las constantes también tiene reglas de alcance. Hay constantes globales que

pueden ser accesadas por cualquier módulo o cualquier forma del proyecto, las constantes de módulo solo son accesadas por la foma que los contiene, y las contantes locales son accesadas solamente por

el objeto actual o procedimiento/función.

1.   Local – usa ‘Const’ dentro del procedimiento requerido.

2.   Módulo – usa ‘Const’ dentro de la sección deDeclaraciones Generales de una forma o módulo.

3.   Global – usa ‘Global Const’ dentro de la sección deDeclaraciones Generales de un módulo (ésto

es Module1.bas).

Arreglos (arrays)

La variables son muy usadas para lamacenar pequeñas cantidades de información, pero no son convenientes para grandes cantidades de información muy similar. Por ejemplo, para almacenar los salarios de doscientos empleados, necesitaremos 200 variables diferentes. Una mejor forma de almacenar esta información será usra una estructura de datos llamada arreglos array.

Un arreglo es similar a las celdas en un panal de abejas. Todo el arreglo tiene un nombre, y cada celda tiene una dirección. Para el problema de los salarios planteado arriba, un arreglo el cual tiene 200

elementos (celdas) , usaremos el comando Dim para cerar un nueva variable, pero marcaremos también

el tamaño de esta variable .

Dim nombre_del_arreglo (tamaño) [As Tipo] Ejemplo: Dim salarios(199) As Long

En el ejemplo de arriba creamos un arreglo con 200 elementos. El tamaño marcado es de 199 porque por default VB empieza la numeración con 0.

Si sabemos que el nombre ‘Jaime ‘ es el empleado número 24 y tiene un salario de 25,000 pesos mensuales, podemos lintroducir esta cantidad en el arreglo de la siguiente forma:

salarios(23) = 25000

Contrario a lo anterior, si deseamos saber cula es el salario del empleado número 189, podemos usar:

lblvalor.Caption = salarios(188)

Nota: En los dos ejemplos anteriores, para accesar un elemento es necesario colocar el número del elemento anterior (si deseamos el 150, pedimos el 149). esto es VB empieza un arreglo de 0, no de 1.

Sin embargo VB pude ser forzado a empezar con 1, agregando el estatuto ‘Option Base 1′ en la sección

de declaraciones generales de la forma o el módulo.

Trabajo enviado por:

Ist  Cepea  Lima – Perú Bermúdez  Moncada,  Isabel Chumpitaz  Heirisman, Roel germany0116@yahoo.es Guevara  Retamozo  Miguel

Salazar Beaumont Blanco, Enrique

Guía Practica de ADA

 

Los contenidos y organización de esta guía están realizados en función de las necesidades específicas de las asignaturas de Metodología de la Programación y Metodología de la Programación I de la Ingeniería en Informática y las Ingenierías Técnicas en Informática de Gestión y de Sistemas que se imparten en la Facultad de Informática y en la Escuela Universitaria de Informática por parte del Departamento de  Informática y Sistemas.


Estructura básica de un programa en Ada.
Variables, constantes y tipos.
Declaración de variables
Definición de constantes
Definición de tipos
Tipos escalares.
Enumerados
Enteros
Operaciones con tipos ordinales
Reales
Sentencias, asignación y expresiones.
Entrada/salida.
Sentencias de control.
Subprogramas.
Tipos estructurados.
Ristras de caracteres.
Excepciones.
Ficheros.
Ficheros de texto
Ficheros uniformes
Memoria dinámica.
Estructura general de un programa en Ada.
Packages.
Tipos private y limited private.
Unidades genéricas.
Parámetrización de unidades genéricas.


APÉNDICES
Entorno de programación
Operadores
Palabras reservadas
Funciones matemáticas
Conversión de tipos
Depuración

Estructura básica de un programa en Ada.

Un programa en Ada consta de al menos dos partes: una cláusula de contexto y un procedimiento principal.

--Programa simple en Ada
with Text_IO; --cláusula de contexto

procedure Hola is --procedimiento principal
    --Parte de declaraciones
begin
    --Cuerpo de sentencias ejecutables
    Text_IO.Put_Line("Hola");
end Hola;

(Las líneas a partir de los dobles guiones,”–”, son comentarios. Todas las sentencias en Ada deben terminar con un punto y coma (“;”))

Las cláusulas de contexto sirven para especificar el uso de librerías externas al programa. Una librería es un conjunto de recursos (procedimientos, funciones, tipos, …) ya desarrollados y compilados, y que se hallan disponibles para ser usados por nuestros programas. En el caso del ejemplo, la cláusula “with” de la 2ª línea está indicando que nuestro programa va a hacer uso de la librería “Text_IO”, la cual proporciona servicios básicos de entrada/salida; concretamente, en el ejemplo se está usando el procedimiento “Put_Line”, que saca un mensaje por pantalla, y se especifica que hay que buscarlo en la librería Text_IO, antecediendo al nombre del procedimiento con el nombre de la librería seguido por un punto (“.”). Si se quiere evitar tener que hacer este tipo de cualificaciones con todos los recursos externos que se empleen, se habrá de disponer una cláusula “use” como en el siguiente ejemplo:

--Programa simple en Ada
with Text_IO; --cláusula de contexto
use Text_IO;
procedure Hola is --procedimiento principal
    --Parte de declaraciones
begin
    --Cuerpo de sentencias ejecutables
    Put_Line("Hola"); --ya no es necesario poner "Text_IO."
end Hola;

El procedimiento principal es el punto de arranque del programa, del que dependen en última instancia, todos los demás procedimientos y funciones que se utilicen. Un procedimento consta de: (1) una cabecera (línea 5 del segundo ejemplo), donde se le da nombre, (2) una sección de declaraciones, donde se declaran y/o definen todos los elementos que el procedimiento va a utilizar (variables, constantes, tipos, procedimientos, funciones, …), y (3) el cuerpo del procedimiento –las sentencias que describen su algoritmo, líneas 7 a 10–, que comienza con la palabra “begin” y termina con la palabra “end” acompañada del nombre del procedimiento. El cuerpo de un procedimiento no puede estar vacío, en caso de desear, circunstancialmente, tener un procedimiento que no haga nada, debe de contener la instrucción “null;”.

Variables, constantes y tipos.

Declaración de variables.

Las variables se declaran como “nombre_de_variable : tipo“, tal como en el siguiente ejemplo:

x : integer; --una variable de tipo integer llamada x
Si hay que declarar varias variables del mismo tipo, se puede formar una lista, aunque ello no es obligatorio:

x, y, z : integer;
Las variables pueden inicializarse con un valor en el momento de su declaración:

x, y, z : integer := 0;

Definición de constantes.

Para definir una constante, se hace igual que para declarar una variable inicializándola al mismo tiempo, pero poniendo la palabra “constant” antes del tipo.

c: constant integer := 10;
Ahora bien, si se trata de constantes numéricas, también se pueden definir sin especificar su tipo:

pi: constant := 3.1416;

Definición de tipos.

Ada, como todos los lenguajes, ofrece un conjunto básico de tipos predefinidos junto con mecanismos para definir nuevos tipos. Para definir un nuevo tipo, se usa la palabra “type”, seguida del nombre del nuevo tipo; la palabra “is”, y la descripción del nuevo tipo:

type byte is range 0..255;
Ada, también ofrece la posibilidad de definir un subtipo de un tipo ya definido. Por ejemplo:

subtype byte is integer range 0..255;
La diferencia entre ambas definciones de byte, es que en la segunda, al ser un subtipo, es una versión restringida el tipo base de la definición (en este caso, integer), mientras que en la primera se está definiendo un tipo distinto, que no es compatible con el integer, a pesar de que su rango de valores parezca coincidir con una parte de los enteros. De hecho, se puede declarar un nuevo tipo para que sea en todo igual a otro existente, y sin embargo incompatible:

type MiEntero is new integer;
type MiEnteroCorto is new integer range 0..255;

Tipos escalares.

Los tipos en Ada se clasifican en escalares (simples) y estructurados. Los escalares se dividen en: discretos, u ordinales, y no discretos. Los tipos discretos son enumerados y enteros. Los no discretos son los reales. Tanto los enteros como los reales se clasifican a su vez como numéricos.

Los tipos enumerados son aquellos que se definen especificando la lista ordenada de valores que lo componen:

type Día is (Lun, Mar, Mie, Jue, Vie, Sab, Dom);
type Palos is (Oros, Bastos, Espadas, Copas);
type Género is (M, F);
type Color is (Blanco, Rojo, Amarillo, Verde, Azul, Marrón, Negro);
type Luz is (Rojo, Naranja, Verde);
Una vez definido el tipo, se pueden declarar variables. Si, como ocurre con los tipos Color y Luz, hay valores que se repiten, habrá que cualificar ese valor cuando su uso pueda ser ambiguo:

x := Color'Rojo; -- x debe ser de tipo Color
y := Luz'Rojo; -- y debe ser de tipo Luz
Ada tiene predefinido el tipo enumerado character –que representa el alfabeto de caracteres utilizado–, y el tipo boolean, que puede tomar los valores true o false.

Ada posee un tipo integer predefinido cuyo rango de valores depende de la implementación del lenguaje. Además, se pueden definir nuevos tipos enteros que pueden ser, con signo o sin signo:
type MiEntero is range -32768..32767; --Entero con signo
type MiNatural is mod 32767; --Entero sin signo (rango 0..32766)

En ambos casos se pueden usar expresiones para definir los límites del tipo:
type MiEntero is range -2**15..2**15; --Entero con signo
type MiNatural is mod 2**16; --Entero sin signo (rango 0..65535)

Existen predefinidos en el paquete standard dos subtipos del tipo integer:
subtype Natural is integer range 0..Integer'last;
subtype Positive is integer range 1..Integer'last;

Todos los tipos ordinales tienen asociadas, entre otras, las siguientes operaciones como atributos (todas se usan cualificadas con el nombre del tipo), sea T un tipo ordinal:

T’succ(x) devuelve el valor que sigue a x en la lista de valores del tipo (el último valor no tiene sucesor)

T’pred(x) devuelve el valor que precede a x en la lista de valores del tipo(el primer valor no tiene predecesor)

T’pos(x) devuelve la posición que ocupa x en la lista de valores del tipo (al primer valor le corresponde la posición 0)

T’val(x) devuelve el valor correspondiente a la posición x en la lista de valores del tipo

T’first devuelve el valor más pequeño del tipo

T’last devuelve el mayor valor del tipo

T’range devuelve el número de valores posibles del tipo

Ejemplos:

x:=Día'succ(Mar); --x toma el valor Mie

x:=Día'pred(Mar); --x toma el valor Lun

y:=Día'Pos(Mar); --y toma el valor 1

x:=Día'Val(3); --x toma el valor Jue
Ada también tiene un tipo predefinido, float, para representar números reales, cuyo rango y precisión dependen de la implementación. No obstante, el programador también puede definir sus propios reales con dos posibles formatos: coma fija y coma flotante.

Al definir un tipo real de coma flotante se puede especificar el número de dígitos significativos, y el rango de valores posibles.

type rflotante is digits 5 range -1.0..1.0;
Al definir un tipo real de coma fija, se pretende obtener una precisión definida. Los números reales en coma fija se pueden catalogar en: ordinarios y decimales. En el primer caso, se especifica el límite de error en su representación (delta) y, si se quiere, el rango de variación. En el segundo se indica el número de dígitos significativos y el incremento entre valores sucesivos admitidos (delta).

type rfija1 is delta 0.01 range -1.0..1.0; --Coma fija ordinario
type rfija2 is delta 0.01 digits 5;  --Coma fija decimal

En el caso de números decimales en coma fija, el delta tiene que ser potencia de 10.

Sentencias, asignación y expresiones.

Toda sentencia en Ada termina en punto y coma (“;”). Existe la sentencia nula (que no hace nada), y se expresa como “null;”.

La asignación se expresa con el símbolo “:=”. Ejemplo:

x := 3;
En la parte izquierda de la asignación debe ir una variable. En la parte derecha de la asignación va una expresión de un tipo adecuado.

Una expresión está formada por operandos (constantes, variables, expresiones, …) y operadores. Como resultado de su evaluación una expresión da lugar a un valor. El tipo de la expresión es el tipo del valor resultante. Ejemplos:

-4.0
-4.0 + A
B**2 - 4.0*A*C
Index = 0 or Item_Hit
(Cold and Sunny) or Warm
A**(B**C)

Entrada/salida.

Las funciones básicas de entrada/salida son:
get(X), put(X) para X de tipo character, string, integer o float
get_line(S,L), put_line(S) para S de tipo string. L es la longitud leída. Leen o escriben una string en una línea y pasan a la siguiente.
new_line, que, cuando se está escribiendo, inicia una nueva línea.
skip_line, que, cuando se está leyendo, desestima el resto de la línea actual.

Para realizar entrada/salida de ristras o caracteres basta con incluir la cláusula de contexto “with Text_IO;”. Para realizar entrada/salida de otros tipos escalares hay que hacer, además, una declaración, en la sección de declaraciones del procedimiento, que especifica la aplicación de un paquete genérico de entrada/salida adecuado sobre el tipo en cuestión (a esto se llama crear una instancia del paquete), tal como se muestra en los siguientes ejemplos:

package Día_IO is new Enumeration_IO(Día); --Enumeration_IO sirve para tipos enumerados
package MiEntero_IO is new Integer_IO(MiEntero);--Integer_IO sirve para enteros con signo
package rf_IO is new Float_IO(rf); --Float_IO sirve para reales en coma flotante
package rfija1_IO is new Fixed_IO(rfija1);--Fixed_IO sirve para reales en coma fija ordinarios
package rfija2_IO is new Decimal_IO(rfija1);--Decimal_IO sirve para reales en coma fija decimales
package Natural_IO is new Modular_IO(Natural);--Modular_IO sirve para enteros sin signo
En cualquier caso, hay que utilizar la cláusula “with Text_IO;”, puesto que estos paquetes de entrada/salida (Enumeration_IO, Integer_IO, Float_IO, …), se encuentran incluidos en la librería “Text_IO”.

Sentencias de control.

Selección de dos alternativas.

if Exp_lógica then
sentencias;
end if;
if Exp_lógica then
sentencias;
else
sentencias;
end if;

Selección múltiple.

case selector is
 when alternativa => sentencias;
 when alternativa => sentencias;

 when others => sentencias;
end case;

El selector debe ser una expresión discreta de tipos integer o enumerados (tipo ordinal). Las alternativas pueden ser uno o varios valores, o rangos, del tipo del selector separados por “|” (equivale al operador OR).

case mes is
 when 1 .. 2 | 12 => put(“El invierno es duro”);
 when 3 .. 5 => put(“Primavera de la vida”);
 when 6 .. 8 => put(“Estación llena de diversión”);
 when 9 .. 11 => put(“Época de reflexión”);
 when others => put(“¿En qué planeta estás?”);
end case;

Los valores no pueden repetirse entre dos cláusulas “when”. En el caso de que las cláusulas “when” no cubran todos los posibles valores del tipo del selector, es necesario incluir la cláusula “others” para los valores no contemplados.

Iteración.

Controlada por contador.

for variable in [reverse] secuencia de control loop

end loop;

La variable de control es local al bucle, NO SE DECLARA (es del tipo de la secuencia de control) y no puede modificarse explícitamente. La secuencia de control puede ser un rango de un tipo ordinal (entero o enumerado). Cuando se utiliza “reverse” la secuencia de control se recorre en orden inverso:

for num in reverse 1..5 loop
Put(num); — escribe 5 4 3 2 1
end loop;
Controlada por condición lógica.

while condición loop

end loop;
Bucles sin esquema de iteración.

En Ada se puede construir un bucle tal como:

loop

end loop;

del que se sale, normalmente, mediante una sentencia “exit when” o con una alternativa que contenga una cláusula “exit”.

loop

    exit when condición;

end loop;

Bloques.

Un bloque es una sentencia compuesta, formada por una secuencia de sentencias agrupadas mediante las palabras delimitadoras “begin”, “end”, y posiblemente acompañadas de algunas declaraciones locales.
[declare
    declaraciones locales]
begin
    sentencias
end;

(la parte delimitada por corchetes es opcional)

Un bloque puede ponerse en cualquier sitio donde pueda ponerse una sentencia simple.

Ejemplos de bloques:
--bloque sin declaraciones locales
begin
    Put_Line("Hola");
end;

--bloque con declaraciones locales
declare
    Aux : integer; --la variable Aux sólo existe dentro del bloque
begin
    Aux := i; --i, j están declarados en un ámbito más externo
    i := j;
    j := Aux;
end;

Subprogramas.

Definición de subprogramas.

Un subprograma es un procedimiento o función. La estructura de la definición de un subprograma consta de tres elementos:

  1. Cabecera
  2. Declaraciones locales
  3. Sentencias ejecutables

La cabecera describe el protocolo del subprograma, y consta de: especificación de “procedure” o “function”, nombre del subprograma, lista de parámetros y tipo del resultado, caso de ser una function.

procedure Nombre_procedimiento (parámetros) is

function Nombre_función (parámetros) return Tipo is

La lista de parámetros está formada por especificaciones de parámetros formales separadas por “;”. Cada especificación de parámetros consta de una lista de nombres de parámetros separados por “,” y seguida de “:”, el modo de paso (in, out o in out) y el tipo de los parámetros.

procedure LisParam(S1,S2: in integer;S3: out float) is

  • Los parámetros “in” son constantes y no pueden modificarse en el subprograma.
  • Los parámetros “in out” pueden usarse y modificarse.
  • Las funciones sólo tienen parámetros “in”.

La parte de sentencias ejecutables comienza con la palabra “begin” y abarca hasta que aparezca la palabra “end” seguida del nombre del subprograma y “;”.

La parte de declaraciones locales abarca desde la cabecera hasta la palabra “begin” que da comienzo a las sentencias ejecutables. Los elementos declarados (variables, tipos, subtipos, procedimientos,…) tienen ámbito estático.

–Ejemplo de subprogramas
with Text_IO; use Text_IO;
procedure Ejemplo is –procedimiento principal Ejemplo
–requerimientos de entrada/salida;
package Sal1 is new Integer_IO(integer);
package Sal2 is new Float_IO(float);
use Sal1, Sal2;
–función local EsPar
  function EsPar(x: in integer) return boolean is
  begin
      return ((x rem 2) = 0);
  end EsPar;
–procedimiento local Media
  procedure Media(x,y: in float;med: out float) is
      dos: float; --variable local
  begin
      dos := 2.0;
      med := (x + y) / dos;
  end Media;

R1,R2,R3 : float; –variables locales de Ejemplo
i : integer;
begin
put(“Entre un numero: “);
Get(i); skip_line;
if EsPar(i) then put_line("es par"); else put_line("no es par"); end if;
put(“Entre una pareja de numeros separados por un espacio: “);
Get(R1); Get(R2); skip_line;
Media(R1,R2,R3);
Put(R3); new_line;
end Ejemplo;

Sobrecarga de operadores.

Ada permite que el programador sobrecargue los operadores del lenguaje, esto es, que pueda redefinirlos dándoles nuevos significados. Para sobrecargar un operador, simplemente hay que definir una función cuyo nombre sea el operador entre comillas y que tenga los parámetros adecuados. Por ejemplo, dado el siguiente tipo:
type Complejo is record
    PReal, PImag: float;
end Complejo;

podemos sobrecargar el operador de suma (“+”) para utilizarlo con el fin de sumar números complejos:

function "+"(A, B : in Complejo) return Complejo is
    Suma: Complejo;
begin
    Suma.PReal := A.PReal + B.PReal;
    Suma.PImag := A.PImag + B.PImag;
    return Suma;
end "+";

Una vez definida esta función, se puede aplicar el operador entre variables del tipo definido en los parámetros, devolviendo un valor del tipo definido como resultado.
...
S, X, Y: Complejo;
...
S := X + Y;

Sólo se pueden redefinir los operadores del lenguaje (no se pueden inventar operadores), y siempre manteniendo su cardinalidad (los binarios se redefinirán con funciones de dos parámetros y los unarios con funciones de un parámetro).

Tipos estructurados.

Arrays.

Los arrays son estructuras homogéneas, es decir, están formados por un conjunto ordenado de elementos del mismo tipo que se pueden acceder individualmente mediante índices discretos.

En Ada hay dos clases de arrays: restringidos y no restringidos. Los restringidos son arrays para los que se especifica el tamaño en su definición (tamaño fijo). Los no restringidos son aquellos en los que el rango de los índices no se establece al definir el tipo, sino que se concreta posteriormente.

type Vector is array(1..10) of float; –array restringido de 10 elementos
type Libre is array(integer range <>) of float; –array no restringido
V: Vector;
L: Libre(1..10);

Si el array tiene más de una dimensión, se declara separando los rangos de cada dimensión mediante comas (“,”).
type Matriz is array(1..5,1..3) of integer;--matriz de 5x3
...
A: Matriz;

Los arrays tienen los siguientes atributos: First, Last, Range, Length. Si A es un array, entonces:

A’First es el límite inferior del rango del primer índice de A.
A’First(N) es el límite inferior del rango del N-ésimo índice de A
A’Last es el límite superior del rango del primer índice de A.
A’Last(N) es el límite superior del rango del N-ésimo índice de A
A’Range es equivalente al rango A’First .. A’Last.
A’Range(N) es equivalente al rango A’First(N) .. A’Last(N).
A’Length es el número de valores del rango del primer índice.
A’Length(N) es el número de valores del rango del N-ésimo índice.

...
--Ejemplo que recorre una matriz escribiendo sus elementos
for i in A'Range(1) loop
   for j in A'First(2)..A'Last(2) loop
      put(A(i,j)); put(" ");
   end loop;
   new_line;
end loop;
...

Literales arrays e inicialización de arrays.

Se puede expresar un valor literal de tipo array utilizando paréntesis para agrupar los valores por filas y columnas. Se puede utilizar la palabra “others” para indicar un valor por defecto para las posiciones que no se especifiquen explícitamente. Un literal de tipo array se puede utilizar, entre otras cosas, para inicializar una variable de tipo array en su declaración.
...
    type matriz is array(1..5,1..5) of integer;
    type vector is array(1..5) of integer;
...
    v1 : vector := (1,2,3,4,5);
    v2 : vector := (9,8,7, others => 0);
    v3 : vector := (1 => 9, 3 => 8, 5 => 7, others => 0);
...
    m1 : matriz := (others => (others => 0));
    m2 : matriz := ((1,2,3,4,5),
                    (1,2,others => 0),
                    (1 => 9, 3 => 8, 5 => 7, others => 0),
                   others => (others => 0));
...

Rodajas (slices).

Se puede hacer referencia a un trozo de un array monodimensional, simplemente especificando sus límites. Este tipo de “corte” se conoce como rodaja o slice.

 v1(1..3):= v2(3..5);

Records.

Los records son estructuras heterogéneas, es decir, agregados de elementos del mismo o distintos tipos que se pueden acceder individualmente mediante su nombre.

Un record se define con la palabra “record”, seguida de la declaración de los campos del record y “end record”.

type Complejo is record
real: float;
imag: float;
end record;

Para acceder a una campo individual de un record se utiliza el operador punto (“.”); mediante el nombre de la variable cualificada con el nombre del campo en cuestión:
...
x,w : Complejo; --Declaración de variables de tipo record
y   : array (1..20) of Complejo; --Declaración anónima de un array
...
x.real := 3.0;
x.imag := x.real;
y(7) := x;
...
y(1).real := 3.0;
...

Literales records e inicialización de records.

Se pueden formar literales records de dos formas:

  • como agregado posicional, especificando los valores de todos los campos en el orden adecuado y entre paréntesis

    x := (3.5, 7.1);

  •  como agregado nominal, especificando los nombres de los campos junto con los valores

    w := (real => 3.5, imag => 7.1);
    w := (imag => 7.1, real => 3.5);

Ristras de caracteres.

Ada ofrece los tres tipos posibles de ristras de caracteres: tamaño fijo, tamaño limitado y tamaño dinámico.

Cláusula de contexto

Para usar ristras de tamaño fijo, tamaño limitado y tamaño dinámico, hay que incluir en la cláusula “with” los paquetes “Ada.Strings.Fixed”, “ada.Strings.Bounded” y “Ada.Strings.Unbounded”, respectivamente.
    with Text_IO, Ada.Strings.Fixed,
         Ada.Strings.Bounded, Ada.Strings.Unbounded;

En la cáusula “use” sólo hay que incluir “Ada.Strings.Fixed” y “Ada.Strings.Unbounded”, ya que las ristras de tamaño limitado (bounded) necesitan una instanciación para fijar la longitud máxima antes de poder usarse.
    use Text_IO, Ada.Strings.Fixed, Ada.Strings.Unbounded;

Declaración

Una ristra de tamaño fijo es básicamente un array de caracteres que se declara con la palabra “String” y especificando el rango de variación de los índices, que debe ser de tipo Positive. (Lo más normal es que este rango empiece en 1).
    sf1, sf2 : String(1..10); --variables de tipo ristra de 10 caracteres

Antes de declarar ristras de tamaño limitado es necesario instanciar el subpaquete genérico llamado “Ada.Strings.Bounded.Generic_bounded_length” para el tamaño máximo que se desee (esto se hace para poder tener instancias para distintas longitudes).
    package String_max_10 is new Ada.Strings.Bounded.Generic_bounded_length(10);
    use String_max_10;

Una vez hecho esto se pueden declarar variables de tamaño limitado (tipo “Bounded_string”), cualificando el nombre del tipo con el de la instancia definida.
    sl1, sl2 : String_max_10.Bounded_string; --ristras de hasta 10 caracteres

Las ristras de longitud dinámica se declaran del tipo “Unbounded_string”.
    sd1, sd2 : Unbounded_string; --ristra de longitud dinámica

Ristra nula

En las ristras de tamaño fijo no existe la ristra nula, dado que una variable de tipo “String” siempre tiene la longitud definida (Ada utiliza caracteres de relleno, por defecto es el espacio).

La ristra nula para las ristras de tamaño limitado está representada por el valor “Null_Bounded_String”.
    sl1 := Null_Bounded_String.

La ristra nula para las ristras de tamaño dinámico está representada por el valor “Null_Unbounded_String”.
    sd1 := Null_Unbounded_String;

También sirve para representar la ristra nula una ristra fija vacía, que se representa mediante dos dobles comillas (“”), pero para ello son necesarias las funciones de coversión entre ristras.

Literales ristra

Un valor de tipo ristra se escribe como una secuencia de caracteres delimitada por comillas dobles: "esto es una ristra". Un valor literal es una ristra de tamaño fijo cuyo tamaño es igual al número de caracteres de que consta (18 en el ejemplo). Los literales de tipo “Character” se delimitan por comillas simples.
    "a"   esto es una ristra.
    'a'  esto es un carácter, no es una ristra.

Conversión entre ristras

En los respectivos paquetes, existen funciones para convertir ristras de tamaño fijo a ristras de tamaño limitado o dinámico y viceversa.
    sl1 := to_bounded_string("esta linea");  --conversión de string a bounded-string
    sf1 := to_string(sl1);                       --conversión de bounded_string a string
    sd1 := to_unbounded_string("esta linea");--conversión de string a unbounded_string
    sf2 := to_string(sd1);                       --conversión de unbounded_string a string

La conversión entre ristras limitadas y dinámicas ha de hacerse a través de ristras de tamaño fijo.
    sd2 := to_unbounded_string(to_string(sl1));--conversión limitada->fija->dinámica
    sl2 := to_bounded_string(to_string(sd1));  --conversión dinámica->fija->limitada

Se puede asignar la ristra nula convirtiendo una ristra fija vacía.
    sl1 := to_bounded_string("");   --asigna la ristra nula a sl1
    sd1 := to_unbounded_string(""); --asigna la ristra nula a sd1

 Asignación

Se pueden asignar entre sí ristras del mismo tipo (son de distinto tipo las ristras limitadas de diferente tamaño).

En las ristras de tamaño fijo, la asignación sólo puede realizarse entre ristras del mismo tamaño:
    sf1 := "1234567890";

Si se necesita asignar una ristra de un tamaño distinto, se puede emplear la operación “move”:
    move("prueba", sf1);

La operación “move” admite hasta 5 parametros:
    procedure Move (Source  : in  String;
                    Target  : out String;
                    Drop    : in  Truncation := Error;
                    Justify : in  Alignment  := Left;
                    Pad     : in  Character  := Space );

  • “Source” es la ristra origen
  • “Target” es la ristra destino
  • “Drop” determina que acción se tomará en caso de que la ristra origen sea mayor que la destino (las acciones posibles son: “left”, que corta la ristra por la izquierda, “rigth”, que corta la ristra por la derecha, y “error”)
  • “Justify” determina, en caso de que la ristra origen sea menor que la destino, cómo se situará (“left”, a la izquierda, “center”, en el centro, y “rigth”, a la derecha)
  • “Pad” determina, en caso de que la ristra origen sea menor que la destino, con qué carácter se rellenarán las posiciones sobrantes

Entrada / Salida

Sólo las ristras de tamaño fijo pueden intervenir en operaciones de entrada/salida (ello no es un gran problema al existir las funciones de conversión).  Una ristra se lee con la operación “get(X)” o “get_line(X,L)”, donde X es una variable de tipo “String” y L es una variable de tipo “Natural” o compatible con él. La primera exige que se entre el número exacto de caracteres, de acuerdo con la declaración de la ristra; la segunda permite la entrada de un número menor de caracteres, e indica en el segundo parámetro el índice del último carácter leído (si el rango de la ristra empieza en uno, esto es igual al número de caracteres leídos).
Cuando se lee una ristra con get_line, los caracteres leídos sustituyen a los correspondientes que hubiese en la ristra, pero el resto quedan como estaban.
    sf1 := "1234567890";
    get_line(sf1,l_sf1); --l_sf1 es una variable de tipo Natural o compatible

Suponiendo que el usuario introduce la ristra “abcde”, sf1 quedará con el valor “abcde67890″.
Asimismo, las ristras de tipo “String” se escriben con las operaciones “put(X)” o “put_line(X)”. La diferencia entre ambas es que la segunda produce un salto de línea después de escribir la ristra.

Cálculo de la longitud

    En ristras de tamaño fijo esta operacion carece de sentido. En ristras de tamaño limitado o de tamaño dinámico se utiliza una función llamada “Length”, que devuelve la longitud de la ristra.
    x := Length("ULPGC"); --x toma el valor 5

Extracción de una subristra

    En ristras de tamaño fijo esta operación se realiza mediante “slices” (rodajas), igual que en los arrays.
    put_line(sf1(1..5));

En los otros tipos de ristras, se ha de utilizar una función llamada “Slice”.
    function Slice ( Source : in Bounded_String; --Source también puede ser del tipo Unbounded_String
                     Inicio : in Positive;
                     Fin    : in Natural )
    return String;

    Move(Slice(sl1,1,5),sf2); --copia en sf2 la subristra formada por los caracteres de sl1 desde el 1 al 5

Hay que tener cuidado de que los límites del slice estén comprendidos dentro del rango de índices de la ristra, ya que si no se produciría un error.

Concatenación

    Se utiliza como operador de concatenación el operador “&”, que funciona entre todo tipo de ristras y entre ristras y caracteres.
    sl2 := To_Bounded_String("algo ");
    sl1 := sl2 & "nada"; --sl1 toma el valor “algo nada”

Con las ristras de tamaño limitado se puede utilizar la función “append”:
    function Append (Left, Right : in Bounded_String;
                     Drop        : in Truncation  := Error )
    return Bounded_String;

En este caso, la función devuelve la ristra resultante de concatenar “Left” y “Right”. El parámetro “Drop” indica qué hacer en caso de que la ristra resultante exceda el tamaño máximo permitido. Los valores posibles de Drop son: “Error” (produce un error), “Left” (trunca la ristra por la izquierda) y “Right” (trunca la ristra por la derecha). Existen versiones para concatenar ristras de tamaño limitado con ristras de tamaño fijo o con caracteres.
Con las ristras de tamaño dinámico se puede utilizar el procedimiento “append”:
    procedure Append (Source   : in out Unbounded_String;
                      New_Item : in Unbounded_String);

En este caso, no se necesita el parámetro “Drop”, ya que el tamaño de las ristras no está límitado. Existen versiones de “Append” para concatenar una ristra de tamaño dinámico con una ristra de tamaño fijo o con un carácter, en este orden.

Localización de una subristra

    Se realiza mediante la función “Index”.
    function Index ( Source   : in String;  --Source también puede ser del tipo Bounded_String o Unbounded_String
                     Pattern  : in String )
    return Natural;

 s1,s2 : string(1..5);
   s3  : string(1..10);
   ...
   s1 := "12345";
   s2 := "abcde" ;
   s3 := s1 & s2;
   put(Index(s3,"45")); --Se escribe un 4

Operadores relacionales

Los operadores relaciones (=, /=, <, >, <=, >=) son aplicables entre ristras atendiendo al alfabeto utilizado.

Más sobre ristras…

Los paquetes “Ada.Strings.Fixed”,  “Ada.Strings.Bounded” y “Ada.Strings.UnBounded” contienen un gran número de otras operaciones para el tratamiento de ristras de tamaño fijo, limitado y dinámico respectivamente.

Excepciones.

Introducción.

En la ejecución de un programa pueden darse muchas situaciones inesperadas, bien errores, bien casos muy particulares no previstos. Tener en cuenta todas las posibles situaciones anómalas oscurecería los algoritmos. El mecanismo de manejo de excepciones permite controlar estas situaciones sin tener que cargar con ese efecto indeseado.
Normalmente, la ocurrencia de un error causa la terminación del programa con un mensaje indicando la excepción ocurrida. En el estándar de Ada están definidas las siguientes excepciones:

Constraint_error.- ocurre cuando se intenta asignar a una variable un valor no válido, o cuando se intenta acceder a una posición de un array fuera del rango permitido.
Program_error.- ocurre en situaciones extrañas cuando parte de un programa no es accesible, o cuando se alcanza el “end” de una función sin encontrar un “return”.
Storage_error.- ocurre cuando se agota la memoria disponible.
Tasking_error.- está relacionado con errores en programas que utilicen programación concurrente.

Las excepciones se lanzan automáticamente cuando se produce un error. También pueden ser lanzadas manualmente mediante una sentencia “raise” en cualquier lugar de un programa.
raise Constrain_error; --produce la excepción Constrain_error

No es normal lanzar manualmente una excepción predefinida; la utilidad de “raise” consiste en lanzar excepciones definidas por el programador.

Declaración de excepciones.

La sintaxis de declaración de excepciones es igual a la de declaración de variables (aunque una excepción NO es una variable).
MiExcepción : exception;

Manejo de excepciones.

Cuando ocurre una excepción se puede:

  1. Capturarla
  2. Ignorarla

Si se captura, entonces cabe:

  1. Controlarla, e intentar que el programa continúe su ejecución.
  2. Reenviarla a otra parte del programa.

La captura de excepciones se realiza en una sección que se inicia con la palabra “exception” y se puede situar al final de cada bloque (begin..end). Dentro de esta sección se utilizan sentencias “when NombreExcepción” para capturar las distintas excepciones y situar el código que realiza el tratamiento de las mismas.
Si se quiere relanzar la excepción, basta con poner la sentencia “raise”. Si se desea, se puede lanzar una nueva excepción con “raise NombreExcepción“.
begin
  ...
exception
  when E1 =>
    ...
  when E2|E3|...|En =>
    ...
  when others =>
    ...
end;

Excepciones de entrada/salida.

En el paquete Text_IO se definen las siguientes excepciones relacionadas con las operaciones de entrada/salida.

Status_Error.- ocurre cuando se intenta leer o escribir en un fichero que no está abierto, o abrir un fichero que está abierto.
Mode_Error.- ocurre cuando se intenta leer de un fichero que está abierto para escritura o escribir en un fichero que está abierto para lectura.
Name_Error.- ocurre cuando se intenta abrir un fichero y el nombre externo es incorrecto.
Use_Error.- ocurre cuando se intenta abrir un fichero para un uso ilegal (p.e. si se intenta crear un fichero con un nombre externo que ya existe).
Device_Error.- ocurre cuando se produce un fallo técnico en un dispositivo de entrada/salida.
End_Error.- ocurre cuando se intenta leer de un fichero en el que se ha alcanzado la marca de fin de fichero.
Data_Error.- ocurre cuando se intenta leer un valor entero, real o enumerado y los datos de entrada tienen un formato incorrecto.

Ejemplo.

with Text_IO;
use Text_IO;

procedure Excepciones is
 package Entero_IO is new Integer_IO(Integer);
 package Real_IO is new Float_IO(Float);
 use Entero_IO, Real_IO;

 División_por_cero: exception; --declaración de excepción

 procedure Leer(x,y: out integer) is
 begin
  loop
    begin
      put("Deme dos números enteros: ");
      get(x); get(y);
      exit;
    exception
      when Data_error => --se controla la excepción y se
                           --recupera la ejecución del programa
         put_line("Por favor, teclee correctamente");
         skip_line;
    end;
  end loop;
 end Leer;

 function Divide(x,y: in integer) return float is
   r: float;
 begin
   r := float(x) / float(y);
   return r;
 exception
   when Constraint_error =>   --se relanza una excepción más apropiada
     raise División_por_cero;
 end Divide;

 a, b: integer;
begin
   Leer(a,b);
   put("El cociente es: "); put(Divide(a,b));new_line;
exception
   when División_por_cero =>  --se deja que el programa acabe de forma controlada
      new_line;
      put_line("    No se puede dividir por cero");
end Excepciones;

Ficheros de texto.

Introducción.

Un fichero de texto es básicamente una secuencia de caracteres, algunos de los cuales se pueden interpretar como marcas (fin de línea, fin de página, fin de fichero).

Cláusula de contexto.

Los elementos necesarios para manejar ficheros de texto se encuentran en el paquete “Text_IO”, que habrá que incluir en la cláusula de contexto.
    with Text_IO;
    use Text_IO;

Declaración

Para poder usar ficheros de texto se deben declarar en el programa variables de tipo “File_Type”. Estas variables se conocen como ficheros lógicos, o simplemente ficheros, en oposición a los ficheros externos a los que hacen referencia. El programa manipula los ficheros externos a través de los ficheros lógicos.
    Fichero1 : File_Type;
    Fichero2 : Text_IO.File_Type;

Apertura

Antes de poder hacer transacciones con una variable fichero debe asociarse con un fichero externo; esto se puede hacer con los procedimientos: “Create” y “Open”. El primero sirve para crear un fichero y el segundo para abrir un fichero que ya existe.
    procedure Create (File : in out File_Type;
                      Mode : in File_Mode := Out_File;
                      Name : in String    := "";
                      Form : in String    := "");

    procedure Open   (File : in out File_Type;
                      Mode : in File_Mode;
                      Name : in String;
                      Form : in String := "");

  • File es la variable fichero (fichero lógico).
  • Mode es el modo de apertura (puede tomar los valores: “In_File”, “Out_File”, “Append_File”).
    • In_File abre el fichero en modo lectura.
    • Out_File abre el fichero en modo escritura (si el fichero ya existe, se vacía).
    • Append_File abre el fichero en modo escritura para añadir texto al final de un fichero existente.
  • Name es el nombre del fichero externo (no aparecerá en ningún otro sitio del programa).
  • Form no se usa generalmente; es un parámetro que depende del sistema y puede servir para cosas como proteger el fichero con una password.

    Create(Fichero1,Name => "C:\TEMP\unfichero.txt");
    Open(Fichero2, In_File, "C:\TEMP\otrofichero.txt");

En el ejemplo, la primera sentencia crea y abre en modo escritura un fichero externo llamado "C:\TEMP\unfichero.txt"  y lo asocia con la variable “Fichero1″. La segunda sentencia abre en modo lectura un fichero externo llamado "C:\TEMP\otrofichero.txt" y lo asocia con la variable “Fichero2″.
Los errores que pudieran producirse al intentar abrir un fichero pueden controlarse mediante excepciones.
Existe una función para ver si un fichero está abierto:
    function  Is_Open(File : in File_Type) return Boolean;

Por ejemplo: “Is_Open(Fichero1)” devuelve True si el fichero “Fichero1″ está abierto y “False”, si no.
Existen funciones similares para obtener el modo de apertura y nombre de un fichero abierto.
    function  Mode   (File : in File_Type) return File_Mode;
    function  Name   (File : in File_Type) return String;

Cierre

Una vez que se ha terminado de trabajar con un fichero se debe cerrar con la operación “Close”.
    procedure Close  (File : in out File_Type);

    Close(Fichero1); --cierra Fichero1

Transferencia de información

Para realizar lecturas y escrituras desde/a ficheros se utilizan las ya conocidas operaciones “get”, “put”, “get_line”, “put_line”, con un primer parámetro que es la variable fichero de la que se va a leer o escribir.
    get_line(Fichero1,S,L);
    put_line(Fichero2,S(1..L));

El texto leído de Fichero1 en la variable S  se copia en Fichero2. Se supone que S es una ristra de tamaño fijo y L es una variable de tipo Natural.

Estructura del fichero

Existen operaciones para controlar las marcas del fichero en relación a:

  • Líneas: “New_Line”, “Skip_Line” y “End_Of_Line”.
    • New_Line(Fichero1)   --Inicia una nueva línea en Fichero1
    • Skip_Line(Fichero1)  --Salta hasta después de la próxima marca de fin de línea
    • End_Of_Line(Fichero1)--Es una función que devuelve True si se ha alcanzado el fin
                           --de la línea, y False en caso contrario
  • Páginas: “New_Page”, “Skip_Page” y “End_Of_Page”
    • New_Page(Fichero1)   --Inicia una nueva página en Fichero1
    • Skip_Page(Fichero1)  --Salta hasta después de la próxima marca de fin de página
    • End_Of_Page(Fichero1)--Es una función que devuelve True si se ha alcanzado el final
                           --de la página y False en caso contrario
  • Ficheros: “End_Of_File”
    • End_Of_File(Fichero1) es una función que devuelve True si se ha alcanzado el final de fichero1 y False en caso contrario.

Existen funciones para configurar el tamaño de la línea y el tamaño de la página.

Eliminación de un fichero externo

Se realiza mediante la operación “Delete”.
     procedure Delete (File : in out File_Type);

“Delete” borra el fichero externo asociado con la variable fichero que se le pase y cierra ésta.

Además…

En el anexo A.10 del “Ada 95 reference manual” incluido en la ayuda de ObjectAda 7.1 se puede encontrar más información sobre manipulación de ficheros de texto.

Ejemplo

with Text_Io;
use Text_Io;

--El procedimiento Copiar_Fichero copia un fichero de texto, llamado "original.txt"
--en otro llamado "copia.txt"
--precondiciones: debe existir un fichero externo llamado "original.txt"
--                no debe existir un fichero externo llamado "copia.txt"
--postcondiciones: Existe un fichero externo llamado "copia.txt" cuyo contenido es
--                 idéntico al de "original.txt"
procedure Copiar_Fichero is
    F_Entrada,F_Salida: File_Type;
    Línea             : String(1..200);
    Lon_Línea         : Natural;
begin
    --se abren los ficheros
    Open(F_Entrada,Mode => In_File,Name => "original.txt");
    Create(F_Salida,Name => "copia.txt");
    --se copia F_Entrada en F_Salida
    while not End_Of_File(F_Entrada) loop
        Get_Line(F_Entrada,Línea,Lon_Línea);
        Put_Line(F_Salida,Línea(1..Lon_Línea));
    end loop;
    --se cierran los ficheros
    Close(F_Entrada);
    Close(F_Salida);
end Copiar_Fichero;

Ficheros uniformes.

Introducción

Un fichero uniforme es una secuencia arbitrariamente larga de elementos del mismo tipo. En Ada se distinguen ficheros secuenciales y ficheros de acceso directo. (Un fichero de texto es básicamente un fichero secuencial de caracteres).

Cláusula de contexto

Los elementos necesarios para manejar ficheros secuenciales se encuentran en el paquete “Sequential_IO”, los ficheros de acceso directo se manejan con el paquete “Direct_IO” que habrá que incluir en la cláusula de contexto.
    with Text_IO,Sequential_IO,Direct_IO;
    use Text_IO;

Declaración

Para poder usar ficheros uniformes se ha de definir previamente el tipo de sus componentes (a menos que sea de un tipo predefinido)
type TPersona is
 record
    Nombre : string(1..20);
    DNI    : string(1..10);
    Edad   : natural;
 end record;

Una vez definido el tipo, se han de definir instancias de los paquetes de manejo de ficheros, según el tipo de acceso que se vaya a utilizar.
    package FichTPersona_Sec is new Sequential_IO(TPersona); -- Acceso secuencial
    package FichTPersona_Dir is new Direct_IO(TPersona); -- Acceso directo
    use FichTPersona_Sec,FichTPersona_Dir;

Ahora se pueden declarar variables de tipo fichero directo o secuencial:

  Fichero1 : FichTPersona_Sec.File_Type;
  Fichero2 : FichTPersona_Dir.File_Type;

Apertura

Antes de poder hacer transacciones con una variable fichero debe asociarse con un fichero externo; esto se puede hacer con los procedimientos: “Create” y “Open”. El primero sirve para crear un fichero y el segundo para abrir un fichero que ya existe.
    procedure Create (File : in out File_Type;
                      Mode : in File_Mode := Out_File; --InOut_File en acceso directo
                      Name : in String    := "";
                      Form : in String    := "");

    procedure Open   (File : in out File_Type;
                      Mode : in File_Mode;
                      Name : in String;
                      Form : in String := "");

  • File es la variable fichero (fichero lógico).
  • Mode es el modo de apertura (puede tomar los valores: “In_File”, “Out_File”, “Append_File”, “InOut_File”).
    • In_File abre el fichero en modo lectura.
    • Out_File abre el fichero en modo escritura (si el fichero ya existe, se vacía).
    • Append_File abre un fichero secuencial en modo escritura para añadir información al final.
    • InOut_File abre un fichero de acceso directo, permitiendo realizar lecturas y escrituras.
  • Name es el nombre del fichero externo (no aparecerá en ningún otro sitio del programa).
  • Form no se usa generalmente; es un parámetro que depende del sistema y puede servir para cosas como proteger el fichero con una password.

    Create(Fichero1,Name => "C:\TEMP\unfichero.dat");
    Open(Fichero2,InOut_File,"C:\TEMP\otrofichero.dat");

En el ejemplo, la primera sentencia crea y abre en modo escritura un fichero secuencial externo llamado "C:\TEMP\unfichero.dat"  y lo asocia con la variable “Fichero1″. La segunda sentencia abre en modo lectura/escritura un fichero externo de acceso directo llamado "C:\TEMP\otrofichero.dat" y lo asocia con la variable “Fichero2″.
Los errores que pudieran producirse al intentar abrir un fichero pueden controlarse mediante excepciones.
Existe una función para ver si un fichero está abierto:
    function  Is_Open(File : in File_Type) return Boolean;

Por ejemplo: “Is_Open(Fichero1)” devuelve True si el fichero “Fichero1″ está abierto y “False”, si no.
Existen funciones similares para obtener el modo de apertura y nombre de un fichero abierto.
    function  Mode   (File : in File_Type) return File_Mode;
    function  Name   (File : in File_Type) return String;

Cierre

Una vez que se ha terminado de trabajar con un fichero se debe cerrar con la operación “Close”.
    procedure Close  (File : in out File_Type);

    Close(Fichero1); --cierra Fichero1

Transferencia de información

Para realizar lecturas y escrituras desde/a ficheros se han de declarar previamente variables del tipo de los componentes del fichero.
    Persona_1,Persona_2,Persona_3 : TPersona;

Las operaciones de entrada/salida se realizan con las operaciones “Read” y “Write”.
    procedure Read(File : in out File_Type; Item : out Element_Type;
                                            From : in  Positive_Count);
    procedure Read(File : in out File_Type; Item : out Element_Type);

Read lee un elemento de un fichero y avanza una posición:

  • File es la variable fichero.
  • Item es la variable, del tipo de los componentes del fichero, en la que se lee el elemento.
  • From es la posición en el fichero desde donde se quiere leer el dato. Sólo es aplicable a ficheros de acceso directo. Si no se especifica se lee de la posición actual en el fichero.

    procedure Write(File : in out File_Type; Item : in Element_Type;
                                             To   : in Positive_Count);
    procedure Write(File : in out File_Type; Item : in Element_Type);

Write escribe un elemento en un fichero y avanza una posición:

  • File es la variable fichero.
  • Item es la variable, del tipo de los componentes del fichero, cuyo contenido se escribe en el fichero
  • To es la posición en el fichero donde se quiere escribir el dato. Sólo es aplicable a ficheros de acceso directo. Si no se especifica se escribe en la posición actual en el fichero.

Estructura del fichero

Existe una operación para controlar la marca de fin de fichero:

  • End_Of_File(Fichero1) es una función que devuelve True si se ha alcanzado el final de Fichero1 y False en caso contrario.

Operaciones con ficheros de acceso directo

El tamaño, en número de componentes, de un fichero de acceso directo se obtiene mediante la función “Size”, que devuelve un número comprendido entre 0 y el máximo tamaño posible definido en la implementación del compilador de Ada que se esté usando.
    function Size(File : in File_Type) return Count;

Se puede saltar a una posición específica de un fichero utilizando el procedimiento “Set_Index”
    procedure Set_Index(File : in out File_Type; To : in Positive_Count);

La posición actual en el fichero se puede averiguar mediante la función “Index”, que devuelve un valor comprendido entre 1 y el máximo tamaño posible definido en la implementación.
    function Index(File : in File_Type) return Positive_Count;

Eliminación de un fichero externo

Se realiza mediante la operación “Delete”.
     procedure Delete (File : in out File_Type);

“Delete” borra el fichero externo asociado con la variable fichero que se le pase y cierra ésta.

Además…

En los anexos A.7, A.8 y A.9 del “Ada 95 reference manual” incluido en la ayuda de ObjectAda 7.1 se puede encontrar más información sobre manipulación de ficheros.

Punteros.

Introducción

Los tipos puntero representan direcciones de memoria y son un importante mecanismo para llevar a cabo una gestión dinámica de la memoria, así como para crear estructuras de datos dinámicas complejas.

Declaración

En Ada los punteros se denominan “accesos”. Para declarar punteros que puedan referenciar a variables dinámicas se utiliza la palabra reservada access.
    type TPersona is record
        Nombre : string(1..20);
        Apellido1 : string(1..20);
        Apellido2 : string(1..20);
    end record;
    type TP_Persona is access TPersona; --Tipo de los punteros a TPersona dinámicos
    type TP_Integer is access integer; --Tipo de los punteros a integer dinámicos

    Pint1, Pint2 : TP_Integer; --Punteros a integer dinámico
    PPer1, PPer2 : TP_Persona; --Punteros a TPersona dinámicos
    i : integer; --Variable estática de tipo integer
    p : TPersona; --Variable estática de tipo TPersona

Los punteros se inicializan automáticamente con el valor null. indicando que no referencian a ninguna variable.

Ubicación dinámica

Para crear una variable dinámica se utiliza la palabra reservada new seguida del tipo de la variable que se quiere crear:
    Pint1 := new integer; --Se crea una variable dinámica de tipo integer
    PPer1 := new TPersona; --Se crea una variable dinámica de tipo TPersona

Las variables creadas dinámicamente se pueden inicializar en su creación:
    Pint2 := new integer'(35);
    PPer2 := new TPersona'("Pepe      ","Pérez     ","Pérez     ");

Además, una variable puntero puede ser inicializada en su declaración:
    Pint : TP_Integer := new integer'(35);

Acceso a las variables referenciadas por los punteros

Para acceder o modificar el contenido de una variable referenciada por un puntero, se utiliza el cualificador all:
    i := Pint2.all; --Se asigna a i el valor de la variable referenciada por Pint2
    PPer1.all := PPer2.all;
      --Se asigna a la variable referenciada por PPer1 el valor
      --de la variable referenciada por PPer2
    Pint1 := Pint2; --Pint1 y Pint2 referencian a la misma variable
    i := Pint1; --ERROR, i es de tipo integer y Pint es de tipo puntero

Cuando la variable referenciada es un record se puede acceder a cada campo utilizando simplemente el cualificador punto (.):
    p.Nombre := PPer1.Nombre;
    p.Apellido1 := PPer1.all.Apellido1; --El .all es inncesario

Si la variable referenciada es un array se puede acceder a sus elementos individuales utilizando paréntesis del modo habitual:
    type TArray is array (integer range <>)  of integer;
    type TP_Array is access TArray;
    v : TP_Array := new TArray'(1,2,3,4,5);
    ...
    i := v(3);

Liberación de variables dinámicas

En Ada, una variable dinámica se libera automáticamente cuando finaliza la ejecución de la parte del programa donde está definido el tipo puntero correspondiente. Por tanto, para realizar un control más eficiente de la memoria es necesario emplear operaciones de liberación explícita de las variables creadas dinámicamente cuando ya no sean necesarias. Para ello se necesita, primero, incluir “Unchecked_Deallocation” en la cláusula de contexto:
    with Text_io, Unchecked_Deallocation;

A continuación, se debe instanciar el procedimiento genérico “Unchecked_Deallocation” para el tipo de dato y tipo de puntero que se quiere liberar:
    procedure Libera_Persona is new Unchecked_Deallocation(TPersona,TP_Persona);

Ahora, se puede usar Libera_Persona para liberar variables dinámicas de tipo TPersona referenciadas por variables puntero de tipo TP_Persona:
    Libera_Persona(PPer1);

Punteros que apuntan a variables estáticas

Las variables puntero almacenan direcciones. Todas las variables tienen una dirección; sería lógico querer que en una variable puntero a un tipo T se pueda almacenar la dirección no sólo de variables de tipo T creadas dinámicamente, sino también de variables estáticas de tipo T. En Ada los punteros sólo pueden almacenar direcciones de variables dinámicas, a menos que se declaren “access all”:
    type PGen_TPersona is access all TPersona;
        --Las variables de tipo PGen_TPersona pueden referenciar
        --variables de tipo TPersona, tanto estáticas como dinámicas
    PGPer : PGen_TPersona;

Para que la dirección de una variable estática pueda ser asignada a un puntero, ésta debe  permitirlo en su declaración, utilizando la palabra aliased:
    p1: aliased TPersona;

La dirección de una variable declarada así puede obtenerse mediante el atributo access:
    PGPer := p1'access; --Asigna a PGPer la dirección de p1

Se puede imponer restricción de lectura a un puntero, de forma que no se pueda modificar el objeto al que apunta, pudiendo entonces utilizarse para referenciar una constante, o para referenciar una variable de forma segura, con la garantía de que no podrá modificarse a través del puntero:
    type Punt_Const_Int is access constant integer; --Tipo de los punteros a enteros constantes
    ...
    type Punt_Int is access all integer; --Tipo de los punteros a variables integer (estáticos o dinámicos)
    PCI1,PCI2 : Punt_Const_Int;
    Pint3 : Punt_Int;
    i : aliased integer;
    ...
    PCI1 := i'access;
    Pint3 := i'access; --PCI1 y Pint3 son alias de i
    i := 3;
    Pint3.all := 4; --Modifica i
    PCI1.all := 5; --ERROR, no se puede modificar i a través de PCI
    PCI2 := new integer'(9); --Se puede hacer new siempre que se inicialice

Declaraciones incompletas de tipos

Una aplicación frecuente de los punteros es la creación de estructuras de datos dinámicas, por ejemplo listas encadenadas. Este tipo de estructuras están formadas por un conjunto de nodos conectados mediante campos de enlace que son punteros a nodos del mismo tipo. Para estos nodos sería necesaria una definición de tipo tal como:
    type NodoLista is record
        Info: integer;
        Siguiente: PNodoLista;
    end record;

donde PNodoLista es el tipo de los punteros a objetos de tipo NodoLista. Esto significa que debería existir una definición para PNodoLista:
    type PNodoLista is access NodoLista;

Esta definición debería estar antes que la de NodoLista, para que sea conocida cuando se declara el campo siguiente de éste; pero entonces NodoLista no se conocería cuando se intenta definir PNodoLista, creándose un círculo vicioso. La solución consiste en empezar con una declaración incompleta de NodoLista que establezca que tal tipo va a existir, pero sin concretarlo, cosa que debe hacerse más adelante en la misma parte del programa. Una declaración incompleta comienza igual que una definición completa, pero termina con un punto y coma (;) donde ésta tiene la palabra is:
    type NodoLista;
    type PNodoLista is access NodoLista;
    type NodoLista is record
        Info: integer;
        Siguiente: PNodoLista;
    end record;

Estructura general de un programa en Ada.

Formalmente un programa en Ada se estructura en unidades distribuidas en uno o varios ficheros. Las unidades pueden ser:

  • subprogramas.- definen algoritmos y se clasifican en procedimientos y funciones.
  • paquetes (package).- definen módulos que son colecciones de entidades (tipos, constantes, subprogramas, …).
  • tareas (task) .- definen acciones que pueden ejecutarse en paralelo con otras.
  • unidades protegidas (protected unit).- sirven para coordinar la compartición de datos entre tareas concurrentes.
  • unidades genéricas.- sirven para definir componentes reusables.

Paquetes.

Los paquetes son la clase de unidad más importante. En general un programa en Ada se compone de un conjunto de paquetes y un procedimiento principal. La claúsula de contexto “with” sirve para declarar que se van a usar los servicios de un paquete.

Un paquete se divide generalmente en dos partes: especificación  e implementación (body). Ambas partes se sitúan casi siempre en ficheros diferentes, con las extensiones “*.ads” y “*.adb”, respectivamente.

La estructura de la especificación de un package es la siguiente:

    package nombre_de_la_unidad is
        --declaraciones
    private
        --declaraciones privadas
    end nombre_de_la_unidad;

Las declaraciones que aparecen antes de la palabra “private” constituyen la parte visible, o interfaz, del paquete. Las que aparecen después son privadas; no obstante, la parte privada es opcional, y si no aparece la palabra “private” se entiende que el paquete no tiene parte privada.

Las declaraciones pueden ser desde declaraciones de tipos hasta prototipos de procedimientos o funciones. Un prototipo de un procedimiento o función es su cabecera, pero cambiando la palabra “is” por un punto y coma (“;”), lo que indica que el subprograma se desarrollará más tarde (en la implementación del paquete).

La declaración de un paquete requiere una implementación si hay declaraciones que requieran completarse (por ejemplo, el prototipo de una función requiere su desarrollo). La estructura de un “package body” es:

    package body nombre_de_la_unidad is
        --desarrollo del package
    end nombre_de_la_unidad;

Todas las declaraciones hechas en la especificación del paquete se pueden usar en la implementación (body), aunque estén en la parte “private”. Las declaraciones hechas en el “package body” sólo se pueden usar en el mismo.

Tipos “private” y “limited private”.

Si se declara un tipo en la especificación de un paquete, cualquiera que utilice el paquete podrá acceder a los elementos internos de la implementación del mismo. Esto hace que la aplicación se vuelva dependiente de la forma en que se ha implementado el nuevo tipo. Para evitar esta dependencia –y poder cambiar la implementación si es necesario– se declara el tipo como “private” en la parte pública del paquete y se pone su definición en la parte privada. Al declarar un tipo como “private” su definición queda oculta, y el usuario del paquete sólo podrá utilizar con él las operaciones que se hallan declarado en la parte pública del paquete, además de la asignación (:=), la comparación de igualdad (=) y la de desigualdad (/=).

    package Manejo_De_Claves is
        type Clave is private;
        Clavenula : constant Clave;
        procedure Tomar_Clave(C : out Clave);
        function "<"(X, Y : in Clave) return Boolean;
    private
        Max : constant := 2 ** 16 - 1;
        type Clave is range 0 .. Max;
        Clavenula : constant Clave := 0;
    end Manejo_De_Claves;

Huelga decir que el paquete Manejo_De_Claves necesita un “package body” para completar su definición.

    package body Manejo_De_Claves is
      procedure Tomar_Clave(C : out Clave) is
      begin
          -- Cuerpo del procedimiento
      end Tomar_Clave;

      function "<"(X, Y : in Clave) return Boolean is
      begin
          -- Cuerpo del operador
      end "<";
end Manejo_De_Claves;
Si además se quiere deshabilitar las operaciones de asignación y comparación de igualdad/desigualdad, habrá que declarar el tipo como “limited private“.

Unidades genéricas en Ada.

Ada permite crear unidades genéricas, es decir, con parámetros que se pueden concretar para diferentes instancias de la unidad. Para ello basta anteceder la unidad correspondiente con la palabra “generic”; los parámetros formales de la unidad se sitúan en la zona comprendida entre la palabra “generic” y el comienzo de la unidad propiamente dicha. Vemos un ejemplo para el caso de un procedimiento genérico:
    ...
    generic
        type TElemento is private;
    procedure Algo(Elemento:in TElemento) is
        --Declaraciones
    begin
        --Acciones
    end Algo;

Para utilizar el procedimiento “Algo” primero se deberán crear instancias para los tipos apropiados:
    ...
    procedure Algo_entero is new Algo(TElemento=>integer);
    procedure Algo_carácter is new Algo(character);

Ahora, ya se pueden invocar los procedimientos “Algo_entero” y “Algo_carácter”, según convenga:
    Algo_entero(30);
    Algo_carácter('a');

Paquetes genéricos

Los paquetes genéricos en Ada son los que soportan la genericidad de los tipos abstractos de datos (tad) en este lenguaje. El formato general de un paquete genérico es, en la especificación del paquete:
    generic
        --Zona de declaración de parámetros formales
    package nombre_del_paquete_genérico is
        --Zona de uso de los parámetros formales
    end nombre_del_paquete_genérico;

En la implementación del paquete:
    package body nombre_del_paquete_genérico is
        --Zona de uso de los parámetros formales
    end nombre_del_paquete_genérico;

Los parámetros formales se pueden emplear en la implementación del paquete (package body) sin más requerimientos.

La forma de usar un paquete genérico es crear una instancia del paquete genérico en la que se especifiquen todos los parámetros formales requeridos. Esta sentencia tiene la forma:
    package nombre_instancia is new nombre_del_paquete_genérico(parámetros_actuales,...);

Parámetros formales

Los parámetros formales pueden ser: objetos, tipos o subprogramas. Al igual que los parámetros de los subprogramas, los parámetros de un paquete genérico pueden tener un valor por defecto. Los parámetros formales se declaran de distinta forma según su naturaleza:

  • Tipos privados: “private”, “limited private”, “tagged private” y “tagged limited private”. La definición del tipo privado corresponde, en este caso, al llamador –no va a aparecer en la parte private, si estamos desarrollando un paquete–, (type Tipo is private;).
  • Tipos escalares:
    • Discretos. Se utiliza el símbolo “<>”, (type TDiscreto is <>;).
    • Enteros con signo. Se utiliza “range <>”, (type TEntero is range <>;).
    • Modulares. Se utiliza “mod <>”, (type TModular is mod <>;).
    • Reales en coma flotante. Se utiliza “digits <>”, (type TRealFlotante is digits <>;).
    • Reales en coma fija. Se utiliza “delta <>”, (type TRealFijo is delta <>;).
    • Decimales. Se utiliza “delta <> digits <>”, (type TDecimal is delta <> digits <>;).
  • Arrays. Hay que incluir como parámetros, el tipo de los elementos del array, el tipo del índice del array y el tipo array:
    generic
     type TElemento is private;
     type Índice is (<>);
     type Vector is array (Índice range <>) of TElemento;
    package P is
     ...
    end P;
  • Punteros. Hay que especificar el tipo puntero y el tipo apuntado:
    generic
     type TNodo is private;
     type TP_Nodo is access TNodo;
    package P is
     ...
    end P;
  • Subprogramas. Se utiliza la palabra “with” precediendo al protocolo del subprograma que se espera:
    generic
     type TElemento is private;
     with procedure Acción(X : in TElemento);
    procedure Iterar(Seq : in Secuencia_de_TElemento);
    ...
    procedure Asignar_Elemento(X : in Item);
    ...
    -- Es posible la siguiente instancia
    procedure Asignar_Lista is
                  new Iterar(TElemento => Item,
                              Acción => Asignar_Elemento);
    Se puede especificar un nombre por defecto para el subprograma pasado por parámetro, utilizando “is nombre”:
     with procedure Acción (X : in TElemento) is Escribir;

Si se quiere que el nombre por defecto sea el mismo que el del parámetro formal, se pondrá como nombre “<>”:
 with procedure Acción (X : in TElemento) is <>;

En caso de especificar un nombre por defecto se podrá omitir el parámetro al instanciar la unidad.

  • Paquetes. Se utiliza la palabra “with” precediendo a la instanciación del paquete que queremos como parámetro formal.
    with package Nombre_formal is new Nombre_genérico Parte_formal_real;

Ejemplo:

El siguiente código muestra la declaración, implementación y uso de un paquete genérico para manejar listas ordenadas.
Fichero de especificación (*.ads)
-- Se declara un paquete genérico para manejar listas ordenadas
-- Se pasan como parámetros:
--    El Tamaño máximo de la Lista (por defecto 10)
--    El tipo de dato que se va a almacenar en la Lista
--    Una operación de comparación
--    Una acción aplicable a un elemento de la Lista

  generic
      Tamaño : Natural := 10; -- Tamaño por defecto
      type Telemento is private; -- Tipo de dato a almacenar.
        -- El paquete no necesita conocer de Telemento mas que
        -- las operaciones que vienen a continuación
      with function "<"(X,Y: in Telemento) return Boolean is <>;
        -- Operación de comparación de elementos
      with procedure Accion(X: in Telemento);
        -- Operación que se aplica a un elemento

  package Lista_Ordenada is
      type Tlista is private; -- La implementación de la lista queda oculta
      procedure Insertar(X: in Telemento; L: in out Tlista);
        -- Inserta un elemento en la lista
      procedure Extraer(I: in Natural; L: in out Tlista);
        -- Extrae un elemento de la lista
      procedure Iterar(L: in Tlista);
        -- Recorre la lista aplicando una operación a cada elemento

  private  -- Estructura de datos de la lista
      type Contenedor is array (1..Tamaño) of Telemento;
      type Tlista is record
         Elementos : Contenedor;
         Num_Elementos : Natural := 0;
      end record;
  end Lista_Ordenada;

Fichero de implementación (*.adb)
-- Implementación de las operaciones de la lista ordenada

package body Lista_Ordenada is

  procedure Insertar(X: in Telemento; L: in out Tlista) is
      I : Natural;
  begin
      I := 1;
      -- Esquema de búsqueda para la localización de la posición
      while I <= L.Num_Elementos and then L.Elementos(I) < X loop
          I := I + 1;
      end loop;
      L.Num_Elementos := L.Num_Elementos + 1;--Posible Constrain Error
      if I < L.Num_Elementos then
          -- Se desplazan los elementos para insertar el nuevo
          for J in reverse I+1..L.Num_Elementos loop
              L.Elementos(J) := L.Elementos(J-1);
          end loop;
      end if;
      L.Elementos(I) := X;
  end Insertar;

  procedure Extraer(I: in Natural; L: in out Tlista) is
  begin
      -- Se desplazan los elementos para cubrir el hueco de la extracción
      for J in I..L.Num_Elementos-1 loop
          L.Elementos(J) := L.Elementos(J+1);
      end loop;
      L.Num_Elementos := L.Num_Elementos - 1;--Posible Constrain Error
  end Extraer;

  procedure Iterar(L: in Tlista) is
  begin
      -- Esquema de recorrido sobre la lista
      for J in 1..L.Num_Elementos loop
         Accion(L.Elementos(J));-- Se aplica la acción pasada como parámetro
      end loop;
  end Iterar;

 end Lista_Ordenada;

Fichero del programa principal (*.ada)
with Text_Io,Lista_Ordenada;
use Text_Io;

procedure Prueba_De_Contenedor is
    package Enteros_Io is new Integer_Io(Integer);
    use Enteros_Io;

    procedure Escribir_Elemento(X: in Integer) is
    begin
       Put(X);
       New_Line;
    end;
    -- Se crea una instancia del paquete genérico Lista_Ordenada, llamada
    -- Lista_Enteros, con un tamaño máximo de 20, los elementos que se almacenan
    -- son de tipo integer, la operación de comparación es el operador "<" de
    -- los integer (al no especificarlo se usa por defecto), como accion se pasa
    -- el procedimiento Escribir_Elemento
    package Lista_Enteros is new Lista_Ordenada(20,Integer,Accion => Escribir_Elemento);
    use Lista_Enteros ;

    L: Tlista;
begin
    for J in reverse 1..15 loop
        Insertar(J,L);
    end loop;
    Iterar(L);
end Prueba_De_Contenedor;

Apéndices

Entorno de Programación en Ada

Sistema de desarrollo:

Como entorno de desarrollo de programación en Ada 95 se empleará “ObjectAda for Windows“. Este entorno de desarrollo integra los siguientes componentes:

i) Compilador de Ada 95.
ii) Gestor de proyectos.
iii) Editor sensible a Ada 95.
iv) Depurador.

Al ejecutar el entorno “OA.exe” nos encontramos con: un menú principal, cuatro de las cinco barras de herramientas y la ventana dividida en dos secciones, el área de trabajo principal y la ventana de mensajes. La mayoría de las opciones seleccionables por menú nos las encontramos también en una de las cinco barras de herramientas: Estándar, Proyecto, Edición, Depurado, Examinar. Por defecto la barra de examinar no está visible. El área de trabajo es la zona de edición de ficheros, proyectos y objetivos, la ventana de mensajes está compuesta de tres secciones donde se muestra la salida de la ejecución de distintas herramientas del entorno.

Entorno OA

El entorno maneja ficheros, proyectos y objetivos. Un proyecto representa un conjunto de ficheros relacionados. Un objetivo (target) representa un subconjunto de ficheros usados para construir uno o más programas y las opciones de compilación y construcción usadas en el proceso. Los “targets” se pueden usar para controlar la forma en que se construyen los programas. Distintos “targets” pueden tener los mismos ficheros pero distintas opciones, o las mismas opciones y distintos ficheros.

Por defecto, un proyecto se crea con dos objetivos: “release” y “debug”. El primero está pensado para obtener programas definitivos, el segundo está pensado para ejecución del programa en una fase de pruebas.

Creación de un proyecto.

Para crear un nuevo proyecto es suficiente con escoger la opción “New” del submenú “File” y seleccionar “Project” en el dialogo que se muestra. El proyecto se creará en una carpeta, preferiblemente vacía, donde el entorno creará subcarpetas de trabajo. En el diálogo de creación de proyectos (New project) hay que especificar tanto el nombre del proyecto como la carpeta (Directory) donde queremos crearlo.

Una vez creado el proyecto se puede establecer con “Project|Files” qué ficheros lo forman. Estos ficheros deben existir en el momento de añadirlos al proyecto (cuando se crea un proyecto se despliega automáticamente el diálogo de “Project|Files”, pero como los ficheros del proyecto normalmente no existirán en ese momento, es mejor cerrarlo y volver a activarlo vía ménu cuando los mismos se hayan creado).

Para crear ficheros se escoge la opción “File|New”, igual que para crear el proyecto, pero ahora se deberá elegir “Code/Text” en el diálogo de creación.

Los ficheros donde se almacena el proyecto tienen la extensión “.prj” . Los ficheros fuente tienen extensiones “.ada”, “.adb” y “ads”. La extensión “.ada” corresponde al fichero que contiene el procedimiento principal del proyecto, que servirá como punto de inicio del programa.

Para ejecutar un programa es necesario compilar cada unidad y enlazarlas sin ningún error. Se utilizará la opción “Project|Compile” para compilar y “Project|Build” para enlazar. Con “Project|Execute” se ejecuta el programa. No debe olvidarse compilar y enlazar siempre que se hagan cambios.

Para trasladar, guardar o copiar un proyecto es suficiente con copiar los ficheros “.prj”, “.ada”, “.adb” y “ads”.

Operadores principales

Clasificación Operador Descripción Asociación
Delimitadores () Llamada a función De izquierda a derecha
() Elemento de un array
. Operador punto para los miembros de un estructura
Aritméticos ** Exponenciación De derecha a izquierda
abs Valor absoluto
NOT no lógico
* Multiplicación De izquierda a derecha
/ División
rem Resto
- Menos unario
= Suma unaria
& Concatenación de string De izquierda a derecha
+ Suma
- Sustracción
Relacionales = Igualdad lógica De izquierda a derecha
/= Desigualdad lógica
< Menor que
<= Menor o igual que
> Mayor que
>= Mayor o igual que
in Miembro de
not in No miembro de
Lógicos and Y lógico De izquierda a derecha
or O lógico
xor O exclusive
and then
or else
Asignación := Asignación simple De derecha a izquierda
Separador , Separador coma De izquierda a derecha

Palabras reservadas.

abort

and

case

digits

exception

goto

loop

of

pragma

record

select

then

with

abs

array

constant

do

exit

if

mod

or

private

rem

separate

type

xor

accept

at

declare

else

for

in

new

others

procedure

renames

subtype

use

access

begin

delay

end

function

is

not

out

raise

return

task

when

all

body

delta

entry

generic

limited

nul

package

reange

reverse

terminate

while

Funciones matemáticas elementales.

Existe un paquete generico llamado Ada.Numerics.Generic_Elementary_Functions que ofrece las siguientes funciones matemáticas:

 function Sqrt    (X           : Float_Type'Base)        return Float_Type'Base;
 function Log     (X           : Float_Type'Base)        return Float_Type'Base;
 function Log     (X, Base     : Float_Type'Base)        return Float_Type'Base;
 function Exp     (X           : Float_Type'Base)        return Float_Type'Base;
 function "**"    (Left, Right : Float_Type'Base)        return Float_Type'Base;

 function Sin     (X           : Float_Type'Base)        return Float_Type'Base;
 function Sin     (X, Cycle    : Float_Type'Base)        return Float_Type'Base;
 function Cos     (X           : Float_Type'Base)        return Float_Type'Base;
 function Cos     (X, Cycle    : Float_Type'Base)        return Float_Type'Base;
 function Tan     (X           : Float_Type'Base)        return Float_Type'Base;
 function Tan     (X, Cycle    : Float_Type'Base)        return Float_Type'Base;
 function Cot     (X           : Float_Type'Base)        return Float_Type'Base;
 function Cot     (X, Cycle    : Float_Type'Base)        return Float_Type'Base;

 function Arcsin  (X           : Float_Type'Base)        return Float_Type'Base;
 function Arcsin  (X, Cycle    : Float_Type'Base)        return Float_Type'Base;
 function Arccos  (X           : Float_Type'Base)        return Float_Type'Base;
 function Arccos  (X, Cycle    : Float_Type'Base)        return Float_Type'Base;
 function Arctan  (Y           : Float_Type'Base;
                   X           : Float_Type'Base := 1.0) return Float_Type'Base;
 function Arctan  (Y           : Float_Type'Base;
                   X           : Float_Type'Base := 1.0;
                   Cycle       : Float_Type'Base)        return Float_Type'Base;
 function Arccot  (X           : Float_Type'Base;
                   Y           : Float_Type'Base := 1.0) return Float_Type'Base;
 function Arccot  (X           : Float_Type'Base;
                   Y           : Float_Type'Base := 1.0;
                   Cycle       : Float_Type'Base)        return Float_Type'Base;

 function Sinh    (X           : Float_Type'Base)        return Float_Type'Base;
 function Cosh    (X           : Float_Type'Base)        return Float_Type'Base;
 function Tanh    (X           : Float_Type'Base)        return Float_Type'Base;
 function Coth    (X           : Float_Type'Base)        return Float_Type'Base;
 function Arcsinh (X           : Float_Type'Base)        return Float_Type'Base;
 function Arccosh (X           : Float_Type'Base)        return Float_Type'Base;
 function Arctanh (X           : Float_Type'Base)        return Float_Type'Base;
 function Arccoth (X           : Float_Type'Base)        return Float_Type'Base;
(cycle es las unidades que determinan una circunferencia completa. Si no se especifica se suponen radianes)

Forma de usarlas.

1) Cláusulas de contexto.

  • Incluir en una cláusula with: “Ada.Numerics.Generic_Elementary_Functions”
  • Incluir en una cláusula use: “Ada.Numerics”

2) Declaraciones.

  • Instanciar el paquete “Generic_Elementary_Functions” con el tipo real adecuado, e incluir la instancia en una claúsula use.

Ejemplo.

with Text_IO, Ada.Numerics.Generic_Elementary_Functions;
use TExt_IO, Ada.Numerics;

procedure UsaMat is
    x, y : float;
    package MiFloat_IO is new Float_IO(float);
    package Funciones is new Generic_Elementary_Functions(float);
    use MiFloat_IO, Funciones;
begin
    put("Dame un número real: ");
    get(x);
    y := sqrt(x);
    put("raíz cuadrada: ");
    put(y);
end UsaMat;

Conversión de tipos

Ada es un lenguaje muy estricto en la utilización de los tipos, y no permite nunca mezclar tipos diferentes de forma implícita. Sin embargo, puede haber situaciones en que sea necesario operar tipos diferentes en una expresión. Por ejemplo:

Media := SumaElementos / NumeroElementos;

Si SumaElementos  y NumeroElementos son variables integer y Media es float, tal expresión es incorrecta, dado que el resultado de la división es de tipo integer, y pretendemos asignársela a un float. Previamente, se deben convertir SumaElementos y NumeroElementos a float. Para ello basta hacer lo siguiente:

Media := float(SumaElementos) / float(NumeroElementos);

Nótese que:

Media := float(SumaElementos / NumeroElementos);

no daría el mismo resultado.

En general, para convertir de un tipo numérico a otro basta con poner:

Nuevo_Tipo(Expresión_tipo_original)

Cuando una expresión real se convierte a un tipo entero, se redondea el resultado al entero más cercano: “integer(2.85)” da como resultado “3″. Si no es esto lo que queremos, podemos utilizar las siguiente funciones atributo de los reales en coma flotante:
(Sea FT un tipo real en coma flotante, x una variable de tipo FT y el resultado también será de tipo FT)
FT'rounding(x)   redondea x al valor entero más próximo
FT'truncation(x) devuelve el valor de x sin decimales
FT'floor(x)      devuelve el valor entero más grande menor que x
FT'ceiling(x)    devuelve el valor entero más pequeño mayor que x

EL LENGUAJE DE PROGRAMACION “ADA”

DEPTO. DE SISTEMAS DE INFORMACION FACULTAD DE CIENCIAS EMPRESARIALES UNIVERSIDAD DEL BIO-BIOLENGUAJE DE PROGRAMACION ADA

Extractado por Eduardo Jara de J.G.P. Barnes. Programmining in Ada.

Contenido

1. Introducción …………………………………………………………………….. 01

2. Descripción general ………………………………………………………….. 03

3. Estilo léxico …………………………………………………………………….. 07

4. Tipos escalares ………………………………………………………………… 10

5. Estructuras de control ……………………………………………………….. 23

6. Tipos compuestos …………………………………………………………….. 33

7. Subprogramas ………………………………………………………………….. 46

8. Estructura general …………………………………………………………….. 60

9.  Tipos Privados  ………………………………………………………………. 66

10. Excepciones …………………………………………………………………. 70

11. Genéricos …………………………………………………….. 78

12. Tareas …………………………………………………………. 63

1.  Introducción

La historia de Ada comienza en 1974 cuando el Departamento de Defensa de los Estados Unidos (DoD) se percató que estaba gastando demasiado en software. Se llevó a cabo un estudio detallado sobre la distribución  de  los  costos  y  se  descubrió  que  sobre  la  mitad  de  éstos  estaba  directamente  relacionado  con sistemas incrustados (embedded)

Se realizó un análisis de los lenguajes utilizados en diferentes áreas. Se descubrió que COBOL era el estandar para el procesamiento de datos y FORTRAN lo era para cálculos científicos y numéricos. Aunque estos  lenguajes  no  eran  modernos,  el  hecho  que  fueran  uniformemente  utilizados  en  sus  respectivas  áreas evitaba duplicaciones.

La  situación  con  respecto  a  los  sistemas  incrustados  era  diferente.  La  cantidad  de  lenguajes utilizados era enorme.   No sólo cada unidad militar tenía su lenguaje de alto nivel favorito, sino que usaban varios  lenguajes  assembler.  En  resultado  era  que  había  gastos  innecesarios  en  compiladores  y  costos adicionales en entrenamiento y mantención debido a la falta de estandarización.

Se determinó que la única forma de controlar efectivamente los costos en los sistemas incrustados

era estandarizar el uso de lenguajes de programación. El primer paso en  esta dirección fue  la generación de

un documento en que se delineaban los requerimientos del lenguaje estandar. La primera versión (Strawman)

fue  publicada  en  1975.  Después  de  recibir  comentarios  de  diversas  fuentes  el  documento  fue  refinado (Woodenman).  En  junio  de  1976  se  produjo  una  nueva  versión  (Tinman).  Este  era  un  documento  más específico e identificaba la funcionalidad que se requería del lenguaje.

En esta etapa se evaluaron varios lenguajes existentes respecto a la especificación Tinman. Como se podría esperar ninguno de éstos satisfacía totalmente los requerimientos; por otro lado la impresión general

era  que  sería  necesario  crear  un  nuevo  lenguaje  basado  en  conceptos  de  vanguardia  en  el  área  de  la programación.

Los lenguajes existentes fueron clasificados en tres categorías:

a)   ”no apropiados”:  Lenguajes obsoletos u orientados a otras áreas que no fueron considerados en las etapas siguientes. Por ejemplo, FORTRAN y  CORAL 66.

b)  ”no    inapropiados”:    Estos    lenguajes    tampoco    eran    satisfactorios,    pero    tenían    algunas características  interesantes  que  podían  ser  tomadas  como  ”inspiración”  para  enriquecer  el estandar. Por ejemplo,  RTL/2 y LIS.

c)   ”bases recomendadas”: Los lenguajes Pascal, PL/I y Algol 68 fueron considerados como posibles puntos de partida para el diseño del lenguaje final.

En este punto el documento de requerimientos fue revisado y reorganizado (Ironman). Se llamó a propuestas para el diseño del lenguaje. Se recibieron diecisiete propuestas de las cuales se eligieron tres para que “compitieran” en paralelo. Los cuatro elegidos fueron CII Honeywell Bull (verde), Intermetrics (Rojo), Softech (Azul) y SRI International (Amarillo). Los códigos de color se introdujeron para que la comparación

se realizara anónimamente.

Los  diseños  iniciales  aparecieron  a  comienzos  de  1978  y    fueron  analizados  por  varios  grupos alrededor  del  mundo.  El  DoD  juzgó  que  los  diseños  Verde  y  Rojo  eran  más  promisorios  que  los  Azul  y Amarillo y éstos últimos fueron eliminados.

Entonces,  el  desarrollo  entró  en  una  segunda  fase  y  se  dio  a  los  desarrolladores  un  año  más  para refinar sus diseños. Los requerimientos también fueron mejorados a la luz de la retroalimentación recibida de

los diseños iniciales (Steelman).

La  elección  final  del  lenguaje  fue  hecha  el  2  de  mayo  de  1979  cuando  el  ”lenguaje  verde” desarrollado en   CII Honeywell Bull   por un equipo internacional liderado por Jean Ichbiah   fue declarado ganador.

Entonces el DoD anunció que el nuevo lenguaje sería conocido como Ada en honor de Augusta Ada Byron, condesa de Lovelace (1815-1852). Ada, hija de Lord Byron, fue la asistente y mecenas de Charles Babbage  y  trabajó  en  su  ”máquina  analítica”.  En  un  cierto  sentido  ella  fue  la  primera  programadora  de  la historia.

Entonces  el  desarrollo  de  Ada  entró  a  una  tercera  etapa,  el  propósito  de  la  cual  fue  el  que  los eventuales  usuarios  hicieran  sus  comentarios  respecto  a  que  tan  conveniente  era  el  lenguaje  para  sus necesidades.  Se continuó con otros estudios de los  que se concluyó que Ada era un buen lenguaje, pero que

en algunas áreas todavía se requerían algunos refinamientos.    Después de esto (en julio de 1980) se publicó

la primera versión definitiva del lenguaje y se la propuso a la ANSI (America National Standards Institute)

como un estandar.

La estandarización por parte de ANSI tomó unos dos años y se le introdujeron algunos cambios a

Ada. El Manual de Referencia del Lenguaje estandar de la ANSI fue finalmente publicado en enero de 1983.

A esta versión del lenguaje se le conoce como Ada 83.

Tan pronto como se comenzó a utilizar el lenguaje se iniciaron los estudios para su mejora basada en

la experiencia práctica de los usuarios con el lenguaje. A la nueva versión  se le denominó Ada 9X, entre las principales  mejoras  hechas  sobre  la   anterior  se  cuenta  la  incorporación  de  mecanismo  de  herencia  en  el manejo  de  tipos  con  que  contaba  Ada  83,  el  cual,  a  pesar  de  ser  muy  poderoso,  al  carecer  de  herencia herencia no se adecuaba al paradigma de Orientación a Objetos. A esta segunda versión de Ada se denomina actualmente como Ada 95.

Ada  es  un  lenguaje  grande  en  la  medida  que  enfrenta  la  mayoría  de  los  aspectos  relevantes  a  la programación de sistemas prácticos en el mundo real. Por ejemplo, es mucho más grande que Pascal, el que a pesar  de  sus  extensiones  realmente  sólo  es  adecuado  para  propósitos  de  entrenamiento  (para  lo  que  fue diseñado) y para programas pequeños. A continuación se enumeran las principales características de Ada:

a)   Legibilidad: se reconoce que los programas profesionales se leen con mayor frecuencia de lo que son  escritos.  Por  lo  tanto  es  importante  evitar  una  notación  lacónica  como  en  C,   que  permite escribir un programa rápidamente, pero que hace casi imposible entenderlo, excepto para el autor

al poco tiempo de haberlo escrito.

b)  Tipificación    fuerte:  esto  asegura  que  cada  objeto  tiene   un  conjunto  claramente  definido  de posibles   valores   y   previene   la   confusión   entre   conceptos   lógicamente   distintos.                                             Como consecuencia de esto, muchos errores pueden ser detectados en tiempo de compilación, en otros lenguajes esto podría conducir a programas ejecutables, pero incorrectos.

c)   Programación en gran escala:  se necesitan mecanismos de encapsulación, compilación separada

y manejo de bibliotecas para escribir  programas portables y  mantenibles.

d)  Manejo de excepciones: es un hecho que los programas raramente son correctos en un cien por ciento.  Por este motivo se hace necesario proveer un medio por el cual los programas puedan ser construidos de forma tal que los errores en una parte de éste no repercutan  en las demás.

e)   Abstracción  de  datos:  como  ya  se  ha  mencionado,  se  puede  lograr  mayor  portabilidad  y mantenibilidad  si  los  detalles  de  la  representación  de  los  datos  puede  ser  separada                                               de  la especificación de las operaciones lógicas sobre los datos.

f)   Tareas:  en  muchos  casos  es  importante  que  el  programa  sea  concebido  como  una  serie  de actividades paralelas en lugar de una secuencia simple de acciones. Al entregar estas facilidades dentro  del  lenguaje  y  no  a  través  de  llamadas  a  un  sistema  operativo  se  logra  una  mayor portabilidad y mantenibilidad.

g)  Unidades  genéricas:  es  muchos  casos  la  parte  lógica  de  un  programa  es  independiente  de  los tipos de valores que son manipulados. Por lo tanto se requiere de un mecanismo para la creación

de partes lógicamente relacionadas a partir de un prototipo único. Esto es especialmente útil para

la creación de bibliotecas.

2. Descripción general

Uno  de  los  aspectos  más  importantes  en  la  programación  es  el  reuso  de  partes  de  programas existentes  para  que  el  esfuerzo  de  generar  nuevo  código  sea  mínimo.  Así  el  concepto  de  biblioteca  de programas emerge en forma natural y un aspecto importante de un lenguaje de programación es su habilidad para expresar el cómo reusar  itemes de una biblioteca.

Ada reconoce esta necesidad e introduce el concepto de unidades de biblioteca (library units). Un programa Ada completo es concebido como un programa principal (en sí mismo una unidad de biblioteca) que solicita los servicios de otras unidades.

Supongamos que queremos escribir un programa que imprime la raíz cuadrada de un cierto número, por ejemplo, 2.5. Podría esperarse  que estén disponibles bibliotecas que nos provean servicios para calcular raíces cuadradas y entregar datos. De este modo, nuestro trabajo se reduciría a escribir un programa principal que haga uso de estos servicios en el modo que deseemos.

Supondremos que en nuestra biblioteca existe una función de nombre SQRT que permite calcular raíces cuadradas, y   que, además,   nuestra biblioteca cuenta con un paquete (package) llamado SIMPLE_IO que  contiene  servicios  simples  de entrada-salida (lectura y escritura de números,   escrituras de cadenas de caracteres, etc.). Nuestro programa sería:

with SQRT, SIMPLE_IO; procedure PRINT_ROOT is use SIMPE_IO;

begin PUT(SQRT(2.5)); end PRINT_ROOT;

El programa está escrito como un procedimiento llamado PRINT_ROOT  precedido por una cláusula with  donde se dan los nombre de las unidades de biblioteca que se desea usar. El cuerpo del procedimiento contiene una única instrucción:

PUT(SQRT(2.5)); Al escribir

use SIMPLE_IO;

se obtiene acceso inmediato a los servicios del paquete SIMPLE_IO. Si se hubiese omitido la cláusula use

hubiese sido necesario usar la “notación punto”.

SIMPLE_IO.PUT(SQRT(2.5));

para indicar dónde debe buscarse el procedimiento PUT.

Podemos hacer nuestro programa más útil haciendo que lea el número cuya raíz cuadrada se desea obtener.  El programa quedaría:

with SQRT, SIMPLE_IO; procedure PRINT_ROOT is use SIMPE_IO;

X : FLOAT;

begin

GET(X); PUT(SQRT(X)); end PRINT_ROOT;

La estructura general del procedimiento es clara: entre is y begin podemos escribir declaraciones, y entre  begin  y  end  escribimos  instrucciones  (operaciones).  En  términos  generales  podemos  decir  que  las declaraciones  presentan  las  entidades  que  queremos  manipular  y  las  instrucciones  indican  la  secuencia  de acciones a realizar.

Notemos algunos detalles:

  • Todas las declaraciones e instrucciones terminan con un punto y coma a diferencia de otros lenguajes como Algol y Pascal donde los punto y coma son utilizados como separadores en lugar de terminadores.
  • El programa contiene varios identificadores tales como procedure , PUT y X. Los identificadores se dividen en dos grandes grupos. Unos pocos (63 en Ada 83) como procedure e is , que son utilizados para

indicar la estructura del programa; son palabras reservadas y no pueden ser usados para otros propósitos. Los

otros tales como  PUT y X pueden ser usados para cualquier propósito  que se desee. Algunos de ellos (en nuestro ejemplo FLOAT) tienen un significado predefinido, pero podrían ser usados para otros propósitos, aunque se corre el riesgo de confusión en el código.

Finalmente  observemos  que  el  nombre  del  procedimiento,  PRINT_ROOT,  se  repite  entre  el  end final y el punto y coma. Esto es opcional, pero se recomienda para aclarar la estructura general, el cual es obvio en un ejemplo tan pequeño.

Nuestro  ejemplo  es demasiado  simple, sería más útil si se lee una serie de números y se imprime cada respuesta en una línea separada. Arbitrariamente definiremos que el  proceso terminará al ingresarse un valor igual a cero.

with SQRT, SIMPLE_IO;

procedure PRINT_ROOTS is use SIMPLE_IO;

X : FLOAT;

begin

PUT(“Roots of various numbers”); NEW_LINE(2);

loop

GET(X);

exit when X = 0.0; PUT(“Root of”); PUT(X); PUT(“is”);

if X < 0.0 then

PUT(“not calculable”);

else

end if;

PUT(SQRT(X));

NEW_LINE; end loop; NEW_LINE;

PUT(“Program finished”);

NEW_LINE;

end PRINT_ROOTS;

La salida del programa ha sido mejorada mediante la llamada a los procedimientos NEW_LINE y PUT  incluidos  en   el  paquete  SIMPLE_IO.  El  parámetro  numérico  entero  del  procedimiento  NEW_LINE indica  cuantas  líneas  se  deberá  escribir,  si  no  se  entrega  el  parámetro  se  asume  un  valor  igual  a  1  (uno). También  hay  una  llamada  a  PUT  con  una  cadena  como  argumento.  Este  es  de  hecho  un  procedimiento diferente al que escribe el número X. El compilador diferencia cual corresponde en cada caso de acuerdo al tipo (y/o cantidad) de parámetros utilizado.

En Ada se debe observar reglas estrictas para cerrar estructuras; loop es cerrado con end loop e if con enf if. Todas las estructuras de control de Ada tienen sus cierres en lugar de la forma abierta de Pascal que puede conducir a errores.

Veamos ahora la posible estructura de la función SQRT y el paquete SIMPLE_IO que en nuestro

ejemplo se han supuesto como existentes.

La función SQRT tendrá una estructura similar a nuestro programa, la mayor diferencia radica en la existencia de parámetros.

function SQRT (F:FLOAT) return FLOAT is

R: FLOAT;

begin

— calcular la raíz cuadrada de F y guardarla en R

return R;

end SQRT;

El paquete SIMPLE_IO consta de dos partes, la especificación que describe su interfaz con el mundo externo  (es  decir,  las  otras  unidades  de  biblioteca  que  hacen  uso  de  el  paquete)  y  el  cuerpo  (body)  que contiene los detalles de cómo han sido implementados los componentes del paquete. Si el paquete contiene sólo los procedimientos que hemos usado en nuestro ejemplo, su especificación sería:

package SIMPLE_IO is

procedure GET(F: out FLOAT); procedure PUT(F: in FLOAT); procedure  PUT(S: in STRING);

procedure NEW_LINE(N: in INTEGER:=1);

end SIMPLE_IO;

El  parámetro     de  GET  es  un  parámetro  out  (salida)  puesto  que  el  efecto  de  llamar  a  este procedimiento en   GET(X) es entregar un valor “desde dentro” del procedimiento por medio del parámetro

real  X  (actual  parameter  X).  Los  demás  parámetros  son  in  puesto  que  sirven  para  ingresar  datos  a  los procedimientos.

Sólo  una  parte  de  los  procedimientos  está  presente  en  la  especificación  del  paquete;  esta  parte  se conoce  como  la  ”especificación  de  los  procedimientos”  donde  sólo  se  entrega  la  información  que  permite llamar a los procedimientos. Es decir, para que nuestro programa PRINT_ROOTS use el procedimiento GET sólo  necesita  saber  el  nombre  de  éste  y  sus  parámetros,  los  detalles  de  implementación  en  esta  etapa  no importan. Esto significa que para compilar el programa PRINT_ROOTS sólo es necesario que esté presente

la  declaración  del  paquete  SIMPLE_IO.  Lógicamente,  para  poder  ejecutar  el  programa  se  necesitará  la implementación de todos los procedimientos.

En nuestro ejemplo podemos ver dos especificaciones superpuestas de PUT, una con un parámetro

de  tipo  FLOAT  y  otro  con  un  parámetro  de  tipo  STRING.  Es  importante  recalcar  que  estos  son  dos procedimientos diferentes, el compilador Ada discriminará de acuerdo a los parámetros de cada uno de ellos. Finalmente, notemos  como se asigna un valor por omisión al parámetro del procedimiento NEW_LINE.

El  cuerpo  del  paquete  (package  body)  SIMPLE_IO  contendrá  la  totalidad  de  los  cuerpos  de  los procedimientos especificados, además de otros elementos (variables, estructuras de datos, otras funciones o procedimientos) que se requieran para su implementación y que quedan de una forma natural escondidas de

los usuarios externos. Esto quiere decir que los usuarios del paquete (otras unidades de biblioteca: funciones, procedimientos, paquetes, tareas)  sólo “conocen” lo indicado en la especificación del paquete.  En términos generales el cuerpo del paquete tendría la siguiente estructura:

with INPUT_OUTPUT;

package body SIMPLE_IO is

. . .

procedure GET (F: out FLOAT) is

. . .

begin

. . .

end GET;

– otros procedimientos parecidos

end SIMPLE_IO

La  cláusula  with  muestra  que  la  implementación  (pero,  en  este  caso,  no  la  especificación)  de  los procedimientos  en  SIMPLE_IO  requiere  de  un  hipotético  paquete más general llamado INPUT_OUTPUT. Notemos, además, que en el cuerpo de GET se repite la especificación del procedimiento que fue entregada

en la especificación del paquete.

Es importante mencionar el paquete especial STANDARD. Este es un paquete que existe en toda implementación del lenguaje y contiene las declaraciones de todos los identificadores predefinidos tales como FLOAT y NUMERIC_ERROR. Se asume el acceso automático a este paquete y por lo tanto no es necesario

dar su nombre en una cláusula with.

En  resumen,  un  programa  Ada  es  concebido  como  un  conjunto  de  componentes  (procedimientos, funciones, paquetes, tareas) que se proveen servicios mutuamente.

Ejercicio: Seguramente la función SQRT no estará directamente disponible en una biblioteca, sino que será parte de un paquete junto con otras funciones matemáticas. Supongamos que dicho paquete tiene por nombre  SIMPLE_MATHS  y  los  otras  funciones  son  LOG,  EXP,  SIN  y  COS.  Se  pide  que  escriba  la especificación de dicho paquete (utilice la especificación de SIMPLE_IO como modelo) ¿Qué cambios habría que hacerle al programa PRINT_ROOTS?

3. Estilo léxico

Ciertamente no es muy agradable comenzar el estudio de un lenguaje con un tema tan árido como son  los  detalles  de  la  construcción  de  cosas  tales  como  identificadores  y  números,  sin  embargo  ello  es esencial  para  un  conocimiento  acabado  de  un  lenguaje  y,  obviamente,  para  la  correcta  construcción  de programas.

3.1. Elementos léxicos

Un  programa  Ada  se  escribe  como  una  secuencia  de  líneas  de  texto  que  contienen  los  siguientes caracteres:

  • alfabeto a-z y A-Z
  • dígitos 0-9
  • otros caracteres ” # & ´( ) * + , – / : ; < = > _ |
  • el carácter blanco

Se debe tener presente que los siguientes delimitadores compuestos no deben contener espacios:

  • => usado en when, cases, etc.
    .. usado para rangos
      ** para exponenciación
        := para asignación
          /= no igual
            >= mayor o igual
              <= menor o igual
                <> para arreglos

                3. 2. Identificadores

                Un identificador se define de la siguiente manera:

                identifier ::= letter {[underline] letter_or_digit}

                letter_or_digit ::= letter | digit

                letter ::= upper_case_letter | lower_case_letter

                Esto  quiere  decir  que  un  identificador  consiste  en  una  letra  seguida  (posiblemente)  de  una  o  más letras  o  dígitos  con  subrayados  aislados.  Se  pueden  usar  letras  mayúsculas  y  minúsculas,  las  que  no  son tomadas como diferenciadores de identificadores, por ejemplo:

                Sueldo_Base y SUELDO_BASE

                son el mismo identificador. A pesar de esta libertad para escribir los identificadores, se recomienda, para una mejor legibilidad  usar minúsculas para las palabras reservadas y mayúsculas para los demás identificadores.

                Ada  no  impone  un  límite  en  el  largo  del  identificador,  sin  embargo,  puede  haber  limitaciones impuestas  por  implementaciones  específicas.  De  esta  manara  se  estimula  el  uso  de  nombres  de  variables autodocumentados como SUELDO_BASE en lugar del poco significativo S.

                Ejercicio: Indique cuáles de los siguientes identificadores son incorrectos y por qué

                a)   Ada

                b)  fish&chips

                c)   RATE-OF-FLOW

                d)  UMO164G

                e)   TIME_ _LAG

                f)   77E2

                g)  X_

                h)  tax rate i)   goto

                3.3. Números

                Los números pueden ser enteros o reales. La mayor diferencia es que los reales siempre contienen un punto decimal y los enteros no. Es ilegal usar un entero donde el contexto indica que debe usarse un real y viceversa. Entonces

                AGE: INTEGER := 43.9; y               WEIGHT: REAL := 150;

                son expresiones incorrectas.

                La forma más simple de un entero es una secuencia de dígitos, y la de un real es una secuencia de dígitos con un punto decimal. Nótese que debe haber al menos un dígito a cada lado del punto decimal.

                A diferencia de otros lenguajes en Ada tanto los enteros como los reales pueden tener exponente, el cual se indica con una letra E (puede ser minúscula) seguida de un entero con o sin signo. El exponente no puede ser negativo en el caso de un entero (si así fuera el resultado podría no ser un entero). Por ejemplo el real 98.4 podría ser escrito con exponente de las siguientes formas:

                9.84E1        98.4e0         984.0e-1         0.984E+2

                pero 984e-1 sería incorrecto.

                En forma análoga, el entero 1900 podría escribirse con exponente como:

                19E2            190e+1          1900E+0

                pero  19000 e-1 sería incorrecto.

                Ejercicio: Indique cuáles de las siguientes secuencias de dígitos son reales o enteros válidos.

                a)   38.6

                b)  .5

                c)   32e2 d)  32e-2 e)   E+6

                f)   27.4e_2

                3.4. Comentarios

                ejemplo:

                Un  comentario  en  Ada  es  cualquier  texto  ubicado  a  la  derecha  de  dos  guiones  (seguidos),  por

                – Este es un comentario

                PUT(SQRT(2.5));  – Este es otro comentario

                El comentario se extiende hasta el final de la línea.

                4. Tipos escalares

                En este capítulo se hechan las bases de los aspectos a pequeñas escala de Ada. Se comienza con la declaración  de  objetos,  la  asignación  de  valores  a  ellos  y  las  idea  de  rango  de  validez  y  visibilidad.Se introducen los importantes conceptos de tipo, subtipos y restricciones.

                4.1. Declaración de objetos y asignaciones

                Los  valores  (datos)  pueden  almacenarse  en  objetos  que  son  de  un  cierto  tipo  (lo  que  se  indica  al momento  de  su  declaración).  Estos  objetos  pueden  ser  variables  o  constantes.  Una  variable  se  declara mediante su nombre (un identificador), su tipo y (posiblemente ) un valor inicial. Por ejemplo:

                I : INTEGER;           — variable de nombre I y de tipo entero

                P: INTEGER := 38; — variable de nombre P, de tipo entero y valor inicial 38

                Es posible declarar varias variables simultáneamente separándolas por comas. Por ejemplo: I,J,K : INTEGER;

                P,Q,R: INTEGER:= 38;

                Si se declara una variable sin un valor inicial es necesario tener cuidado de no usarla con un valor indefinido. Si un programa usa un valor indefinido de una variable no inicializada, su comportamiento puede

                se impredecible. En este caso el programa   debería considerarse estrictamente erróneo, sin embargo tanto el compilador como el sistema de run-time podrían no ser capaces de detectar el problema.

                La manera más simple de asignar un valor a una variable es mediante el comando de asignación (:=). Por ejemplo:

                I:= 36; P:=Q + R;

                Existe una gran similitud entre una declaración con valor inicial y el comando de asignación. Ambos usan := antes de la expresión, la cual puede ser de cualquier nivel de complejidad. Una diferencia importante

                es que es posible inicializar varias variables simultáneamente, pero no es posible usar un único comando de asignación para varias variables. Esto parece poco práctico, sin embargo la necesidad de asignar un mismo valor a varias variables generalmente sólo surge al momento de inicializarlas.

                Una constante se declara de forma similar a una variable, escribiendo la palabra constant después de

                los  dos  puntos.  Lógicamente,  una  constante  debe  ser  inicializada  al  ser  declarada,  de  otro  modo  pierde  su sentido. Por ejemplo:

                PI : constant REAL :=3.1415926536;

                Ejercicio: Indique los errores de las siguientes declaraciones y comandos:

                a)   var I:INTEGER;

                b)  G:constant :=981;

                c)   P,Q:constant INTEGER;

                d)  P:=Q:=7;

                e)   MN:constant INTEGER:=M*N;

                f)   2PI:constant:= 2.0*PI;

                4.2. Bloques y ámbito de validez (scope)

                Ada  hace  una  clara  distinción  entre  la  declaraciones  de  nuevos  identificadores  y  los instrucciones que los usan. Es obvio que las primeras deben preceder a los últimos. Un bloque (block) es la estructura más simple que incluye declaraciones y comandos.

                Un  bloque  comienza  con  la  palabra  reservada  declare,  algunas  declaraciones,  begin,  algunas instrucciones y concluye con la palabra reservada end y un punto y coma. Por ejemplo:

                declare

                I: INTEGER:=0;

                begin

                I:=I+1;

                end;

                Un bloque es un caso particular de una instrucción, por lo tanto una de sus instrucciones   internas puede ser otro bloque.

                Cuando se ejecuta una instrucción “bloque” se prepara su parte declarativa (entre declare y begin) y luego se ejecutan las instrucciones  (entre begin y end). Nótese la terminología: se preparan las declaraciones

                y se ejecutan las instrucciones. La preparación de una declaración consiste en “crear” el objeto declarado y

                asignarle (si corresponde) un valor inicial. Al llegar al final (end) del bloque todas las cosas declaradas en él automáticamente dejan de existir.

                Un  punto  importante  es  que  los  objetos  usados  para  la  inicialización  de  otros  objetos  deben, lógicamente, existir. Es decir, toda declaración debe preceder a su uso. Por ejemplo:

                declare

                I: INTEGER :=0; K: INTEGER:= I;

                begin

                está permitido, pero

                declare

                K: INTEGER:= I; I: INTEGER:= 0;

                begin

                en general no. (¿En qué situación esto estaría correcto, aunque con un significado diferente?)

                Al  igual  que  otros  lenguajes  Ada  maneja  la  idea  de  ocultamiento  (hiding).       Consideremos  el siguiente bloque

                declare

                I,J: INTEGER;

                begin

                . . .                   — aquí es válida la variable I externa

                declare

                I: INTEGER;

                begin

                . . .              — aquí es válida la variable I interna

                end;

                . . .                    — aquí es válida la variable I externa

                end;

                La declaración interna de I no hace que la externa desaparezca, sino que ésta se torna temporalmente invisible. Al terminar el bloque interno, la variable I interna deja de existir y la externa vuelve a estar visible.

                Hay que distinguir entre el ámbito de validez (scope) y la visibilidad de un objeto. Por ejemplo, en el caso  de  un  bloque  el  ámbito  de  validez  de  una  variable  (o  constante)  se  extiende  desde  el  punto  de  su declaración hasta el final (end) del bloque. Sin embargo, una variable (o constante) no es visible en su propia declaración ni en ningún bloque interno donde su nombre sea utilizado para la declaración de otro objeto. Los ámbitos de validez y visibilidad de ilustran en el siguiente ejemplo

                declare

                I: INTEGER:=0;

                begin

                declare

                K: INTEGER:= I; I: INTEGER:= 0;

                begin

                . . .

                end;

                . . .

                end;

                scope visibilidad     scope visibilidad
                I externa I externa     I interna I interna

                Para inicializar K se utiliza el valor de I externa, puesto que la declaración de K precede a la de la

                variable I interna. Vemos pues que el código

                K: INTEGER:= I; I: INTEGER:= 0;

                puede o no ser válido, depende del contexto.

                Ejercicio: Indique qué errores hay en la siguiente sección de código.

                declare

                I: INTEGER:= 7; J,K: INTEGER

                begin

                J:= I+K;

                declare

                P: INTEGER = I; I,J: INTEGER;

                begin

                I:= P+Q; J:= P -Q; K:= I* J;

                end; PUT(K);

                end;

                4.3. Tipos

                Un tipo queda totalmente caracterizado por un conjunto de valores y un conjunto de operaciones. En

                el caso del tipo de datos primitivo INTEGER, el conjunto de valores está representado por

                . . . -3, -2, -1, 0, 1, 2, 3 . . . y las operaciones por

                +, -, *, etc.

                Se asume que los tipos primitivos del lenguaje están definidos en el paquete STANDARD.

                Los valores de un tipo no pueden ser asignados a variables de otro tipo. Esta una regla fundamental del manejo estricto de tipos.

                La declaración de tipos se realiza con una sintaxis distinta a la declaración de variables y constantes para enfatizar la diferencia conceptual existente. La declaración se realiza mediante la palabra reservada type,

                el identificador asociado con el tipo, la palabra reservada is   y la definición del tipo seguida de un punto y coma. Por lo tanto, el paquete STANDARD debería contener declaraciones tales como

                type INTEGER is . . ;

                ejemplo:

                La definición entre is y ; de alguna manera entrega el conjunto de   valores asociados al tipo. Por

                type COLOUR is (RED, AMBER, GREEN);

                Se  ha  definido  un  nuevo  tipo  denominado  COLOUR,  al  que  se  asocia  un  conjunto  de  3  valores: RED, AMBER y GREEN, es decir, las variables de tipo COLOUR sólo podrán “almacenar” alguno de dichos valores. Por ejemplo:

                C: COLOUR;

                D: COLOUR:=RED;

                DEFAULT: constant COLOUR:=GREEN;

                son declaraciones válidas.

                Debido al manejo estricto de tipos no podemos “mezclar” colores y enteros, por ejemplo:

                I: INTEGER; C:COLOUR;

                . . . I := C;

                es incorrecto. Comparemos esto con la “filosofía” del leguaje C (Keninghan, B. Ritchie D. El Lenguaje de

                Programación C).

                Una enumeración es una lista de valores enteros constantes, como en

                enum boolean  {NO, YES};

                El primer nombre en un enum tiene un valor 0, el siguiente 1, y así sucesivamente, a menos que sean especificados  valores  explícitos.   Los  valores  no  especificados  continúan  la  progresión  a  partir  del  último valor que sí lo fue, como en el segundo de esos ejemplos:

                enum escapes  { BELL = ´\a´, RETROCESO = ´\b´, TAB = ´\t´, NVALIN = ´\n´, VTAB = ´\v´, RETURN = ´\r´};

                enum months { ENE = 1, FEB, MAR, ABR, MAY, JUN, JUL, AGO, SEP, OCT, NOV, DIC};

                Las   enumeraciones   proporcionan   una   manera   conveniente   de   asociar   valores   constantes   con nombres,  son   una  alternativa  a  #define.  Aunque  las  variables  de  tipos  enum   pueden  ser  declaradas,  los compiladores  no  necesitan  revisar  que  lo  que  se  va  a  almacenar  en  tal  variable  es  un  valor  válido  para  la enumeración. No obstante, las variables de enumeración ofrecen la oportunidad de revisarlas y tal cosa es a menudo mejor que #define. Además, un depurador puede ser capaz de imprimir los valores de variables de enumeración en su forma simbólica.

                Como puede verse, en Ada existen mecanismos totalmente diferentes para definir constantes y para

                definir tipos de enumeración, en cambio en C los tipos enum sirven para ambos acciones. Además, lo dicho respecto a tipos enum en C es un claro ejemplo de la estilo “relajado” de manejo de tipos de este lenguaje.

                4.4. Subtipos

                Un subtipo, como su nombre lo indica, es un subconjunto de los valores de otro tipo, al que se le denomina tipo base.   Dicho subconjunto se define mediante una restricción. Si embargo no hay forma de restringir el conjunto de operaciones de tipo base.

                Por ejemplo, supongamos que queremos manipular días; sabemos que los días de un mes están en el

                rango de 1 a 31, entonces el subtipo (del tipo INTEGER) sería

                subtype DAY_NUMBER is INTEGER range 1 .. 31;

                Posteriormente  podemos  declarar  variables  y  constantes  usando  el  identificador  de  un  subtipo exactamente igual a como lo hacemos con un identificador de tipo. Por ejemplo, al declarar

                D: DAY_NUMBER;

                estamos asegurando que la variable D pude tomar sólo valores enteros en el rango de 1 a 31. En tiempo de compilación podrían detectarse errores como

                D := 32;

                Además, el compilador agregará  chequeos de tiempo de ejecución (run time checks)  para verificar que se cumpla la restricción, si fallase un chequeo se indicaría un excepción CONSTRAIN_ERROR.

                Es importante notar que la declaración de un subtipo no introduce un nuevo tipo. Un objeto como D

                es de tipo entero y por lo tanto el siguiente código es totalmente legal

                D: DAY_NUMBER;

                I: INTEGER;

                . . . D:=I;

                Lógicamente,  durante  la  ejecución,  el  valor  de  I  podría  salirse  del  rango  1  a  31.  En  este  caso  se indicaría CONSTRAIN_ERROR. Pero una asignación en el sentido inverso

                I := D;

                siempre funcionará correctamente.

                No es necesario declarar un  nuevo tipo para imponer una restricción. Por ejemplo, mediante

                D: INTEGER range 1 .. 31;

                se ha declara una variable de tipo entero junto con una restricción. Ejercicio:

                1. ¿Cuándo tendrá sentido definir un tipo con una cierta restricción  y cuándo una variable?

                2. ¿Qué diferencia semántica habrá entre las siguientes declaraciones?

                a) subtype DAY_NUMBER is INTEGER range 1 .. 31;

                b) type DAY_NUMBER is range 1 .. 31;

                4.5. Tipos numéricos simples

                La complejidad de los problemas de análisis numérico (estimación de errores y otros) se refleja en

                los tipos de datos numéricos de   Ada. Sin embargo, aquí expondremos sólo los aspectos más simples de los tipos entero (INTEGER) y real (REAL), para usarlos en nuestros ejemplos.

                El   valor   mínimo   del   tipo   INTEGER   está   dado   por   INTEGER´FIRST   y   el   máximo   por

                INTEGER´LAST. Estos son dos ejemplos de atributos, los cuales se denotan mediante un apóstrofe seguido

                de un identificador.

                El  valor  de  INTEGER´FIRST  dependerá  de  la  implementación  y  siempre  será  negativo.  En  una máquina de 16 bits (con complemento a dos) se tendrá

                INTEGER´FIRST = -32768

                INTEGER´LAST = +32767

                Por supuesto que para lograr una mejor portabilidad debemos usar los atributos FIRST y LAST en lugar de los valores -32768 y +32767. Dos subtipos muy útiles son

                subtype NATURAL is INTEGER range 0 .. INTEGER´LAST;

                subtype POSITIVE is INTEGER range  1 .. INTEGER´LAST;

                Los atributos FIRST y LAST se aplican automáticamente a los subtipos, por ejemplo

                POSITIVE´FIRST = 1

                NATURAL´LAST = INTEGER´LAST

                También  es  posible  hacer  restricciones  sobre  los  reales.  Además,    los  atributos  FIRST  y  LAST

                también son aplicables.

                A continuación se resumen algunas de las operaciones predefinidas para los tipos entero y real más importantes.

                1)  +, – Estos son tanto operadores unarios como binarios. En el primer caso el operando puede ser entero o real; el resultado será del mismo tipo. El operador unario + en realidad no hace nada, pero  el  operador  unario  -  cambia  el  signo  del  operando.  Cuando  se  usan  como  operadores binarios, ambos operandos deben ser del mismo tipo y el resultado será del tipo que corresponda.

                Su acciones son la adición y sustracción  normales.

                2)  *     Multiplicación;  ambos  operandos  deben  ser  enteros  o  reales;  el  resultado  será  del  tipo correspondiente.

                3)  /  División;  ambos operandos deben ser del mismo tipo. La división entero trunca el resultado.

                4)  rem   Resto; los operandos deben ser enteros y el resultado también lo es. Entrega el resto de la división.

                5)  mod  Módulo; ambos operandos deben ser enteros y el resultado también lo es. Corresponde a la operación matemática módulo.

                6)  abs   Valor absoluto; es un operador unario y el operador debe ser entero o real. El resultado es del mismo tipo y corresponde al valor absoluto del operando.

                7)  **  Exponenciación; eleva el primer operando a la potencia indicada en el segundo. Si el primer operando  es  un  entero,  entonces  el  segundo  debe  ser  un  entero  positivo  o  cero.  Si  el  primer operando es un real, entonces el segundo puede ser cualquier entero. El resultado es del mismo tipo del primer operando.

                Además,  pueden  realizarse  las  operaciones   =,   /=,  <,  <=,  >  y >=  para  obtener  resultados  de  tipo booleano TRUE o FALSE. También en este caso los operadores deben ser del mismo tipo.

                A pesar que estas operaciones son bastante sencillas es necesario hacer algunas observaciones.

                La regla general es que no se puede mezclar valores de distinto tipo en una expresión aritmética. Por ejemplo, no se puede sumar un entero a un real. El cambio de un valor INTEGER a uno REAL  o viceversa puede hacerse usando el nombre del tipo (o subtipo) de dato que se necesita como si fuera el nombre de una función.   Por ejemplo, si tenemos

                I: INTEGER:=3; R:REAL:=5.6;

                no podemos escribir

                I + R

                pero si es válida la expresión

                REAL(I) + R

                que usa la “suma real” para entregar 8.6, o

                I + INTEGER(R)

                que entrega 9, al hacer uso de la “suma entera”.

                La conversión de real a entero siempre entrega un valor redondeado,  por ejemplo:

                1.4 entrega 1

                1.6 entrega 2

                Dependiendo  de  la  implementación  los  valores  intermedios  (como  1.5)  serán  redondeados  hacia arriba o hacia abajo.

                Finalmente, hagamos algunas observaciones respecto al operador **. Para un exponente positivo (es decir, un entero positivo) esta operación corresponde a una multiplicación repetida. Por ejemplo:

                3**4 = 3*3*3*3 = 81

                3.0**4 = 3.0*3.0*3.0*3.0* = 81.0

                Cuando el segundo operando es cero, el resultado será obviamente uno

                3**0 = 1

                3.0**0 = 1.0

                El exponente no puede ser negativo si la base es entera, ya que el resultado podría no ser entero. Pero si la base es real este operador entrega el correspondiente recíproco

                3.0**(-4) = 1.0/81.0 = 0.0123456780123…

                Como es usual existen diferentes niveles de precedencia entre los operadores en una expresión, y, además, la precedencia natural puede ser alterada con el uso de paréntesis. Los operadores de igual jerarquía son aplicados de izquierda a derecha. La jerarquía de operados en Ada es la siguiente

                =  /=  <  <=  >  >=

                +   -                        (binarios)

                +  -                          (unarios)

                / * mod rem
                abs **
                Por ejemplo:
                A/B*C significa (A/B)*C
                A+B*C+D significa A+(B*C)+D
                A*B+C*D significa (A*B)+(C*D)
                A*B**C significa A*(B**C)

                Como ya se indicó, por omisión los operadores de un mismo nivel se aplican de izquierda a derecha,

                sin embargo, no está permitido escribir varios operadores de exponenciación sin el uso de paréntesis. Por esto

                la expresión

                A**B**C

                es incorrecta y debe escribirse

                (A**B)**C          o             A**(B**C)

                De esta manera se evita el riesgo de que accidentalmente se escriba una expresión sintácticamente correcta, pero semánticamente errónea.

                Es necesario tener cuidado con los operadores unarios, por ejemplo:

                -A**B      significa      -(A**B)      y no     (-A)**B

                además, las expresiones

                A**-B   y  A*-B

                son ilegales. Es necesario, en estos casos, utilizar paréntesis para definir claramente lo que se desea calcular. Ejecicio:

                1. Evalúe las siguientes expresiones:

                I:INTEGER:=7; J:INTEGER:=-5; K:INTEGER:=3;

                a) I*J*K b) I/J*K c) I/J/K

                d) J+2 rem I

                e) K**K**K

                2. Escriba las siguientes expresiones matemáticas en Ada. Defina identificadores apropiados.

                a) b2 - 4ac

                b) p r3

                4.6. Tipos de enumeración

                Primero mostremos algunos ejemplos de tipos de enumeración:

                type COLOUR is (RED,AMBER,GREEN);

                type  DAY is (MON, TUE, WED, THU, FRI, SAT, SUN);

                type STONE is (BERYL, QUARTZ);

                type SOLO is (ALONE);

                No existe un límite superior para la cantidad de valores de un tipo de enumeración, pero debe haber

                al menos uno.

                Las restricciones de los tipos enumerados son semejantes a las restricciones sobre los enteros. Por

                ejemplo:

                subtype WEEKDAY is DAY range MON .. FRI;

                D: WEEKDAY;

                Si el límite superior es inferior al límite inferior se obtendrá un rango vacío. Por ejemplo:

                subtype COLOURLESS is COLOUR range AMBER .. RED;

                Los atributos FIRST y LAST también son aplicables a los tipos de enumeración, entonces

                COLOUR´FIRST = RED WEEKDAY´LAST = FRI

                Otros atributos predefinidos entregan el sucesor (SUCC) y el predecesor (PRED) de un cierto valor

                de un tipo de enumeración. Por ejemplo:

                COLOUR´SUCC(AMBER) = GREEN STONE´SUCC(BERYL) = QUARTZ DAY´PRED(FRI) = THU

                Lógicamente, lo indicado entre paréntesis es cualquier expresión válida del tipo correspondiente. Si

                se  intenta  obtener  el  antecesor  (sucesor)  del  primer  (último)  valor  el  sistema  entregará  una  excepción

                CONSTRAIN_ERROR.

                Otro atributo predefinido   es POS, el cual entrega la posición del valor dentro de la declaración del tipo (empezando desde cero). El atributo contrario a POS es VAL, que entrega el valor asociado a una cierta posición. Por ejemplo:

                COLOUR´POS(RED) = 0

                COLOUR´POS(AMBER) = 1

                COLOUR´POS(GREEN) = 2

                COLOUR´VAL(0) = RED DAY´VAL(6) = SUN

                SOLO´VAL(1)   – CONSTRAIN_ERROR

                Ada  incluye  los  atributos  POS  y  VAL,  puesto  que  podrían  ser  necesarios  en  algunas  situaciones, pero no se recomienda su uso pues no es una buena práctica de programación. Es mejor pensar en términos de valores de enumeración que en números.

                Puesto que se establece un orden entre los valores de un tipo de enumeración , es posible aplicar los operadores  =, /=, <, <=, > y  >=. Por ejemplo:

                RED < GREEN   es   TRUE

                WED >= THU      es   FALSE

                Ejercicio:

                a)   Evalúe

                1) DAY´SUCC(WEEKDAY´LAST)

                2) WEEKDAY´SUCC(WEEKDAY´LAST)

                3) STONE´POS(QUARTZ)

                b)  Declare los siguientes tipos

                1) frutas típicas.

                2) proveedores de computadores.

                c)   Si el primer día del mes está  en la variable D de tipo DAY, escriba una asignación   que                reemplace

                D por el día de la semana del N-ésimo día del mes.

                4.7. Tipo Booleano

                El tipo booleano es un tipo de enumeración predefinido, cuya declaración puede considerarse como

                type  BOOLEAN is (FALSE, TRUE);

                Los  valores  booleanos  son  producidos  por  los  operadores  =,  /=,  <,  <=,  >  y  >=,  los  que  tienen  el significado normalmente aceptado y se aplican a muchos tipos. Por ejemplo:

                if TODAY = SUN then

                TOMORROW := MON;

                else

                end if;

                TOMORROW:= DAY´SUCC(TODAY);

                Existen otros operadores asociados al tipo BOOLEAN.

                1.   not  Es un operador unario y cambia TRUE a FALSE y viceversa.

                2.   and  Es un operador binario. El resultado es TRUE si ambos operadores son TRUE, en otro         caso será

                FALSE.

                3.   or Es un operador binario. El resultado es TRUE si al menos uno de los operadores es TRUE, es FALSE

                si ambos operadores son FALSE.

                4.   xor Es un operador binario. El resultado es TRUE si y sólo si ambos operadores son distintos, es decir, uno es TRUE y el otro es FALSE.

                Los  operadores   and,  or  y  xor  tienen  la  misma  precedencia,  la  cual  es  menor  que  cualquier  otro operador. En particular, tienen menor precedencia que =, /=, <, <=, > y >=. Como consecuencia de esto no se necesitan paréntesis para expresiones como

                P < Q and  I = J

                Sin  embargo,  a  pesar  de  tener  la  misma  precedencia,  los  operadores  and,  or  y  xor  no  pueden mezclarse sin paréntesis, entonces

                B and C or D

                es incorrecto y debe escribirse

                B and (C or D)       o          (B and C) or D

                para enfatizar el sentido de la expresión.

                Los programadores familiarizados con otros lenguajes recordarán que los operadores and y or tienen distinta precedencia. Esto puede conducir a errores, por ello Ada les da la misma precedencia y obliga a usar paréntesis. Obviamente, si se aplica el mismo operador en forma sucesiva no es necesario usar paréntesis ya que tanto and como or son asociativos. Por ejemplo:

                B and C and D

                es una expresión correctamente escrita.

                Las  variables  y  constantes  booleanas   pueden  ser  declaradas  y  utilizadas  en  la  manera  usual.  Por

                ejemplo:

                DANGER: BOOLEAN;

                SIGNAL: COLOUR;

                . . .

                DANGER := SIGNAL = RED;

                La variable DANGER será TRUE si la señal es RED. Además, podríamos escribir

                if DANGER then

                STOP_TRAIN;

                end if;

                Notemos que no deberíamos escribir

                if DANGER = TRUE then

                porque a pesar que es sintácticamente correcto no toma en cuenta que DANGER es una variable booleana y puede utilizarse directamente como una condición. Peor aún sería escribir

                if  SIGNAL = RED then

                DANGER .= TRUE;

                else

                end if;

                DANGER := FALSE;

                en lugar de           DANGER := SIGNAL = RED;

                Ejercicio:

                1.   Escriba declaraciones de constantes T y F  con los valores TRUE y FALSE.

                2.   Evalúe  (A /= B) = (A xor B) para todos los valores posibles de las variables booleanas A y B.

                5. Estructuras de Control

                Ada  tiene  tres  estructuras  de  control:  if,  case  y  loop.  A  pesar  que  estas  tres  estructuras  son suficientes para escribir programas con claridad, el lenguaje también incluye la instrucción goto.

                La sintaxis de estas estructuras de control es similar. Existe una palabra reservada de inicio: if, case

                o loop, la que es pareada al final de la estructura por la misma palabra reservada precedida por la palabra end. Entonces tenemos

                if                            case                        loop

                …                            …                           …

                end if;                   end case,               end loop;

                La palabra loop puede ir precedida por un cláusula de iteración que comienza con for o while.

                5.1. Instrucción if

                La sintaxis de esta instrucción es:

                if_statement ::= if condition  then

                sequence_of_statements

                {elseif condition then

                sequence_of_statements}

                [else

                end if;

                sequence _of_statements]

                condition::= boolean_expression

                La semántica de la instrucción if de Ada es semejante a sus análogas en otros lenguajes. Veamos algunos ejemplos:

                if HUNGRY then COOK; EAT; WASH_UP;

                end if;

                Esta es la variante más simple de la instrucción if. En este ejemplo la expresión booleana (condition) está dada por una variable booleana (HUNGRY) y las instrucciones (sequence_of_statements) son llamadas a procedimientos.

                Las  instrucciones  internas  al  if  (tanto  en  la  rama  then  como  en  la  else)  pueden  tener  cualquier complejidad, incluso pueden ser instrucciones if anidadas. Por ejemplo, si deseamos encontrar las raíces de

                ax2  + bx + c = 0

                Lo primero que hay que chequear es el valor de a. Si a = 0 la ecuación cuadrática se transforma en una ecuación lineal con una única raíz igual a -c/b. Si a no es cero se debe analizar el discriminante b2-4ac para  ver si las raíces son reales o complejas.  Esto podría programarse de la siguiente manera

                if A = 0.0 then

                – caso lineal

                else

                if B**2 – 4.0*A*C  >= 0.0 then

                – raíces reales

                else

                – raíces complejas

                end if;

                end if;

                Obsérvese la repetición de end if. Esto no es muy estético y ocurre con la suficiente frecuencia para

                ameritar una construcción especial: elseif, cuyo uso se muestra a continuación

                if A = 0.0 then

                – caso lineal

                elseif B**2 – 4.0*A*C >= 0.0 then

                – raíces reales

                else

                end if;

                – raíces complejas

                De  esta  forma  se  enfatiza  el  mismo  status  de  los  tres  casos  y  la  naturaleza  secuencial  de  los

                chequeos. La rama elseif puede repetirse un número arbitrario de veces y la rama final else es opcional. Ejercicio:

                a) Las variables DAY, MONTH y YEAR contienen el día en curso, y están declaradas de la siguiente manera.

                DAY: INTEGER range 1 .. 31; MONTH: MONTH_NAME;

                YEAR: INTEGER range 1901 .. 2099;

                donde

                type MONTH_NAME IS (JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT, NOV,DEC);

                Escriba una sección de programa Ada que determine la fecha del día siguiente ¿Qué ocurrirá                  si la fecha es 31 DEC 2099?

                b)  X  e  Y  son  dos  variables  reales.  Escriba  un  bloque  que  intercambie  sus  valores,  si  es  necesario,  para asegurar que el valor mayor quede en X.

                5.2. Instrucción Case

                Una  instrucción  case  permite  elegir  una  secuencia  de  instrucciones  de  entre  varias  alternativas  de acuerdo  al  valor  de  una  expresión.  Por  ejemplo,  para  controlar  los  movimientos  de  un  vehículo  robot podríamos utilizar

                case ORDER is

                when LEFT => TURN_LEFT; when RIGHT => TURN_RIGHT; when BACK => TURN_BACK; when ON => null;

                end case;

                Es necesario explicitar todos los valores posibles (sólo una vez) para asegurar que no haya omisiones accidentales. Si para uno o más valores no se requiere una acción específica (como en el ejemplo) se debe usar la instrucción null, la cual no hace absolutamente nada, pero indica que eso es precisamente lo que se desea.

                Es  frecuente  que  se  necesite  realizar  una  misma  acción  para  varios  valores.  Por  ejemplo,  para

                expresar la idea que desde lunes a jueves se trabaja; el viernes se trabaja y se asiste a una fiesta; y los sábados

                y domingos no hay actividades fijas, podemos escribir

                case TODAY is

                when MON|TUE|WED|THU => WORK;

                when FRI                                    => WORK;

                PARTY;

                when SAT|SUN                          =>  null;

                end case;

                Si para varios valores sucesivos se realiza una misma acción, se pueden usar rangos, en el ejemplo anterior podría haberse escrito

                when MON .. THU => WORK;

                A  veces  se  requiere  que  una  cierta  acción  se  realice  para  todos  los  valores  que  no  hayan  sido explícitamente indicados; esto se realiza utilizando la palabra reservada others, la cual puede aparecer sólo en

                la última alternativa. Según esto, el ejemplo anterior podría reescribirse

                case TODAY is

                when MON|TUES|WED|THU => WORK; when FRI => WORK; PARTY;

                when  others                               =>  null;

                end case;

                Ejercicio: Reescriba la respuesta al ejercicio (a) de la pág. anterior usando la instrucción case.

                5.3. Instrucciones de iteración

                La forma más simple de una instrucción de iteración es

                loop

                sequence_of_statements

                end loop;

                Las instrucciones internas a la iteración se repiten indefinidamente hasta que una de ellas termina el loop de alguna manera.  Por ejemplo, consideremos el cálculo del número trascendental e.

                e = 1 + 1/1! + ½! + 1/3! + ¼!  +  . . . Una posible solución sería

                declare

                begin

                end;

                E:REAL := 1.0; I:INTEGER := 0; TERM:REAL :=1.0;

                loop

                I:=I+1;

                TERM:= TERM / REAL(I); E:=E + TERM;

                end loop;

                . . .

                Notemos  que  la  variable  I  es  de  tipo  entero  porque  la  lógica  del  problema  nos  indica  que  es  un

                contador, pero el valor de e a calcular debe ser real, por eso es necesario hacer el cambio de entero a real al calcular el nuevo valor de TERM en cada iteración.

                Matemáticamente hablando la serie arriba indicada tiende al valor de e cuando n tiende a infinito. Sin embargo, al realizar el cálculo computacional no es posible desarrollar la serie hasta el infinito, puesto que así nunca obtendríamos el valor requerido y, por otra parte, sabemos que la representación de los reales

                en  un  computador  no  es  exacta,  por  lo  que  tampoco  tiene  sentido  desarrollar  la  serie  hasta  un  valor  muy grande de n.  La forma más sencilla de finalizar el loop es determinar una  cantidad fija  iteraciones antes del inicio del loop.  Por ejemplo, supongamos que se decide realizar el loop N veces (N puede ser una constante

                o una variable a la que de alguna manera se le asigna un valor antes del loop), éste quedaría

                loop

                if I = N then exit; end if; I:=I+1;

                TERM:= TERM / REAL(I); E:=E + TERM;

                end loop;

                La   instrucción   exit   detiene   el   proceso   de   iteración,   pasando   el   control   a   la   instrucción inmediatamente siguiente a end loop. Puesto que la construcción

                if condition then exit; end if;

                es bastante usada, el lenguaje provee una construcción análoga más abreviada

                exit when condition;

                Por lo que el loop quedaría

                loop

                exit when I = N; I:=I+1;

                TERM:= TERM / REAL(I); E:=E + TERM;

                end loop;

                La  instrucción  exit  puede  aparecer  en  cualquier  parte  del  loop:  al  principio,  al  medio  o  al  final. Además, puede aparecer más de una vez (en distintas ramas de instrucciones if, posiblemente, anidadas), sin embargo se recomienda estructurar el loop de forma tal que exista una sola sentencia exit dentro de cada loop.

                Es bastante común que la instrucción vaya al comienzo del loop (antes de cualquier otra instrucción) como en

                nuestro ejemplo. Para estos casos se puede usar la palabra reservada while, de la siguiente manera

                while I /= N loop

                I:=I+1;

                TERM:= TERM / REAL(I); E:=E + TERM;

                end loop;

                En  los  casos  en  que  se  requiere  iterar  un  número  específico  de  veces  se  puede  usar  una  última variante de la instrucción loop, la que consiste en la utilización de la palabra reservada for. Nuestro ejemplo quedaría

                for  I in 1 .. N loop

                TERM:= TERM / REAL(I); E:=E + TERM;

                end loop;

                La variable usada para controlar la iteración (en este caso I) se declara implícitamente y no requiere

                ser  definida  externamente.  Su  tipo  se  determina  por  el  tipo  indicado  en  el  rango  de  variación,  y  para  los efectos internos del loop debe considerarse como una constante, en el sentido en que en cada iteración tiene

                un  valor  (determinado  por  el  mecanismo  del  loop)  que  no  puede  ser  modificado.  Cuando  se  termina  la iteración (por el término del rango o por una instrucción exit) I deja de existir.

                Para tomar los valores del rango en orden descendente se utiliza la palabra reservada reverse, de la manera siguiente

                for I in reverse 1 .. N loop

                Nótese que el rango  siempre se escribe en forma ascendente. No es posible definir un paso distinto

                de 1. Esto no debería causar problema porque la gran mayoría de los ciclos controlados por una variable lo hacen  con  un  paso  de  1  (ascendente  o  descendente)  y  aquellos  que  tienen  un  paso  diferente  (que  son  los menos) pueden ser simulados agregando los factores correspondientes. Por ejemplo, para calcular

                n

                å 1 / 2i

                i =1

                el código Ada más adecuado sería,

                for  I in 1 .. N loop

                TERM:= 1.0 / (2.0 * REAL(I)); E:=E + TERM;

                end loop;

                ya que refleja mejor la notación matemática estandar.

                El rango en un loop tipo for puede ser vacío (por ejemplo si N es cero en el caso anterior) en cuyo caso  el  loop  termina  en  forma  inmediata.  Los  límites  de  un  rango  no  necesariamente  son  constantes  y/  o variables, son en general expresiones de un cierto tipo discreto (obviamente, el mismo para ambos límites). Las expresiones que determinan los límites son evaluadas una sola vez antes del inicio de la primera iteración

                y no pueden ser cambiados dentro del loop. Por este motivo el loop

                N:=4;

                for I in 1 .. N loop

                . . . N:=10;

                end loop;

                será ejecutado 4 veces a pesar que el valor de N haya sido cambiado a 10.

                Como ya se dijo, el rango debe ser  de algún tipo discreto, es decir, no necesariamente de tipo entero. Por ejemplo, para realizar una cierta acción para cada uno de los días de la semana escribiríamos

                for TODAY in DAY loop

                . . .

                end loop;

                si quisieramos  sólo realizar la acción para los días lunes a viernes escribiríamos

                for TODAY in MON … FRI  loop

                . . .

                end loop;

                pero una manera más adecuada sería

                for TODAY in WEEKDAY  loop

                . . .

                end loop;

                Como ya dijimos, la instrucción exit pasa el control al punto inmediatamente siguiente al loop dentro

                del cual se encuentra. Pero los   loops pueden estar anidados y a veces ocurre que se desea salir de toda la estructura  anidada  y  no  solo  del  loop  más  interno.  Por  ejemplo,  supongamos  que  estamos  realizando  una búsqueda en dos dimensiones.

                for I in 1 .. N loop

                for J in 1 .. M loop

                – si los valores de I y J satisfacen una cierta

                – condición terminar la búsqueda

                end loop;

                end loop;

                Una  instrucción  exit  simple  nos  sacaría  del  loop  interno,  pero  estaríamos  obligados  a  chequear nuevamente  la  condición  (agreguese  el  inconveniente  de  que  J  ya  no  existe)  inmediatamente  después  de terminado el loop interno. Este problema puede ser resuelto dando un nombre al loop externo y usando dicho nombre para salir inmediatamente de ambos.

                SEARCH:

                for I in 1 .. N loop

                for J in 1 .. M loop

                if condition_O_K then I_VALUE :=  I; J_VALUE := J;

                exit SEARCH;

                end if;

                . . . end loop;

                end loop SEARCH;

                — el control pasa a este punto

                Un loop es bautizado colocando un identificador antes de su inicio seguido de dos puntos. (Esto se parece mucho a la forma en que en otros lenguajes se definen labels, pero en Ada no es así, ya que no es posible  usar  los  nombres  de  loops  para  la  instrucción  Goto.)  El  identificador  debe  ser  repetido  entre  el correspondiente end loop y el punto y coma final.

                La instrucción exit  en su forma condicional también puede referenciar a un loop por su nombre.

                exit SEARCH when condition;

                Ejercicio:

                a) Calcule

                n

                1

                g = å p  - ln n
                p=1

                n ® ¥, g ® g

                = 0.577215665

                La función Ada LN entrega el logaritmo natural, el parámetro debe ser real y el resultado también es real.

                b)  Calcule  e  con  un  error  absoluto  de  0.00005.  Es  decir,  la diferencia  en  valor  absoluto  entre  dos  valores

                (calculados) de e consecutivos debe ser menor o igual a 0.00005.

                5.4. Instrucción goto y labels

                A muchos podría sorprender el que un lenguaje de programación moderno contemple la instrucción goto, puesto que su uso  se ha considerado una mala práctica de programación. Ada incluye esta instrucción, pero no estimula su uso al entregar estructuras de control suficientemente variadas para expresar cualquier flujo de control normalmente usado.

                La  generación  automática  de  programas  es  el  motivo  principal  para  incluir  la  instrucción  goto  en

                Ada. Cuando se genera automáticamente un programa Ada desde una cierta especificación de más alto nivel, ésta última será considerada como “el programa fuente”, por lo que el código Ada no tiene porque ser muy legible y es posible realizar cierta licencias, entre ellas el uso del goto. Además, al traducir un programa desde Cobol o Fortran a  Ada  puede resultar conveniente usar goto para hacer una traducción literal del programa original.

                La instrucción goto en Ada tiene una sintaxis semejante a la de otros lenguajes, la palabra reservada

                goto seguido de un nombre de label, el cual se define entre paréntesis angulares dobles. Por ejemplo el label

                de nombre INICIO se define

                <<INICIO>>

                colocándolo  en  el  lugar  donde  se  desea  que  continúe  el  control  del  flujo  de  ejecución  del  programa.  La instrucción en sí tendrá la siguiente forma

                goto  INICIO;

                Una instrucción goto no puede ser usada para transferir el control al interior de un if, case o loop, ni entre las ramas de las instrucciones if o case.

                6. Tipos Compuestos

                En esta sección se describirán los tipos compuestos, es decir, arreglos y registros. Además, con la introducción de los caracteres y strings  se completará la discusión sobre los tipos de enumeración.

                6.1. Arreglos

                Un arreglo es un objeto compuesto que consiste de un grupo de componentes todos del mismo tipo. Un arreglo puede ser de una, dos o más dimensiones. Una declaración típica de un arreglo es

                A: array (INTEGER range 1..6) of REAL

                Aquí se declara la variable A como un objeto que contiene 6 componentes, cada uno de los cuales es

                de tipo REAL. Para referirse a los componentes individuales se debe indicar el nombre del array seguido de una expresión entre paréntesis que entrega un valor entero dentro del rango discreto 1..6. Si el resultado de esta expresión, conocida como el valor del índice, no está dentro del rango especificado, el sistema emitirá la excepción CONSTRAIN_ERROR. Para colocar ceros en cada componente de A podríamos escribir

                for I in 1..6 loop

                A(I) := 0.0;

                end loop;

                Como se dijo, un arreglo puede tener varias dimensiones, en cuyo caso se debe indicar un rango para cada dimensión. Entonces

                AA: array (INTEGER range 0..2, INTEGER range 0..3) of REAL;

                es  un  arreglo  de  12  componentes,  cada  uno  de  los  cuales es  referenciado  mediante  dos  índices  enteros,  el primero  dentro  del  rango  0..2  y  el  segundo  en  el  rango  0..3.  Para  colocar  todos  estos  elementos  en  cero podríamos escribir

                for I in 0 .. 2 loop for J in 0 .. 3 loop AA(I,J) := 0.0; end loop;

                end loop;

                Los rangos no deben ser necesariamente estáticos, por ejemplo, es posible escribir

                N: INTEGER:= . . .;

                . . .

                B: array (INTEGER range 1..N) of BOOLEAN;

                así, la cantidad de componentes de B estará determinada por el valor N, el que lógicamente deberá tener un valor antes de realizar la elaboración de B.

                Los rangos discretos en los arreglos siguen reglas similares a los rangos de los sentencias for. Una

                de ellas es que un rango de la forma 1..6 implica que el tipo asociado es INTEGER, de ahí que podamos escribir

                A: array (1..6) of REAL;

                ejemplo

                Sin  embargo,  el  índice  de  un  arreglo  puede  ser  de  cualquier  tipo  discreto.  Podríamos  tener  por

                HOURS_WORKED: array (DAY) of REAL;

                Este       arreglo       tiene       siete       componentes,      desde       HOURS_WORKED(MON)         hasta HOURS_WORKED(SUN). Podemos usar este arreglo para almacenar las horas de trabajo para cada día de la semana

                for D in WEEKDAY loop

                HOURS_WORKED(D) := 8.0; end loop; HOURS_WORKED(SAT) := 0.0; HOURS_WORKED(SUN) := 0.0;

                Si  quisiésemos  que  el  arreglo  sólo  tuviera  cinco  elementos,  uno  para  cada  día  de lunes a viernes, podríamos escribir

                HOURS_WORKED: array (DAY range MON..FRI) of REAL;

                o mejor

                HOURS_WORKED: array (WEEKDAY) of REAL;

                Los  arreglos  tienen  varios  atributos  relacionados  con  sus  índices.  A’FIRST  y  A’LAST  entregan, respectivamente,   los límites inferior y superior del primer (o único) índice de A. Entonces, de acuerdo a la última declaración de HOURS_WORKED

                HOURS_WORKED’FIRST = MON HOURS_WORKED’LAST = FRI

                A’LENGTH entrega la cantidad de valores del primer (o único) índice de A.

                HOURS_WORKED’LENGTH = 5

                A’RANGE es una forma abreviada para A’FIRST .. A’LAST. Entonces

                HOURS_WORKED’RANGE = MON .. FRI

                Los mismos atributos pueden aplicarse a los demás índices de un arreglo multidimensional, para ello

                se  debe  agregar  la  dimensión  (una  expresión  entera  estática)  involucrada  entre  paréntesis.  Entonces,  en  el caso de nuestro arreglo bidimensional AA tenemos

                AA’FIRST(1) = 0

                AA’FIRST(2) = 0

                AA’LAST(1) = 2

                AA’LAST(2) = 3

                AA’LENGHT(1) = 3

                AA’LENGHT(2) = 4

                AA’RANGE(1) = 0 .. 2

                AA’RANGE(2) = 0 .. 3

                El atributo RANGE es particularmente útil con iteraciones. Nuestros ejemplos anteriores quedarían mejor escritos como

                for I in A’RANGE loop

                A(I) := 0.0;

                end loop;

                for I in AA’RANGE(1) loop

                for J in AA’RANGE(2) loop

                AA(I,J) := 0.0;

                end loop;

                end loop;

                El atributo RANGE también puede utilizarse en declaraciones, por ejemplo

                J: INTEGER range A’RANGE;

                es equivalente a

                J: INTEGER range 1 .. 6;

                En  lo  posible,  es  mejor  utilizar  los  atributos  para  reflejar  las  relaciones  entre  entidades  de  un programa, de este modo los cambios quedan restringidos a las declaraciones sin que, en general, impliquen

                cambios  en  el  resto  del  código.  Por  ejemplo,  si  cambiamos  los  rangos  de  los  arreglos  A  y  AA,  no  será necesario modificar los ciclos for usados para asignarles valores.

                Los arreglos que hemos visto hasta el momento son variables en el sentido normal. Por lo tanto, se

                les puede asignar valores y ser usadas en expresiones. Al igual que otras variables, a los arreglos se les puede asignar un valor inicial. Esto generalmente se denota mediante un conjunto que es la notación literal de un arreglo.  La  forma  más  simple  de  realizar  esta  asignación  es  mediante  una  lista  ordenada  (encerrada  entre paréntesis) de expresiones (separadas por comas) que entregan los valores de los componentes. Por ejemplo, para inicializar el arreglo A con ceros, escribimos

                A: array (1 .. 6) of REAL := (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);

                En el caso de arreglos multidimensionales la lista será una lista con sublistas, por ejemplo

                AA: array (0 .. 2, 0 .. 3) of REAL := ( (0.0,  1.0,  2.0,      3.0), (4.0,  5.0,  6.0,    7.0),

                (8.0,  9.0,  10.0, 11.0) ); De acuerdo a esta declaración, tenemos

                AA(0,0) = 0.0

                AA(0,1) = 1.0

                AA(0,2) = 2.0

                AA(0,3) = 3.0

                AA(1,0) = 4.0

                etc.

                Nótese que la lista debe estar completa, es decir, si se desea inicializar un componente del arreglo, entonces deben inicializarse todos, y en el orden correcto.

                Es posible declarar un arreglo como constante, en cuyo caso, obviamente, es obligatorio dar un valor inicial.  Este  tipo  de  arreglos  es  útil  para  implementar  tablas.  En  el  siguiente  ejemplo  se  usa  un  arreglo constante para determinar si un día en particular es día de trabajo o no.

                WORK_DAY: constant array (DAY) of BOOLEAN := (TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE);

                Un ejemplo interesante lo constituye un arreglo constante que permite determinar el día siguiente, sin preocuparse del último día de la semana.

                TOMORROW: constant array (DAY) of DAY := (TUE, WED, THU, FRI, SAT, SUN, MON); Finalmente,  notemos  que  los  componentes  de  un  arreglo  pueden  ser  de  cualquier  tipo  o  subtipo.

                Además, las dimensiones de un arreglo multidimensional pueden ser de diferentes tipos discretos. Un ejemplo

                un tanto extraño, pero válido, sería

                STRANGE: array (COLOUR, 2 .. 7, WEEKDAY range TUE .. THU)

                of PLANET range MARS .. SATURN;

                Ejercicio:

                1. Declare un arreglo F de enteros cuyo índice varíe entre 0 y N. Además, escriba código Ada para asignar a los componentes de F su correspondiente valor de la serie de Fibonaci.

                F0 = 0,  F1 = 1, Fi = Fi-1 + Fi-2

                2. Escriba código Ada para encontrar los índices I y J de componente con mayor valor del arreglo

                A:array (1 .. N, 1 .. M) of REAL;

                3. Declare un arreglo DAYS_IN_MONTH que permita determinar el número de días de cada mes.

                4. Declare un arreglo YESTERDAY análogo al arreglo TOMORROW dado en un ejemplo anterior.

                5. Declare un arreglo BOR tal que:   BOR(P,Q) = P or Q

                6. Declare una matriz constante unitaria UNIT de orden 3. Una matriz unitaria es aquella que tiene unos en su diagonal principal y ceros en todos los demás componentes.

                6.2. Tipos arreglo

                Los  arreglos  hasta  ahora  vistos  no  tienen  un  tipo  explícito.  De  hecho  son  de  tipo  ”anónimo”. Reconsiderando el primer ejemplo del punto anterior, podemos escribir la   definición de tipo

                type VECTOR_6 is array (1 .. 6) of REAL;

                y luego declarar  A usando el tipo definido  de la manera ya conocida

                A: VECTOR_6;

                Una ventaja de usar tipos es que es posible realizar asignaciones de arreglos completos que han sido declarados separadamente.  Por ejemplo, si además declaramos

                B:VECTOR_6;

                podemos escribir

                B:= A;

                que sería una forma abreviada y más recomendable de

                B(1):= A(1); B(2):= A(2); . . .B(6):= A(6); Por otro lado, si escribimos

                C: array (1 .. 6) of REAL; D: array (1 .. 6) of REAL;

                la expresión C:= D; será ilegal debido a que C y D no son del mismo tipo. Son de diferentes tipos, ambos anónimos. La regla que subyace en todo esto es que cada definición de tipo introduce un nuevo tipo y

                en este caso la sintaxis nos dice que una definición de tipo arreglo es el trozo de texto que comienza con la palabra array hasta el punto y coma (exclusive). Incluso, si escribimos

                C,D: array (1 .. 6) of REAL;

                la  expresión  C:=  D;  seguirá  siendo  incorrecta,  esto  porque  esta  declaración    múltiple  es  sólo  una  forma abreviada de las dos declaraciones anteriores.

                El que se defina o no un nuevo tipo depende mucho del grado de abstracción que se pretenda lograr.

                Si se está pensando en los arreglos como objetos que serán manipulados (asignados, mezclados, etc..) entre ellos,  se  hace  necesario  definir  un  tipo.  Si,  por  otro  lado,  se  conceptualiza  al  arreglo  sólo  como  una agrupación  de  elementos  indexables,  sin  relación  alguna  con  otros  arreglos  semejantes,  entonces  lo  más recomendable es definirlo como de tipo anónimo.

                El modelo de tipos arreglo presentado no es del todo satisfactorio, pues no nos permite representar una abstracción que agrupe arreglos con diferentes límites, pero que para todos los demás efectos forman una sola familia. En particular, no nos permite escribir subprogramas que puedan tomar arreglos de largo variable como parámetros reales. De ahí que Ada introduce el concepto de tipo arreglo no restringido, en el cual no se expresan los límites al momento de la declaración. Consideremos

                type VECTOR is array (INTEGER range <>) of REAL; (Al símbolo compuesto <> se le denomina “caja”).

                Con  esto  se  está  diciendo  que  VECTOR  es  el  nombre  de  un  tipo  arreglo  unidimensional  de componentes REAL   con índice INTEGER. Sin embargo, los límites del rango no se han especificado. Esta información debe (obligatoriamente) aportarse cuando se declaran variables o constantes de tipo VECTOR. Esto  puede  hacerse  de  dos  formas.  Podemos  introducir  un  subtipo  intermedio  y  luego  declarar  los  objetos (variables y/o constantes).

                subtype VECTOR_5 is VECTOR(1..5); V: VECTOR_5;

                o podemos declarar los objetos directamente

                V:VECTOR(1 .. 5);

                En ambos casos los límites deben darse mediante una restricción de índices que toman la forma de

                un rango discreto entre paréntesis.

                El  índice  del  tipo  arreglo  no  restringido  también  puede darse a través de un subtipo. Entonces, si

                tenemos

                type P is array (POSITIVE range <>) of REAL;

                los límites reales de cualquier objeto deben estar dentro del rango estipulado por el subtipo POSITIVE.

                Otra definición de tipo muy útil es

                type MATRIX is array (INTEGER range <>, INTEGER range <>) of REAL;

                la que luego nos permite definir subtipos como

                subtype MATRIX_3 is MATRIX(1 ..3, 1 .. 3);

                y también variables

                M:MATRIX(1 .. 3, 1 .. 3);

                Volvamos al asunto de la asignación entre arreglos completos. Para realizar este tipo de asignación

                es necesario que los arreglos ubicados a ambos lados del comando de asignación sean del mismo tipo y que

                sus  componentes  puedan  ser  pareados.  Esto  no  significa  que  los  límites  tengan  que  ser  necesariamente iguales, sino sólo que la cantidad de componentes en las dimensiones correspondientes sea la misma. De ahí que podamos escribir

                V:VECTOR(1 .. 5); W:VECTOR(0 .. 4);

                . . . V:= W;

                Tanto V como W son de tipo VECTOR y tienen 5 componentes. También sería válido escribir

                P:MATRIX(0 .. 1, 0 .. 1);

                Q: MATRIX(6 .. 7, N .. N+1);

                . . . P:= Q;

                La igualdad y diferencia entre arreglos siguen reglas similares a la asignación. Dos arreglos pueden

                ser  comparados  sólo  sin  son  del  mismo  tipo,  y  son  iguales  si  sus  dimensiones  correspondientes  tienen  el mismo número de componentes y si los componentes pareados son iguales.

                Los atributos FIRST, LAST, LENGHT y RANGE también pueden aplicarse a tipos y subtipos en  la medida estén restringidos, entonces

                VECTOR_6′LENGHT = 6

                pero

                VECTOR’LENGHT   es ilegal.

                6.3. Caracteres y strings

                Completaremos la discusión de los tipos de enumeración introduciendo los tipo caracter. En los tipos

                de enumeración vistos anteriormente, tal como

                type COLOUR is (RED, AMBER, GREEN);

                los valores habían sido representados por identificadores. También es posible tener un tipo de enumeración en

                el que parte o todos los valores están representados por literales de caracter.

                Un  literal  de  caracter  es  otra  forma  que  puede  tomar  un  lexema.  Consiste  de  un  caracter  único encerrado entre apóstrofes. El caracter debe ser un caracter imprimible o un espacio blanco. No puede ser un caracter de control, tales como tabulador horizontal o nueva línea.

                Este es un caso donde se hace distinción entre mayúsculas y minúsculas. De ahí que los literales

                ‘A’          y              ‘a’

                sean diferentes.

                Basados en lo dicho, podemos definir el tipo de enumeración.

                type ROMAN_DIGIT is (‘I’, ‘V’, ‘X’, ‘L’, ‘C’, ‘D’, ‘M’);

                y luego declarar

                DIG: ROMAN_DIGIT:= ‘D’;

                Además, son aplicables todos los atributos de los tipos de enumeración. ROMAN_DIGIT’FIRST = ‘Y’

                ROMAN_DIGIT’SUCC(‘X’) = ‘L’ ROMAN_DIGIT’POS(‘M’) = 6

                Existe  un  tipo  de  enumeración predefinido de nombre CHARACTER que es (obviamente) el tipo caracter. Su declaración es aproximadamente

                type CHARACTER is (null, . . ., ‘A’, ‘B’, ‘C’, . . ., del);

                Nótese que la introducción de los tipos ROMAN_DIGIT y CHARACTER lleva a la sobrecarga de algunos literales. De ahí que la expresión

                ‘X’  < ‘L’

                sea ambigua. No sabemos si se están comparando caracteres de tipo ROMAN_DIGIT o CHARACTER. Para resolver esta ambigüedad se debe cualificar uno o ambos argumentos

                CHARACTER’(‘X’) < ‘L’ = FALSE ROMAN_DIGIT’(‘X’) < ‘L’ = TRUE

                Así como existe el tipo predefinido CHARACTER, existe el tipo predefinido STRING

                type STRING is array (POSITIVE range <>) of CHARACTER;

                Este es un tipo arreglo normal y por lo tanto sigue las reglas previamente enunciadas. Por lo tanto, podemos escribir

                S:STRING(1 .. 7);

                para declarar un arreglo de rango 1 .. 7. En el caso de arreglos constantes es posible deducir los límites a partir del valor inicial. Entonces, si escribimos

                G: constant STRING:= (‘P’, ‘I’, ‘G’);

                el límite inferior de G (es decir, G’FIRST) será 1, puesto que el tipo del índice de STRING es POSITIVE y

                POSITIVE’FIRST es 1.

                Existe  una  notación  alternativa  más  abreviada  para  los  strings,  esta  consiste  en  una  cadena  de caracteres encerrada entre comilla dobles. El ejemplo anterior quedaría

                G: constant STRING:= “PIG”;

                El  uso  más  importante  de  los  strings  es,  obviamente,  la  creación  de  textos  de  salida.  Es  posible “imprimir” una secuencia simple de caracteres usando el subprograma (sobrecargado) PUT. Por ejemplo, la llamada

                PUT(“The Countess of Lovelace”);

                colocará el texto

                The Countess of Lovelace en algún archivo apropiado.

                Es  posible  aplicar  los  operadores  relacionales  <,  <=,  >,  y  >=  sobre  los  string.  Las  reglas  son  las ampliamente usadas en distintos lenguajes. Por ejemplo, todas las comparaciones siguientes son verdaderas.

                “CAT” < “DOG”

                “CAT” < “CATERPILLAR” “AZZ” < “B”

                “” < “A”

                Existe,  además,  el  operador  de  concatenación  &,  que  permite  unir  dos  strings  para  generar  uno nuevo. El límite inferior del resultado es igual al límite inferior del primer operando. Por ejemplo, si tenemos

                STRING_1:STRING:=”CAT”; STRING_2:STRING(11 .. 18):=”ERPILLAR”;

                entonces

                STRING_1 & STRING_2 =  ”CATERPILLAR” STRING_2 & STRING_1 =  ”ERPILLARCAT”

                es decir, dos strings de igual largo, pero con rangos  diferentes, en la primera expresión el rango es 1 .. 11 y la segunda  11  ..  21.  En  todo  caso,  cuando  se  están  generando  strings  para  salidas  los  rangos  en  sí  no  son relevantes.

                Finalmente, es posible tomar sólo una parte de un string, ya sea para utilizarlo en una expresión, o para asignar ciertos valores  a una parte específica de un string. Por ejemplo, si escribimos

                S:STRIG(1 .. 10):= “0123456789″;

                . . .

                T: constant STRING:=S(3 .. 8);

                . . .

                S(1 .. 3):= “ABC”;

                al  final  T  tendrá  el  valor  ”234567″,  con  T’FIRST  =  3  y  T’LAST  =  8.  Y  S  terminará  con  el  contenido

                “ABC3456789″.

                Ejercicio:

                1. Declare un arreglo   constante de nombre ROMAN_TO_INTEGER que pueda ser usado como una tabla para convertir un ROMAN_DIGIT en su entero equivalente. Por ejemplo, para convertir ‘C’ en 100.

                2.  Escriba  código  Ada  que  tome  un  objeto  R  de  tipo  ROMAN_INTEGER  y  lo  calcule  el  valor  entero correspondiente en la variable V. Debe asumirse que R es un número romano correctamente escrito.

                6.4. Registros

                Un  registro  es  un  objeto  compuesto  cuyos  componentes  tienen  nombres  y  pueden  ser  de  tipos distintos. A diferencia de los arreglos, no es posible tener registros de tipo anónimo. Es obligación definir un tipo  registro  explícito  para  posteriormente  crear  los  objetos  (constantes  y/o  variables)  correspondientes. Consideremos el siguiente tipo registro

                type MONTH_NAME is (JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC);

                type DATE is record

                DAY:INTEGER range 1 .. 31;

                MONTH:MONTH_NAME; YEAR:INTEGER;

                end record;

                DATE  es  un  tipo  registro  que  contiene  tres  componentes  con  nombre:  DAY,  MONTH  y  YEAR. Ahora, podemos definir variables y constantes de la manera usual.

                D:DATE;

                declara  una  variable  de  tipos  DATE.  Mediante  la  notación  punto  se  pueden  accesar  los  componentes individuales de D. Así,  para asignar valores a estos componentes podemos escribir

                D.DAY:= 15; D.MONTH:=AUG; D.YEAR:= 1959;

                Los registros pueden ser manipulados como objetos completos. Además, es posible asignar valores a todos los componentes de un registro mediante un conjunto ordenado. Por ejemplo

                D:DATE:= (15,AUG,1959); E:DATE;

                . . . E:= D;

                Es posible dar valores por omisión a todos o algunos de los componentes. Por ejemplo, al escribir

                type COMPLEX is record

                RL: REAL:= 0.0;

                IM: REAL:= 0.0;

                end record;

                se está declarando un tipo que contiene dos componentes de tipo REAL y cada uno de ellos se le da un valor por omisión de  0.0. Ahora podemos definir

                C1:COMPLEX;

                C2: COMPLEX:= (1.0, 0.0);

                La  variable  C1  tiene  sus  componentes  con  los  valores  por  omisión,  es  decir,  0.0.  Por  su  parte,  la variable C2 recibe valores que reemplazan a los indicados en la definición del tipo. Nótese que a pesar que el segundo   componente   de   C2   toma   el   mismo   valor   estipulado   por   omisión,   es   necesario   indicarlo explícitamente. Esto se debe a que cuando se asignan los valores mediante un conjunto, es obligación dar el valor para todos y cada uno de los componentes.

                Las únicas operaciones predefinidas para los registros son = y /=, y la asignación. Es posible realizar otras operaciones a nivel de registro o definirlas explícitamente en subprogramas, como se verá más adelante.

                Los componentes de un registro pueden ser de cualquier tipo; entre otros pueden ser otros registros o arreglos.  Sin  embargo,  si  el  componente  es  un  arreglo,  este  debe  ser  restringido  y  no  puede  ser  de  tipo anónimo. Y, obviamente, un registro no puede contener una instancia de sí mismo.

                Los componentes no pueden ser constantes, pero un registro en sí si puede serlo. Por ejemplo, para representar la unidad imaginaria (raíz cuadrada de -1) podemos declarar

                I: constant COMPLEX:= (0.0, 1.0);

                A continuación presentamos un ejemplo más elaborado de un registro

                type PERSON is

                record

                BIRTH: DATE; NAME:STRING (1 .. 20); end record;

                El registro PERSON tiene dos componentes, el primero es otros registro y el segundo es un arreglo. Ahora podemos escribir, por ejemplo

                JOHN:PERSON; JOHN.BIRTH:=(19, AUG, 1937); JOHN.NAME(1 .. 4):=”JOHN”;

                y tendremos

                JOHN := ((19, AUG, 1937), “JOHN                    “); Ejercicio:

                1. Declare tres variables C1, C2 y C3 de tipo COMPLEX; y escriba uno o más sentencias para realizar la suma y  el producto de C1 y C2, guardando el resultado en C3.

                7. Subprogramas

                En Ada existen dos tipos de subprogramas: funciones y procedimientos. Las funciones retornan un valor y son usadas en expresiones, mientras que los procedimientos no retornan valor y son llamados como instrucciones.

                Las acciones a realizar en   un subprograma se describen dentro de lo que se denomina “cuerpo del subprograma”, el que es declarado de la manera usual en la parte declarativa de, por ejemplo, un bloque u otro subprograma.

                7.1. Funciones

                Todas las funciones comienzan con la palabra reservada function seguida del nombre (identificador)

                de  la  función.  Si  existen  parámetros,   después  del  identificador   se  entrega  una  lista  con  los  parámetros (separados  por  ”;”)  encerrada  entre  paréntesis.  Luego  de  la  lista  de  parámetros  (si  existe)  viene  la  palabra return y el tipo (o subtipo) del valor de retorno de la función. Tanto el tipo de los parámetros como del valor

                de retorno debe ser indicado con un identificador de tipo (o subtipo) declarado previamente. Por ejemplo, no

                es posible indicar que el valor de retorno será un cierto rango de los enteros indicándolo explícitamente luego

                de  la  palabra  return,  sino  que  es  necesario  definir  un  subtipo  para  dicho  rango  con  anterioridad  a  la declaración de la función y escribir el nombre del subtipo a continuación de la palabra return.

                A la parte descrita hasta ahora  se le conoce como “especificación de la función” y es la que entrega

                los datos para el entorno, en el sentido que en ella se entrega la información necesaria y suficiente para llamar

                a (hacer uso de) la función.

                Después de la especificación viene la palabra is seguida del cuerpo de la  función, que es semejante a

                un   bloque:  una  parte  declarativa,  begin,  una  secuencia  de  instrucciones  y  end.  Como  en  el  caso  de  los bloques, la parte declarativa puede no existir, pero al menos debe existir una instrucción.

                En  algunos  casos  (como  veremos  más  adelante)  es  necesario  escribir  sólo  la  especificación  de  la función, sin su cuerpo. En este caso en lugar de la palabra is va un “;”.

                Los parámetros formales son objetos locales de una función y actúan como constantes cuyos valores

                iniciales son calculados de acuerdo a los correspondientes parámetros reales. Cuando una función es llamada (utilizada dentro de un expresión) se elabora la parte declarativa de la manera usual y luego se ejecutan las instrucciones.  Para  entregar  el  valor  de  retorno  se  utiliza  la  instrucción  return,  la  cual  además  entrega  el control al lugar desde donde se hizo la llamada.

                Consideremos nuestro ejemplo de la raíz cuadrada:

                function SQRT(X:REAL) return REAL is

                R:REAL;

                begin

                –  calcular valor de la raíz de X y guardarlo en R

                return R;

                end SQRT;

                La función puede ser utilizada, por ejemplo, en

                T:REAL:= 0.3;

                . . .

                S:= SQRT(T + 0.5) + 3.6;

                para ello se evalúa la expresión T + 0.5   (es decir, T + 0.5 es el parámetro real) y se asigna al parámetro formal X, el que dentro de la función se comporta como una constante, con valor inicial 0.8. Esto equivale a tener

                X: constant REAL := T + 0.5;

                Luego  se  elaboran  las  declaraciones,  si  las  hubiera,  y finalmente  se  ejecutan  las  instrucciones.  La última de las cuales es la instrucción return, la que retorna el control a la expresión “SQRT(T + 0.5) + 3.6″ junto con el valor contenido en R.

                La expresión de una instrucción return puede ser de cualquier nivel de complejidad, lo importante

                es que el resultado de dicha expresión sea del tipo indicado como tipo de retorno en la especificación de la función.

                El cuerpo de una función puede contener más de una instrucción return. La ejecución de cualquiera

                de ellas termina la función. Por ejemplo,

                function SIGN(X:INTEGER) return INTEGER is

                Entrega +1, 0 o -1 según sea el signo del entero X

                begin

                if X > 0 then return +1;

                elseif X < 0 then return  -1;

                else

                end if;

                end SIGN;

                return 0;

                Puede  verse  que  la  última  instrucción  no  es  necesariamente  un  return  ,  lo  importante  es  que  la

                semántica  del  cuerpo  de  la  función  considere  una  instrucción  return  para  todos  los  posibles  casos.  Si  no fuese  así  y  se  llegase  ”end  SIGN;”  el  sistema  entregaría  la  excepción  PROGRAM_ERROR  (este  tipo  de excepción se usa para situaciones en las cuales se ha violado la secuencia de control en tiempo de ejecución).

                Notemos  que  cada  llamada  a  una  función  genera  una  nueva  instancia  de  cada  uno  de  los  objetos

                declarados en ella (incluyendo, lógicamente, los parámetros) y éstos desaparecen cuando la función termina. Por este motivo (es decir, la administración dinámica de memoria) es posible llamar recursivamente a una función. Por ejemplo:

                function FACTORIAL(N:POSITIVE) return POSITIVE is begin

                if N = 1 then return 1;

                else      return N * FACTORIAL(N-1);

                end if;

                end FACTORIAL; Si escribimos

                F:= FACTORIAL(4);

                se produce la llamada recursiva a la función FACTORIAL con parámetros reales 4, 3, 2 y 1. A veces se dice que  una  función  es  recursiva  puesto  que   ”se  llama  a  si  misma”.  Sin  embargo,  es  necesario  entender  que cuando  se  llama  a  FACTORIAL(4)  se  genera  una  instancia  de  la  función,  es  decir,  se  reserva  espacio  de memoria para todos los objetos  locales (más otros datos) y se procede a ejecutar las instrucciones. Cuando se llega   a “4*FACTORIAL(4 – 1)”,   se genera otra instancia de la función, es decir,   se toma más espacio de memoria  para  localizar  los  objetos  locales,  por  lo  tanto  en  este  punto  hay  dos  áreas  de  memoria  para almacenar el estado de cada una de las dos llamadas (FACTORIAL(4) y FACTORIAL(3)). Esto se repetirá,

                en este caso, cuatro veces. Al ejecutar el cuerpo de la llamada FACTORIAL(1)   no se genera una   nueva instancia,  sino  que  retorna  el  valor  positivo  1.  En  este  momento,  la  llamada  FACTORIAL(1)  termina,  sus objetos  locales  dejan  de  existir,  con  lo  que  el  espacio  para  ella  reservado  también  desaparece.  Lo  mismo ocurrirá  para  las  llamadas  FACTORIAL(2),  FACTORIAL(3)  y  finalmente  FACTORIAL(4).  Lógicamente,

                no es necesario repetir el código de la función, cada instancia de la función controla el número de instrucción que se está ejecutando.

                No es necesario chequear que el parámetro sea positivo, pues el  parámetro formal N es del subtipo POSITIVE. Por lo tanto, si se intenta FACTORILA(-2) se obtendrá CONSTRAIN_ERROR. Sin embargo, si intentamos   FACTORIAL(1000)   podría   generarse   una   excepción   STORAGE_ERROR   puesto   que   las instancias de mil llamadas a la función estarían presentes en un   cierto momento. Por otro lado, la llamada FACTORIAL(70) podría generar la excepción NUMERIC_ERROR..

                Como ya se indicó un parámetro formal puede ser de cualquier tipo, pero dicho tipo (o subtipo) debe tener un nombre. Por lo tanto, está permitido que los parámetros puedan ser de tipo array no restringido. Por ejemplo, si definimos el tipo VECTOR como

                type VECTOR is array (INTEGER range <>) of REAL;

                podemos escribir la función

                function SUM(A:VECTOR) return REAL is

                RESULT: REAL:= 0.0;

                begin

                for I in A´RANGE loop

                RESULT := RESULT + A(I);

                end loop;

                return RESULT;

                end SUM;

                En  este  caso  los  límites  del  vector  A  serán  tomados  del  arreglo  de  tipo  VECTOR  usado  como parámetro  real.  Recordemos  que  todas  las  variables  y  constantes  del  tipo  VECTOR  deben  tener  límites definidos.

                Entonces podemos escribir

                V:VECTOR(1 .. 4):= (1.0,2.0,3.0,4.0); W:VECTOR(-1 .. 3):= (1.5,2.5,3.5,4.5,5.5); S:REAL:=SUM(V);

                T:REAL;

                . . . T:=SUM(W);

                con lo que S tomará el valor 10 y T el valor 17.5.

                Lógicamente, en Ada una función puede tener parámetros de tipo array restringido, pero debe ser por medio de un nombre de tipo o subtipo. Por ejemplo, sería incorrecto escribir

                function SUM_6(A:VECTOR(1 .. 6)) return REAL

                debería definirse un tipo o subtipo y luego usarlo para indicar la naturaleza del parámetro. Por ejemplo:

                type VECTOR_6_A is array (1 ..  6) of REAL;

                subtype VECTOR_6_B is VECTOR(1..6);

                function SUM_6_A(A:VECTOR_6_A) return REAL;

                function SUM_6_B(A:VECTOR_6_B) return REAL;

                Lógicamente, el elegir entre definir un nuevo tipo o un subtipo dependerá de los requerimientos del problema  a  resolver.  La  diferencia   entre  SUM_6_A  y  SUM_6_B  radica  en  que  la  segunda  podrá  aceptar arreglos de tipo VECTOR_6_B, VECTOR y otros subtipos de éste (en la media que contengan 6 elementos indexados del 1 al 6), en cambio SUM_6_A sólo aceptará arreglos del tipo VECTOR_6_A.

                Consideremos otro ejemplo:

                function INNER(A,B. VECTOR) return REAL is

                RESULT: REAL := 0.0;

                begin

                for I in A´RANGE loop

                RESULT.= RESULT + A(I) * B(I);

                end loop;

                return RESULT;

                end INNER;

                La función INNER calcula el producto interno entre dos vectores A y B.  Tenemos aquí un ejemplo

                de una función con más de un parámetro.

                La función INNER no es un código robusto, puesto que sólo funciona correctamente cuando ambos parámetros A y B tiene los mismos límites y no se controla los casos en que éstos difieren. Por ejemplo:

                T:VECTOR(1 .. 3):= (1.0, 2.0, 3.0); U:VECTOR(1 .. 3):= (2.0, 3.0, 4.0); V:VECTOR(0 .. 2):= (3.0, 4.0, 5.0); W:VECTOR(1 .. 4):= (4.0, 5.0, 6.0, 7.0);

                . . .

                R:=INNER(T,U);   – R=1.0*2.0 + 2.0*3.0 + 3.0*4.0 = 20.0

                R:=INNER(T,V);     — CONSTRAIN_ERROR al intentar accesar  B(3) R:=INNER(T,W);   – R=1.0*4.0 + 2.0*5.0 + 3.0*6.0 = 32.0

                En  el  tercer  caso  se  obtiene  un  valor  (32.0),  pero  es  erróneo  calcular  el  producto  interno  de  dos vectores  de  distinto  largo.  Sería  deseable  que  el  lenguaje  proveyera  un  mecanismo  para  chequear  en  el momento de la llamada que los límites de A y B coincidan, pero lamentablemente no es así. La solución más adecuada  es  verificar  los  límites  al  comienzo  de  la  función  y,  posiblemente,  generar  explícitamente  una excepción CONSTRAIN_ERROR.

                if A´FIRST /= B´FIRST or A´LAST /= B´LAST then raise CONSTRAIN_ERROR;

                end if;

                Hemos visto que un parámetro formal puede ser un arreglo no restringido, pero el valor de retorno de una función también puede ser un arreglo cuyos límites se definen de acuerdo a los del arreglo entregado por

                la  instrucción  return.  Por  ejemplo,  la  siguiente  función  retorna  un  arreglo  con  los  mismos  límites  del parámetro, pero con los elementos en orden inverso.

                function REV(X:VECTOR) return VECTOR is

                R:VECTOR(X´RANGE);

                begin

                for I in X´RANGE loop

                R(I):= X(X´FIRST + X´LAST -I);

                end loop;

                return R;

                end REV; Ejercicio:

                1.   Escriba una función de nombre PAR que indique  si un entero es par (TRUE) o impar (FALSE).

                2.   Reescriba la función FACTORIAL de forma tal que el parámetro pueda ser positivo o cero (use el subtipo

                NATURAL). Recuerde que FACTORIAL(0) = 1.

                3.   Escriba la función OUTER que entrega el producto externo de dos vectores (posiblemente con distintos rangos). El producto externo de los vectores A y B se define como la matriz Cij  = Ai *Bj.

                4.   Escriba la función MAKE_UNIT que toma un valor positivo N y entrega una matriz unitaria real de NxN.

                Recuerde que una matriz unitaria es aquella que contiene unos un su diagonal principal y ceros en todos los demás elementos.

                5.   Escriba  la  función  MCD  (usando  recursión)  que  entrega  el  máximo  común  divisor  de  dos  enteros  no negativos. Use el algoritmo de Euclides.

                mcd(x,y) = mcd(y, x mod y)    si y ¹ 0

                mcd (x,0) = x

                7.2. Operadores

                Anteriormente dijimos que toda función comienza con la palabra reservada function seguida de un identificador, sin embargo también es posible usar como nombre de función un string de caracteres, siempre y cuando sea alguno de los siguientes operandos (entre comillas).

                and or xor
                = < <= > >=
                + - & abs not
                / * mod rem **

                En  estos  casos  la  función  definirá  un  nuevo  significado  a  los  respectivos  operadores.  Para

                ejemplificar esto podemos reescribir la función INNER de la siguiente manera

                function “*” (A,B:VECTOR) return REAL is

                RESULT: REAL := 0.0;

                begin

                end “*”;

                for I in A´RANGE loop

                RESULT:= RESULT + A(I) * B(I);

                end loop;

                return RESULT;

                Ahora podemos usar la nueva función con la sintaxis propia del operador *. Entonces, en lugar de

                R:=INNER(V,W);

                podemos escribir

                R:= V * W;

                (Nótese  que  de  todas  maneras  se  puede  usar  la  notación  infijada  R:=  ”*”(V,W),  pero  no  es recomendable).

                Este nuevo significado del operador * se diferencia de los ya existentes (multiplicación de reales y enteros) por el contexto dado por los tipos de los parámetros V y W, y por el tipo requerido por R.

                Al   hecho   que   un   cierto   operador   tenga   varios   significados   se   le   conoce   como   sobrecarga

                (overloading).  La  sobrecarga  de  operadores  predefinidos  no  es  nuevo,  ha  existido  en  los  lenguajes de alto

                nivel desde hace mucho tiempo, lo nuevo de Ada es que permite que el programador también pueda realizar sobrecarga de operadores, funciones y procedimientos de acuerdo a las necesidades del problema a resolver.

                A pesar que los operadores pueden ser sobrecargados, no está permitido el cambiar la sintaxis de la llamada, ni cambiar su nivel de precedencia (jerarquía) con respecto a los demás operadores. Por ejemplo, el operador * siempre debe tener dos parámetros, los cuales pueden ser de cualquier tipo y el resultado puede

                ser  de  cualquier  tipo.  Es  decir,  la  sintaxis  (forma  de  escribir)  se  mantiene,  pero  la  semántica  (acción  del operador) puede cambiar.

                Dos  operadores  (funciones,  procedimientos)  sobrecargados  son  DIFERENTES.  Si  no  entendemos esto  claramente  podríamos  considerar  que  la  función  ”*”  recién  definida  es  recursiva.  Pero  puesto  que  el producto de vectores es diferente (es otro operador con el mismo nombre) al producto de reales, tenemos que A(I)*B(I)  no  es  una  llamada  recursiva,  sino  una  llamada  al  producto  de  reales  dentro  del  producto  de vectores.  Existe un riesgo de escribir accidentalmente operadores recursivos cuando se está reemplazando un operador preexistente en lugar de crear uno nuevo.

                Notemos que dos operadores sobrecargados existen simultáneamente, ninguno de ellos oculta al otro como ocurre cuando, por ejemplo, una variable es declarada con un mismo nombre dentro de un bloque (o loop  )  interno.  En  este  último  caso  la  variable  más  interna  ”cubre”  a  la  interna.  Dos  (o  más)  operadores sobrecargados coexisten y se sabe cuando se está usando uno u otro de acuerdo al contexto. Por ejemplo:

                declare

                begin

                end;

                U,V,W:VECTOR; A,B,C,E:REAL; I:INTEGER;

                — se asignan valores adecuados a V, W, B y C. A:=V*W;      — correcto

                A:=B*C;                               — correcto B:=B*REAL(I);              — correcto U:=V*W;                                               — incorrecto I:=B*C;         — incorrecto E:= 1.0 + A*C;                                               — correcto A:= C*W;         — incorrecto

                Por  último  notemos  que  para  efecto  de  nombrar  operadores  las  mayúsculas  y  minúsculas  no  se

                diferencian, por ejemplo, al sobrecargar el operador or se puede usar “or” o “OR” o incluso “Or”. Ejercicio:

                1.   Escriba las funciones “+” y “*” para sumar y multiplicar dos valores de tipo complejo.

                2.   Escriba las funciones “+” y “*” pasa sumar y multiplicar un complejo con un real. Suponga que el real siempre es el segundo operando.

                7.3. Procedimientos

                Los procedimientos son subprogramas que no retornan un valor y son llamados como instrucciones.

                La sintaxis es similar a la de las funciones. Se comienza con una palabra reservada (procedure en este caso), luego viene un identificador (que no puede ser un operador) , le sigue la lista de parámetros, no existe un tipo

                de retorno y el resto es equivalente a lo dicho para las funciones.

                Los parámetros pueden ser de tres tipos: in, out o in out. Si no se indica el modo se asume que el parámetro es de entrada (in).

                1)  in: El parámetro formal es una constante y se permite sólo la lectura del valor asociado al parámetro real.

                2)  in out: El parámetro formal es una variable y se permite tanto la lectura como la actualización del valor asociado al parámetro real.

                3)  out:  El  parámetro  formal  es  como  una  variable,  se  permite  sólo  la  actualización  del  valor  asociado  al parámetro real. No se puede leer su valor.

                En  el  caso  de  las  funciones  sólo  puede  haber  parámetros  de  entrada,  por  lo  que  los  ejemplos anteriores podrían escribirse

                function SQRT(X: in REAL) return REAL;

                function “*” (A,B: in VECTOR) return REAL; Veamos el funcionamiento de los modos in y out.

                procedure ADD(A,B: in INTEGER; C: out INTEGER) is

                begin

                end;

                C:=A+B;

                con

                P,Q:INTEGER;

                . . . ADD(2+P,37,Q);

                Al  ser  llamado  el  procedimiento  ADD,  en  primer  lugar,  se  evalúan  las  expresiones  2+P  y  37  (en cualquier  orden)  y  los  respectivos  resultados  son  pasados  a  A  y  B  que  en  adelante  se  comportan  como constantes. Luego se evalúa A+B y el valor se asigna al parámetro formal C. Al terminar el valor de C se asigna a la variable Q. Esto equivale, más o menos, a haber escrito.

                declare

                begin

                end;

                A: constant INTEGER:= 2+P;           — in B: constant  INTEGER := 37; — in C:INTEGER;                                        — out

                C:=A+B;                                              — cuerpo

                Q:=C;                                                    — out

                Veamos un ejemplo con modo in out.

                procedure INCREMENT(X: in out INTEGER) is begin

                end;

                X:=X+1;

                con

                I:INTEGER;

                . . . INCREMENT(I);

                Al llamar a ADD el valor de I es asignado a la variable X, luego el valor de X se incrementa en 1. Al terminar, el nuevo valor de X se asigna al parámetro real I. Esto equivale a haber escrito

                declare

                begin

                end;

                X:INTEGER.=I;

                X:=X+1; I:=X;

                Para cualquier tipo escalar (como es el caso de los INTEGER) el modo in equivale a copiar un valor

                en la llamada, el modo out equivale a copiar un valor al terminar de ejecutar el procedimiento y el modo in out equivale a la composición de los anteriores.

                Si  el  modo  es  in,  entonces  el  parámetro  real  puede  ser  cualquier  cualquiera  expresión  del  tipo indicado en el parámetro formal.   En los otros casos el parámetro real debe ser necesariamente una variable

                del  tipo  adecuado.  La  identidad  de  dicha  variable  queda  determinada  cuando  se  produce  la  llamada  y  no puede ser cambiada dentro del procedimiento.  Por ejemplo:

                I:INTEGER;

                A:array (1 .. 10) of INTEGER;

                procedure SILLY (X: in out INTEGER) is begin

                end;

                I:= I+1;

                X:=X+1;

                entonces al ejecutar

                A(5):=1; I:=5;

                SILLY(A(I));

                el valor final en A(5) será 2, I tomará el valor 6, pero A(6) no será afectado. En otras palabras, al momento de

                la llamada la variable X se asocia a A(5) no a A(I), por lo que cualquier cambio en I no influye en X.

                Todos los parámetros (in, out o in out) y los valores de retorno de las funciones deben pertenecer a

                un  cierto  tipo  (o  subtipo)  y  cumplir  todas  las  restricciones  que  en  la  definición  del  tipo  (o  subtipo)

                correspondiente se hayan especificado.

                Si un parámetro formal es de tipo array restringido (es decir, sus límites están predeterminados), los límites del parámetro formal deben coincidir, no basta con que el número de componentes sea idéntico en cada una de las dimensiones. Como se puede ver, el mecanismo de paso de parámetros es más riguroso que la instrucción  de  asignación.  Para  los  arreglos  no  restringidos  los  límites  se  toman  de  los  parámetros  reales, incluso para el modo out.

                Consideremos otro ejemplo, un procedimiento que resuelve la ecuación ax2 + bx +c = 0

                procedure QUADRATIC(A,B,C:in REAL; ROOT_1,ROOT_2: out REAL; OK:out BOOLEAN) is

                D:constant REAL:=B**2 – 4*A*C;

                begin

                if  D < 0.0 or  A = 0.0 then OK:=FALSE; return;

                end if;

                ROOT_1:=(-B+SQRT(D))/(2.0*A); ROOT_2:=(-B-SQRT(D))/(2.0*A); OK:=TRUE;

                end QUADRATIC;

                Si las raíces son reales se entregan en ROOT_1 y ROOT_2 y la variable OK queda con valor TRUE. En cualquier otro caso OK queda en FALSE y ROOT_1 y ROOT_2 quedan indefinidas.

                Nótese el uso de la instrucción return. Puesto que un procedimiento no retorna un valor, si se usa la instrucción return (una o más veces) esta no puede ir seguida de una expresión. Cuando se llega a un return,

                el  procedimiento  termina  y  el  control  pasa  a  la  unidad  de  donde  se  hizo  la  llamada.   A  diferencia  de  las

                funciones, en un procedimiento puede no haber instrucciones return, en este caso el procedimiento se ejecuta hasta su última instrucción y luego el control retorna al punto de llamada.

                Recordemos que un parámetro out no es una variable propiamente tal puesto que no es posible leer (utilizar)  su  valor  asociado.  Por  ejemplo,  no   podríamos  ”mejorar”  el  procedimiento  QUADRATIC  de  la siguiente manera

                procedure QUADRATIC(A,B,C:in REAL; ROOT_1,ROOT_2: out REAL; OK:out BOOLEAN) is

                D:constant REAL:=B**2 – 4*A*C;

                begin

                OK:= (D >= 0.0 and  A /= 0.0);

                if  OK then                 — error: NO SE PUEDE LEER UN PARAMETRO OUT!!!

                ROOT_1:=(-B+SQRT(D))/(2.0*A); ROOT_2:=(-B-SQRT(D))/(2.0*A);

                else

                end if;

                return;

                end QUADRATIC;

                Los  parámetros  formales  de  un  subprograma   pueden  ser  utilizados  en  la  parte  procedural  como parámetros reales en llamadas a subprogramas. Por ejemplo:

                procedure P(I:in INTEGER; J:out INTEGER; K:in out INTEGER) is

                procedure Q(L: in INTEGER; M: out INTEGER; N: in out  INTEGER) is begin

                begin

                end;

                N:=N+L;

                M:=N+L;

                Q(I,I,I);                 — error: I es constante. No puede ser un parámetro real para

                – parámetros formales out o in out.

                Q(I,J,J);                 — error: J es variable especial, no puede ser leída. No puede ser

                – un parámetro real para parámetros formales in out. Q(I,J,K);         — correcto

                Q(K,J,K);              — correcto: K no es pasada como variable, sólo se pasa su valor

                – al parámetro formal L.

                end;

                Entonces

                declare

                begin

                end;

                A,B,C:INTEGER:=0;

                P(A,B,C);              — correcto

                P(A+B,5,C);         — incorrecto: el segundo parámetro debe ser una variable

                P(3, A,B);             — correcto.

                El mecanismo utilizado por Ada para los parámetros de modo out e in out difiere sustancialmente de

                pasaje de parámetros por referencia de Pascal, o el uso de punteros en C. En estos últimos casos todos los cambios que ocurren en un parámetro se van reflejando paralelamente en la variable referenciada, esto puede ocasionar  problemas  cuando  la  variable  utilizada  como  parámetro  real  por  referencia  es,  además,  utilizada como una variable global dentro del subprograma. Se tendría en este caso dos (o más) identificadores para un mismo  objeto  (lugar  físico  en  la  memoria).  Esto  es  parecido  a  tener  dos  o  más  punteros  apuntando  a  un mismo nodo, lo que no es erróneo en sí, pero si no es bien administrado puede conducir a errores. En Ada por otro  lado,  también  es  posible  usar  una  variable  global  dentro  de  un  subprograma,  la  que  a  su  vez  ha  sido usada como parámetro real in out. Pero en este caso se tiene dos objetos diferentes (dos lugares físicos de memoria). Si bien en Ada esta situación pareciera ser menos “riesgosa” es preferible evitarla.

                Ejercicio:

                1.   Escriba un procedimiento de nombre SWAP para intercambiar los valores de dos variables reales.

                2.   Escriba un procedimiento SORT que ordene un arreglo de enteros.

                7.4 Parámetros con nombre y por omisión.

                Hasta ahora las llamadas a los subprogramas se han hecho entregando todos los parámetros en su respectivo orden. También es posible entregar los parámetros indicando su correspondiente nombre (formal).

                En  este  caso  no  es  necesario  seguir  el  orden  en  que  aparecen  en  la  especificación  del  subprograma.  Por ejemplo, en lugar de

                QUADRATIC(L,M,N,P,Q,STATUS); INCREMENT(1);

                podemos escribir

                QUADRATIC(B => M,A => L,C => N, ROOT_1 => P, ROOT_2 => Q, OK => STATUS);

                INCREMENT(X =>1);

                o incluso podríamos escribir

                INCREMENT(X => X);

                También es posible mezclar la forma posicional de entrega de parámetros con el uso de nombres. En este  caso  los  parámetros  sin  nombre  deben  ir  al  comienzo,  y  cuando  se  comienza  a  utilizar  nombres  debe continuarse así hasta el final. Por ejemplo, podemos escribir

                QUADRATIC(L,M,N, ROOT_2 => Q, ROOT_1 => P, OK => STATUS);

                El dar nombre a los parámetros al momento de la llamada tiene dos usos principales. El primero es para hacer más legible el código, por ejemplo, al escribir

                QUADRATIC(L,M,N, ROOT_1 => P, ROOT_2 => Q, OK => STATUS);

                podemos inferir por el nombre del procedimiento y los nombres de los parámetros que en P y Q están las raíces de la ecuación cuadrática.

                El segundo uso dice relación con los valores por omisión de los parámetros. A veces ocurre que un parámetro in toma generalmente un mismo valor en cada llamada. En este caso es posible dar un valor por omisión,  el  que  será  pasado  al  parámetro  formal  en  el  momento  de  la  llamada,  a  menos  que  se  de

                explícitamente un valor.  Por ejemplo, al chequear a los pasajeros que llegan por vuelo internacional a Chile,

                es de esperar que la mayoría de ellos sean chilenos y que,  además, la mayoría viaja con fines turísticos.

                type MOTIVO is (NEGOCIOS, TURISMO, OTROS);

                type PAIS is (CHILE, CHINA, INGLATERRA, etc);

                procedure CHEQUEO_DESEMBARCO (NOMBRE: in NAME; ORIGEN: in PAIS; DESTINO:in PAIS:= CHILE; MOTIVO_DE_VIAJE:in MOTIVO:=TURISMO);

                Podríamos hacer las siguientes tipos de llamadas: CHEQUEO_DESEMBARCO(“J.MARTINEZ”, CHINA);

                CHEQUEO_DESEMBARCO(“J.ESCOBAR”, INGLATERRA,CHINA);

                CHEQUEO_DESEMBARCO(“J.MORA”, CHINA, MOTIVO_DE_VIAJE => NEGOCIOS);

                Para  un  buen  uso  de  los  valores  por  omisión,  todos  aquellos  parámetros  candidatos  con  esta características deben colocarse al final de la lista de parámetros en la especificación del subprograma. De esta forma es posible colocar todos aquellos obligatorios en su respectivo orden al comienzo de la llamada. Luego vienen los parámetros con valores por omisión, los cuales pueden no estar presentes en su totalidad. Si uno de ellos no está presente, y no es el último, los demás parámetros deben ser dados con su respectivo nombre.

                El     valor  por  omisión  puede  no  ser  constante,      puede  ser  en  general  una  expresión  del  tipo correspondiente,    la  cual  es  evaluada  cada  vez  que  se  realiza  una  llamada  al  subprograma.  En  nuestro ejemplo, el parámetro ORIGEN podría tener como valor por omisión  ORIGEN_VUELO(Nro_VUELO), en este   caso,   si   no   se   da   un   valor   explícito   el   parámetro   tomará   el   valor   entregado   por   la   función ORIGEN_VUELO, la que debería entregar un valor de tipo PAIS.

                Ejercicio:

                1. Escriba una función de nombre ADD que suma dos valores enteros y que toma el entero 1 como valor por omisión para el segundo parámetro. ¿De cuántas formas diferentes es posible llamar a la función ADD para que entregue N+1, donde N es el primer parámetro?

                2. ¿Qué ocurrirá si todos los parámetros tienen valores por omisión y se hace  uso de todos ellos?

                7.5 Sobrecarga

                Ya  hemos  visto  que  es  posible  definir  nuevos  significados  a  los  operadores  predefinidos  del lenguaje.  De hecho esta “sobrecarga” semántica se extiende a todos los subprogramas en general.

                Un programa sobrecargará a uno definido con anterioridad en la medida que sea lo suficientemente diferente.  Por  otro  lado,  si  el  orden  y  los  tipos  de  los  parámetros  y  el  resultado  (para  las  funciones)  es  el mismo,  entonces  en  lugar  de  sobrecarga  (overloading)  habrá  ocultamiento  (hiding).  Obviamente,  un procedimiento no puede ocultar una función  ni una función a un procedimiento. Obsérvese que los nombres

                y  modos de los parámetros, y la presencia o ausencia de valores por omisión no son relevantes al momento

                de   determinar   si   existe   sobrecarga   u   ocultamiento.      Es   posible   declarar   dos   o   más   subprogramas sobrecargados en un  misma parte declarativa.

                Los subprogramas y los literales de enumeración pueden sobrecargarse unos a otros. De hecho un literal  de  enumeración  es  formalmente  una  función  sin  parámetros  con  resultado  del  tipo  de  enumeración. (Por  ejemplo,   el  literal  de  enumeración  SUN  formalmente  es  una  función  de  nombre  SUN  que  no  tiene parámetros y  que entrega un valor constante del tipo DAY).

                Existen dos tipos de identificadores: los sobrecargables y los no-sobrecargables.  En cualquier punto

                de un programa Ada un identificador hace referencia a un (y sólo un) objeto no-sobrecargable o a uno o más objetos sobrecargables.  La declaración de un  identificador de un tipo oculta las posibles definiciones previas

                del otro tipo y no pueden  aparecer en una misma parte declarativa.

                7.6 Declaraciones, ámbito (scope) y visibilidad

                Ya  hemos   dicho  que  a  veces  es  necesario  entregar  la  especificación  de  un  subprograma  sin  su cuerpo. (Recuérdese que el cuerpo incluye la especificación.) Un ejemplo concreto en que esto es necesario

                se  presenta  cuando  hay  recursividad  mutua  entre  subprogramas.  Supongamos  que  queremos  declarar  dos procedimientos  F  y  G  donde  cada  uno  llama  al  otro.  Debido  a  la  regla  de  la  ”elaboración  lineal  de declaraciones” no podemos escribir la llamada a F en el cuerpo de G sin antes haber declarado F y viceversa. Claramente esto es imposible de realizar si escribimos los cuerpos, porque necesariamente uno de ellos  habrá

                de ir segundo. Sin embrago, podemos escribir.

                procedure F(…);                — declaración de F

                procedure G(…) is             – cuerpo de G

                begin

                end G;

                F(…);

                procedure F(…) is             – cuerpo de F repite

                begin                                     – su especificación

                G(…);

                end F;

                y todo funcionará correctamente.

                Si la especificación se repite debe haber una total correspondencia. Técnicamente, diremos que las dos  especificaciones  se  corresponden.  Pueden  haber  pequeñas  variaciones,  por  ejemplo  los  valores  por omisión  (que  deben  ser  escritos  dos  veces)  no  afectan  la  correspondencia  puesto  que  son  evaluadas  sólo cuando se llama al subprograma.

                A veces, para lograr mayor claridad en el código, es conveniente escribir todas las declaraciones de

                los subprogramas juntas para que actúen como un sumario; y a continuación se escriben todos los cuerpos.

                Los  cuerpos  de  los  subprogramas  y  las  demás  declaraciones  no  pueden  mezclarse  en  forma arbitraria. Los cuerpos deben estar a continuación de cualquier otra declaración. De esta forma se evita que

                las declaraciones “pequeñas” se pierdan entre los cuerpos de los subprogramas.

                Puesto  que  los  subprogramas  se  escriben  en  las  partes  declarativas  y    a  su  vez  poseen  partes declarativas, es posible anidar subprogramas. Las normas de ocultamiento explicadas anteriormente para los bloques también rigen para los subprogramas. Consideremos.

                procedure P is

                I:INTEGER:=0;

                procedure Q is K:INTEGER:=I; I: INTEGER;

                J: INTEGER;

                end P;

                begin

                end Q;

                . . .

                . . .

                Como  ya  hemos  visto  la  variable  I  interna  ”oculta”  a  la  externa,  sin  embargo,  la  primera  sigue

                existiendo y es posible utilizarla mediante la notación punto, usando como prefijo el nombre del subprograma donde está declarada la variable. En este caso el nombre completo de la variable I externa es P.I, y podríamos, por ejemplo, inicializar J  escribiendo

                J:INTEGER:=P.I;

                El nombre completo de la variable I interna es P.Q.I, y podría ser referenciada  de esa manera para, por ejemplo, explicitar su naturaleza.

                (Recuérdese que los bloques también pueden tener nombres y utilizando la notación punto es posible referenciar cualquier variable que haya sido ocultada por una redefinición. Lo mismo puede hacerse con las iteraciones.)

                Como  hemos  visto  los  subprogramas  pueden  alterar  variables  globales  y  de  este  modo  generar efectos colaterales. (Un efecto colateral es una consecuencia de la llamada a un subprograma en su entorno y que no está relacionada con el mecanismo de parámetros.) En general se considera que los efectos colaterales son una  mala práctica de programación, especialmente en el caso de las funciones.

                8. Estructura general

                En las secciones anteriores han sido descritas las características de Ada a pequeña escala. Lo visto

                del lenguaje hasta el momento difiere poco de otros lenguajes tradicionales,   a pesar que Ada ofrece mayor funcionalidad  en  muchas  áreas.  Ahora  comenzaremos  a  estudiar  aspectos  del  lenguaje  relacionados  con  la abstracción  de  datos  y  la  programación  en  gran  escala.  En  concreto  hablaremos  de  paquetes  (que  es  la principal característica de Ada) y de compilación separada.

                8.1. Paquetes

                Uno de los mayores problemas con los lenguajes estructurados como Algol, Pascal o C, es que no tienen un control adecuado para el ocultamiento de la información. Por ejemplo, supongamos que tenemos una  pila  representada  por  un  arreglo  y  una  variable  que  sirve  de  índice  para  el  elemento  en  el  tope,  un procedimiento PUSH para agregar un elemento y una función POP para removerlo. Podríamos escribir

                MAX: constant INTEGER:= 100 ; S: array (1 .. MAX) of INTEGER; TOP: INTEGER range 0 .. MAX;

                para representar la pila y luego declarar

                procedure PUSH(X: INTEGER) is begin

                TOP:=TOP + 1;

                S(TOP):= X;

                end PUSH;

                function POP return INTEGER is begin

                TOP:=TOP – 1;

                return S(TOP+1);

                end POP;

                En  un  lenguaje  estructurado  normal  no  hay  forma  de  accesar  los  subprogramas  PUSH  y  POP  sin tener a la vez acceso directo a las variables S y TOP. En consecuencia   no es posible obligar al uso de un protocolo correcto y así evitar el tener que conocer la forma como está implementada la pila.

                Ada  supera  este  problema  mediante  el  uso  de  paquetes,  los  que  permiten  colocar  ”una  pared”

                alrededor de un grupo de declaraciones y permiten el acceso sólo a aquellas que por definición son visibles.

                De hecho un paquete se divide en dos partes: la especificación que entrega la interfaz con el mundo externo, y

                el cuerpo en que se detallan los detalles ocultos (privados).

                Usando paquetes, el ejemplo anterior podría escribirse como sigue

                package STACK is                                                           – especificación

                procedure PUSH(X:INTEGER);

                function POP return INTEGER;

                end STACK;

                package body STACK is                                                 – cuerpo

                MAX: constant INTEGER:= 100; S: array (1 .. MAX) of INTEGER; TOP:INTEGER range 0 .. MAX;

                procedure PUSH(X:INTEGER) is begin

                TOP := TOP + 1;

                S(TOP):=X;

                end PUSH;

                function POP return INTEGER is begin

                TOP := TOP – 1;

                return S(TOP +1);

                end POP;

                begin                                                                                    – inicialización

                TOP:=0;

                end STACK;

                La  especificación  de  un  paquete  comienza  con  la  palabra  reservada  package,  el  identificador  del paquete y la palabra is. Luego vienen las declaraciones de las entidades que son visibles. Se finaliza con la palabra end, el identificador (opcional) y el punto y coma final. En el ejemplo sólo se han declarado los dos subprogramas PUSH y POP.

                El  cuerpo  también  comienza  con  la  palabra  package,  pero  seguida  de  body,  el  identificador  y  la palabra is. Luego viene una parte declarativa normal, begin , secuencia de instrucciones, end, el identificador (opcional) y un punto y coma.

                En el ejemplo la parte declarativa del cuerpo contiene las variables usadas para representar la pila y

                los cuerpos de PUSH y POP. La secuencia de comandos entre begin y end es ejecutada cuando se declara el paquete y puede ser usada para inicializaciones, si   estas no son requeridas el begin puede ser omitido. De hecho  en  el  ejemplo  hubiese  sido  más  adecuado  inicializar  la  variable  TOP  en  la  forma  por  nosotros  ya conocida:            TOP: INTEGER RANGE 0 ..MAX:= 0;

                Nótese que un paquete es declarado y por lo tanto es sólo un ítem más en una parte declarativa de un

                subprograma,  bloque  u  otro  paquete,  a  menos  que  sea  una  unidad  de  biblioteca  en  cuyo  caso  no  estará anidado.

                No se puede  colocar un cuerpo en la especificación de un paquete.  Además, si la especificación de

                un   paquete   contiene   la     especificación   de   un   subprograma,   entonces   el   cuerpo   del   paquete   debe necesariamente contener el cuerpo del subprograma. Podríamos conceptualizar a la declaración y al cuerpo de

                un  paquete  como  una  gran  parte  declarativa  con  algunos  elementos  visibles.  Pero  sin  embargo,  es  posible declarar  el  cuerpo  de  un  subprograma  en  el  cuerpo  del  paquete,  sin  hacerlo  en  su  declaración.  Dicho subprograma será local y sólo podrá ser usado por otros subprogramas del paquete (subprogramas visibles o locales) o en la sección de inicialización.

                La elaboración de un paquete consiste simplemente en la elaboración de sus declaraciones internas y

                la ejecución de la secuencia de inicialización (si existe). El paquete existe hasta el final del   ámbito (scope)

                en el que fue declarado.

                Un paquete puede ser declarado en cualquier parte declarativa (en un bloque, un subprograma o en otro bloque). Si la especificación de un paquete se realiza dentro de la especificación de otro paquete (al igual que con los subprogramas), el cuerpo del primero debe ser declarado en el cuerpo del segundo. Aparte de que

                en  la  especificación  de  un  bloque  no  puede  haber  cuerpos,  no  existe  ninguna otra  restricción  y  es  posible hacer cualquiera de las declaraciones vistas hasta ahora.

                El  paquete  en  sí  tiene  un  nombre  y  las  entidades  ubicadas  en  su  parte  visible  pueden  ser conceptualizadas como sus componentes. Por lo tanto la manera más obvia de acceder a estos componentes es utilizando la notación punto. Por ejemplo:

                declare

                begin

                end;

                package STACK is                                                           . . .

                . . .

                end STACK;

                package body STACK is

                . . .

                end STACK;

                . . . STACK.PUSH(M);

                . . . N:=STACK.POP;

                Dentro  del  paquete  es  posible  accesar  PUSH  y  POP  directamente  (pues  son  ”objetos  locales”  al

                paquete), al igual que MAX, S y TOP. Pero esta últimas de NINGUNA FORMA son accesibles desde afuera

                del  paquete.  Son  datos  ”protegidos”,  están  ”encapsulados”  y  en  un  cierto  sentido  hemos  creado  un  tipo abstracto de datos para representar una pila.

                Lógicamente, podemos evitar escribir reiteradamente el nombre del paquete con al notación punto si hacemos  uso  del  la  cláusula  use,  la  cual  podría  ir  inmediatamente  a  continuación  de  la  declaración  del paquete o en cualquier otra parte declarativa donde este sea visible.  Por ejemplo:

                declare

                begin

                end;

                use STACK;

                . . . PUSH(M)

                . . . N:=POP;

                . . .

                Es  posible  declarar  más  de  un  paquete  en  una  misma  parte  declarativa.  En  general,  deberíamos

                escribir primero todas las especificaciones y luego todos los cuerpos o alternadamente las especificaciones y

                luego los cuerpos. Es decir, spec A, spec B, body A, body B, o spec A, body A, spec B, body B. Las reglas que se deben cumplir son bastante simples:

                1.   elaboración lineal de declaraciones.

                2.   la especificación debe preceder al cuerpo para cada paquete (o subprograma).

                3.   los itemes pequeños deberían generalmente preceder a los mayores.

                Por  supuesto,  la  parte  visible  de  un  paquete  no  sólo  puede  contener  subprogramas.  De  hecho,  un caso   importante   es   cuando   no   contiene   subprogramas,   sino   grupos   de   variables,   constantes   y   tipos relacionados.  Este  tipo  de  paquetes  no  necesita  un  cuerpo,  no  provee  ningún  tipo  de  ocultamiento  de información, sólo sirve para agrupar los objetos relacionados.

                Como  ejemplo  podemos  escribir  un  paquete  que  contiene  el  tipo  DAY  y  algunas  constantes relacionadas.

                package DIURNAL is

                type DAY is (MON,TUE,WED,THU,FRI,SAT,SUN); subtype WEEKDAY is DAY range MON .. FRI; TOMORROW: constant array (DAY) of DAY:=

                (TUE,WED,THU,FRI,SAT,SUN,MON);

                NEXT_WORK_DAY: constant array(WEEKDAY) of WEEKDAY:= (TUE,WED,THU,FRI,MON);

                end DIURNAL;

                Un subprograma no puede ser llamado durante la elaboración de una parte declarativa si su cuerpo aún no a aparecido. (Nótese que esto no dificulta la recursión mutua, puesto que en este caso la llamada sólo ocurre  cuando  se  ejecuta  la  secuencia  de  instrucciones.)  Esto  impide  que  sean  usados  para  dar  valores iniciales. Por ejemplo

                function A return INTEGER; I: INTEGER:= A;

                es ilegal, y debería provocar una excepción PROGRAM_ERROR.

                Esta  regla  también se aplica a los subprogramas dentro de los paquetes. No podemos llamar a un subprograma desde fuera de un paquete hasta que el cuerpo del paquete ha sido elaborado.

                Ejercicio: Escriba un paquete COMPLEX_NUMBERS que haga visible

                1.   el tipo COMPLEX

                2.   la contante imaginaria  I =     – 1

                3.   las funciones +, – , *  y / que actúan sobre los complejos.

                8.2. Unidades de biblioteca

                En el pasado muchos lenguajes han ignorado el hecho que los programas son divididos en partes que

                se  compilan  separadamente  y  que  luego  se  unen  en  un  todo.  Ada  reconoce  este  hecho  y  provee  dos mecanismos: uno top-down y otro bottom-up.

                El primero es apropiado para el desarrollo de un programa grande fuertemente cohesionado, el que,

                sin   embargo,   por   alguna  razón  es  dividido  en  módulos  que  pueden  compilarse  separadamente,  con posterioridad al programa general. El segundo mecanismo está orientado a escribir bibliotecas con módulos

                de propósito general, los que son compilados antes de los programas que se espera harán uso de ellos.

                Una   unidad   de   biblioteca  es   la   especificación   de   un   subprograma   o   un   paquete.   A   los correspondientes cuerpos se les denomina unidades  secundarias.   Estas unidades pueden ser compiladas en forma separada, o por conveniencia es posible compilar varias de ellas juntas. Es decir, podemos compilar las especificación y el cuerpo de un paquete juntos, pero como veremos más adelante también es posible realizar

                la compilación separadamente.   Como hemos visto el cuerpo de un subprograma es suficiente para definirlo

                en su totalidad, por lo que es clasificado como una unidad de biblioteca y no una unidad secundaria.

                Cuando se compila una unidad va a parar a una biblioteca. Lógicamente, habrá varias bibliotecas de acuerdo  al  usuario,  proyecto,  área  de  aplicación,  etc.  (Aquí  no  nos  preocuparemos  de  la  creación  y manipulación de bibliotecas, sino de su aplicación y uso desde la perspectiva del “programador usuario” de ellas.)   Una vez que está en una biblioteca, la unidad puede ser usada por cualquier otra unidad compilada, pero la unidad que llama (que usa) debe indicar explícitamente la dependencia con una cláusula with.

                Veamos un ejemplo sencillo. Supongamos que compilamos el paquete STACK, el que no depende

                de ninguna otra unidad y por lo tanto no requiere de cláusulas with.   Compilaremos la especificación y el cuerpo juntos. Luego el texto a compilar será

                package STACK is

                . . .

                end STACK;

                package body STACK is

                . . .

                end STACK;

                Supongamos  que  escribimos  el  procedimiento  MAIN  que  hace  uso  del  paquete  STACK.  (El procedimiento MAIN es el programa principal en el sentido común del término.) El texto a compilar sería

                with STACK;

                procedure MAIN is

                use STACK M,N:INTEGER;

                begin

                . . . PUSH(M);

                . . . N:=POP;

                . . .

                end MAIN;

                La cláusula with va antes de la unidad involucrada, de esta manera la dependencia entre unidades queda clara. Un cláusula with no puede estar anidada en ámbitos (scope) internos.

                Si una unidad depende de varias otras unidades, éstas pueden ir todas juntas en un sola cláusula with

                o separadas. Por ejemplo, podemos escribir

                with STACK, DIURNAL;

                procedure MAIN is

                . . .

                o, lo que es equivalente

                with STACK;

                with DIURNAL;

                procedure MAIN is

                . . .

                Por conveniencia colocaremos la cláusula use inmediatamente después de cada with. Entonces, si escribimos

                with STACK; use STACK;

                procedure  MAIN is

                . . .

                los procedimientos PUSH y POP estarán directamente accesibles dentro del procedimiento, sin necesidad de usar la notación punto.

                Sólo las dependencias directas se deben indicar con la cláusula with. Por ejemplo, si el paquete P usa

                las facilidades del paquete Q, y este a su  vez usa un  paquete R, entonces la cláusula with de P sólo debe mencionar a Q. El usuario de P no debe preocuparse por R.

                Otro  punto  importante  es  que  una  cláusula  with  aplicada  a  la  declaración   de  un  bloque  o subprograma también es válida para el respectivo cuerpo, por lo que no necesita ser repetida. Por supuesto,

                un cuerpo puede tener dependencias adicionales, las que deberán indicarse con otras cláusulas with válidas sólo  para  el  cuerpo.  Estas  últimas  no  deberían  indicarse  para  las  especificación,  debido  a  que  con  esto  se reduce la independencia de la declaración.

                Si la especificación y cuerpo de un paquete son compilados en forma separada, entonces el cuerpo siempre debe ser compilado después de la declaración. Se dice que “el cuerpo depende de la especificación”. Sin  embargo,  cualquier  otra  unidad  que  hace  uso  del  paquete  sólo  depende  de  la  especificación  y  no  del cuerpo. Si el cuerpo es modificado en cualquier grado que no afecte la especificación respectiva, las unidades que hacer uso del paquete (es decir, que dependen de él) no necesitarán ser recompiladas.   La capacidad de compilación separada de declaraciones y cuerpos debería incidir en la simplificación de la mantención de los programas.

                La regla general de compilación es bastante simple: una unidad debe ser compilada después de todas

                las unidades de las cuales depende. En consecuencia, si una es modificada y recompilada, entonces todas sus unidades  dependientes  deben  recompilarse.  Se  puede  usar  cualquier  orden  en  la  recompilación  que  sea consistente con la regla de dependencia.

                Existe  un  paquete  que  no  necesita  ser  mencionado  en  una  cláusula  with.  Este  es  el  paquete

                STANDARD  que  contiene  las  declaraciones  de  todas  los  tipos  predefinidos  tales  como  INTEGER  y

                BOOLEAN.  En  el está declarado un   paquete de nombre ASCII que contiene constantes los caracteres de control tales como CR y LF.

                Otra regla importante es que las unidades de biblioteca no pueden tener nombres repetidos, es decir,

                no pueden ser sobrecargadas. Además, no pueden ser operadores.

                La especificación y el cuerpo de un paquete forman una sola parte declarativa. Por lo tanto, si se declara  una  variable  X  en  la  especificación,  entonces  no  se  puede  ser  redeclarada  en  el  cuerpo  (excepto, lógicamente, si se declara en una región interna como, por ejemplo, en  un subprograma).

                Ejercicio. El paquete D y los subprogramas P, Q y MAIN tienen las siguientes cláusulas with

                especificación de D                            no tiene cláusulas with

                cuerpo de D                                         with P,Q;

                subprograma P                                    no tiene cláusulas with subprograma Q      no tiene cláusulas with subprograma MAIN   with D;

                Se pide que dibuje un grafo mostrando las dependencias entre las unidades. Indique a lo menos 4 ordenes  posibles  de  compilación.  Indique  qué  unidades  pueden  ser  recompiladas  sin  que  eso  implique  la recompilación de las demás.

                9. Tipos Privados

                De  acuerdo  a  lo  que  hemos  visto  hasta  el  momento  una  forma    adecuada    de  ”encapsular”  la estructura y operaciones sobre números complejos sería mediante el siguiente paquete.

                package COMPLEX_NUMBERS is type COMPLEX is

                record

                RL, IM: REAL;

                end record;

                I : constant COMPLEX := (0.0, 1.0);

                function “+” (X,Y:COMPLEX) return COMPLEX; function “-” (X,Y:COMPLEX) return COMPLEX; function “*” (X,Y:COMPLEX) return COMPLEX; function “/” (X,Y:COMPLEX) return COMPLEX;

                end;

                El  problema  con  esta  formulación  es  que  el  usuario  (el  programador  que  utiliza  de  este  paquete) puede hacer uso del hecho que los números complejos están representados en forma cartesiana. En lugar de usar siempre el operador complejo +, podría escribir cosas como

                C.MI:= C.MI + 1.0;

                en lugar de

                C:= C + I;

                De   hecho,   usando   este   paquete,   el   usuario   debería   hacer   uso   de   la   representación   para   la construcción valores complejos. Por ejemplo

                declare

                begin

                end;

                X:COMPLEX;

                X.RL:= 5.0; X.IM:=  1.5;

                Sería deseable no tener   que depender de la representación ni   conocerla, para de ese modo poder

                cambiar la representación, por ejemplo, a coordenadas polares sin que esto repercuta en los programas que usan estos complejos. Esto puede hacerse con los tipos privados. Por ejemplo

                package COMPLEX_NUMBERS is type COMPLEX is private; I:constant COMPLEX;

                function “+” (X,Y:COMPLEX) return COMPLEX; function “-” (X,Y:COMPLEX) return COMPLEX; function “*” (X,Y:COMPLEX) return COMPLEX; function “/” (X,Y:COMPLEX) return COMPLEX; function CONS (R,I:REAL) return COMPLEX; function RL_PART (X:COMPLEX) return REAL; function IM_PART (X:COMPLEX) return REAL;

                private

                type COMPLEX is record

                end;

                RL, IM: REAL;

                end record;

                I : constant COMPLEX:= (0.0, 1.0);

                La parte de la especificación del  paquete que se encuentra antes de la palabra reservada private es

                la parte visible y da la información disponible para los usuarios del paquete. El tipo COMPLEX es declarado como privado. Esto significa que fuera del paquete nada se sabe respecto a los detalles del tipo. Las únicas operaciones  disponibles  son  la  asignación,  =,  y  /=   más  aquellas  agregadas  por  el  autor  del  paquete  como subprogramas especificados en la parte visible.

                También podemos declarar constantes de un tipo privado en la parte visible, como la constante I de nuestro ejemplo. El valor inicial no puede ser declarado en la parte visible debido a que los detalles del tipo

                aún  no  son  conocidos.  Por  lo  tanto,  sólo  indicamos  que  I  es  una    constante.  A  estas  constantes  de  les denomina constantes diferidas.

                Después de private debemos dar los detalles de los tipos declarados como privados y dar los valores iniciales a las constantes diferidas.

                Un tipo privado puede ser implementado de cualquier forma que sea consistente con las operaciones visibles  al  usuario.  Puede  ser  un  registro,  como  en  nuestro  ejemplo;  o  podría  ser  un  arreglo,  un  tipo  de enumeración u otra forma válida; podría incluso ser declarado en términos de otro tipo privado. En nuestro caso,  es  obvio  que  la  forma  más  natural  de  representar  un  complejo  es  por  medio  de  un  registro;  pero podríamos igualmente haber usado un arreglo de dos elementos, tal como

                type COMPLEX is array (1 .. 2) of REAL;

                Habiendo  declarado  los  detalles  de  un  tipo  privado,  estamos  en  posición  de  usarlo  y  declarar constantes y darles valores iniciales.

                Debe  notarse  que  junto  con  las  funciones  +,  -,  *  y  /  hemos  agregado  CONS  para  crear  números complejos  a  partir  de  sus  componentes  real  e  imaginario;  y  RL_PART  y   IM_PART  para  entregar  estos componentes. Lógicamente, el hecho que CONS, RL_PART y IM_PART correspondan a nuestra visión de

                los números complejos en su notación cartesiana no impide que podamos implementarlos en su forma polar.

                El cuerpo del paquete sería como sigue

                package body COMPLEX_NUMBERS is

                function “+”  (X,Y:COMPLEX) return COMPLEX is begin

                return (X.RL + Y.RL, X.IM + Y.IM);

                end “+”;

                — las demás funciones (-, * y / ) se implementan en forma similar

                function CONS(R,I: REAL) return COMPLEX is begin

                return (R,I);

                end CONS;

                function RL_PART (X:COMPLEX) return REAL is begin

                return X.RL;

                end RL_PART;

                function IM_PART (X:COMPLEX) return REAL is begin

                return X.IM;

                end IM_PART;

                end COMPLEX_NUMBERS;

                El paquete COMPLEX_NUMBERS podría ser usado de la manera siguiente

                declare

                begin

                end;

                use COMPLEX_NUMBERS; C,D: COMPLEX

                R,S: REAL;

                C:= CONS(1.5, -6.0);

                D:= C + I;                              — suma de complejos

                R:= RL_PART(D) + 6.0;   –  suma de reales

                . . .

                Fuera  del  paquete  podemos  declarar  variables  y  constantes  del  tipo  complejo  de  la  manera  usual.

                Nótese el uso de la función CONS para crear complejos.

                No es posible combinar operaciones entre números complejos y reales. Por ejemplo, sería incorrecto

                C:= 2.0 * C;

                sino que deberíamos escribir

                C:= CONS(2.0, 0.0) * C;

                Si esto resultase muy tedioso se podría agregar otros operadores (sobrecargados) para permitir las operaciones mezcladas.

                Supongamos   que   por   alguna   razón   hemos   decidido   representar   los   números   complejos   en coordenadas polares. La parte visible del paquete no sufrirá cambios, pero la parte privada quedaría

                private

                PI: constant:= 3.1415926536;

                type COMPLEX is record

                end;

                R:REAL;

                THETA: REAL range 0.0 .. 2.0 * PI;

                end record;

                I : constant COMPLEX := (1.0, 0.5*PI);

                El cuerpo del paquete COMPLEX_NUMBERS necesita ser totalmente reescrito. Algunas funciones

                serán  más  simples  y  otras  más  complejas.  Sin  embargo,  debido  a  que  la  parte  visible  no  ha  cambiado, quienes  hacen  uso  del  paquete  no  se  ven  afectados;  estamos  seguros  de  eso  puesto  que  no  es  posible escribir  código que dependa de los detalles de implementación de un tipo de dato privado. Sin embargo, las unidades  dependientes  del  paquete  COMPLEX_NUMBERS    deberán  ser  recompiladas  de  acuerdo  a  las reglas generales de dependencia ya estudiadas. Esto puede parecer contradictorio, pero debemos recordar que

                el  compilador  necesita  información  sobre  la  parte  privada  para  asignar  espacio  para  los  objetos  del  tipo privado declarados en las unidades usuarias. Si se cambia la parte privada el tamaño de los objetos podría cambiar.

                Finalmente,  notemos  que  entre  la  declaración  de  un  tipo  privado  y  su  posterior  declaración  en extenso, el tipo está en un curioso estado de definición a medias. Debido a esto su uso está sujeto a   serias restricciones;   sólo   puede   ser   usado   para   declarar   constantes   diferidas,   otros   tipos   y   subtipos   y especificaciones de subprogramas. No puede ser usado para declarar variables. Podríamos escribir

                type COMPLEX_ARRAY is array (INTEGER range <>) of COMPLEX;

                y luego

                C: constant COMPLEX_ARRAY(1 .. 10);

                Pero  sólo  después  de  la  declaración  en  extenso  del  tipo  podemos  declarar  variables  de  los  tipos

                COMPLEX o COMPLEX_ARRAY.

                Sin embargo, podemos declarar especificaciones de subprogramas con parámetros de tipo COMPLEX   o   COMPLEX_ARRAY                                   e   incluso   podemos   asignarles   expresiones   por  omisión.        Estas expresiones pueden hacer uso de constantes y funciones. Esto se permite porque una expresión por omisión sólo  se  evalúa  cuando  efectivamente  se  llama  a  un  subprograma  y  esto  sólo  puede  ocurrir  una  vez  que  el cuerpo ha sido declarado, lo cual ocurre – obligatoriamente – después de haber definido el tipo en extenso.

                Ejercicio:

                1. Escriba funciones “*” adicionales para permitir la multiplicación mixta entre complejos y reales.

                2. Complete el paquete RATIONAL_NUMBERS cuya parte visible es

                package RATIONAL_NUMBERS is type RATIONAL is private;

                function “+” (X:RATIONAL) return RATIONAL; –   + unario function “-” (X:RATIONAL) return RATIONAL;  –   - unario function “+” (X,Y:RATIONAL) return RATIONAL;

                function “-” (X,Y:RATIONAL) return RATIONAL; function “*” (X,Y:RATIONAL) return RATIONAL; function “/” (X,Y:RATIONAL) return RATIONAL;

                private

                end;

                function “/” (X:INTEGER;Y:POSITIVE) return RATIONAL;

                function NUMERATOR (X:RATIONAL) return INTEGER;

                function DENOMINATOR (X:RATIONAL) return POSITIVE;

                . . .

                Un número racional es un número de la forma N/D, donde N es un entero y D es un entero positivo.

                Para que la igualdad predefinida trabaje es esencial que los número racionales sean reducidos a su mínima expresión. Por ejemplo, los racionales 12/2, 24/4, etc deberían expresarse como 6/1.

                3. ¿Por qué

                function “/” (X:INTEGER;Y:POSITIVE) return RATIONAL;

                no se sobrepone a la división entera predefinida?

                10. Excepciones

                Una   excepción   es   una   situación   que   requiere   de   un   tratamiento   especial   que   escapa   al funcionamiento normal de un programa (o parte de él). En los capítulos anteriores varias veces se ha indicado que  si  ocurre  un  error  durante  la  ejecución  de  un  programa  se  origina  una  excepción  (generalmente CONSTRAIN _ERROR). En este capítulo se describirá el mecanismo de manejo de excepciones.

                Algunas de las excepciones predefinidas dentro del lenguaje Ada son:

                CONSTRAIN_ERROR: generalmente indica que algo se ha salido de rango.

                NUMERIC_ERROR:  esto  ocurre  cuando  algo  erróneo  pasó  en  los  cálculos  aritméticos,  por  ejemplo,  el intentar dividir por cero.

                PROGRAM_ERROR: ocurre si se intenta violar de alguna manera el control de ejecución, por ejemplo, al llamar a un subprograma cuyo cuerpo (body) todavía no ha sido construido.

                STORAGE_ERROR:  ocurre  al  salirse  del  espacio  de  memoria,  por  ejemplo,  se  llama  a  una  función recursiva FACTORIAL con un parámetro muy grande.

                10.1. Manejo de excepciones

                Si sabemos que una excepción puede ocurrir en alguna parte de nuestro programa, podemos escribir

                un “manejador de excepciones” para tratar la situación. Por ejemplo, supongamos que escribimos

                begin

                – secuencia de comandos

                exception

                when CONSTRAIN_ERROR => — hacer algo

                . . .

                end;

                Si se origina una excepción CONSTRAIN_ERROR mientras se ejecuta la secuencia de comandos entre  begin  y  excepction,  entonces  el  flujo  de  control  es  interrumpido  e  inmediatamente  transferido   a  la secuencia de comandos que sigue a =>. A la cláusula que comienza con when  se le conoce como “manejador

                de excepción” (exception handler).

                Un ejemplo trivial sería el determinar el valor de una variable TOMORROW a partir de TODAY (ambas de tipo DAY)

                begin

                TOMORROW:= DAY´SUCC(TODAY);

                exception

                when CONSTRAIN_ERROR => TOMORROW:= DAY´FIRST;

                end;

                Si  TODAY  es  DAY´LAST  (es  decir,  SUN)  cuando  se  intente  evaluar  DAY´SUCC(TODAY) surgirá               la                  excepción            CONSTRAIN_ERROR.       El    control    pasará      entonces    al    manejador    de CONSTRAIN_ERROR  y  se  evaluará   la  expresión  DAY´FIRST,  cuyo  valor  será   entregado  como resultado.

                En realidad este no es un buen ejemplo puesto que las excepciones deberían ser usadas en casos con

                baja probabilidad de ocurrencia. Puesto que el 14% de los días son domingo sería más adecuado el siguiente código.

                if TODAY = DAY´LAST then

                TOMORROW:= DAY´FIRST;

                else

                end if;

                TOMORROW:= DAY´SUCC(TODAY);

                sin embargo, es un ejemplo sencillo que ilustra el mecanismo involucrado.

                Es  importante  recalcar  que  el  control  nunca  vuelve  a  la  unidad  donde  apareció  la  excepción.  La secuencia de comandos que sigue a => reemplaza al resto de la unidad y completa la ejecución de ésta.

                Entre exception y end es posible escribir varios manejadores de excepciones,

                begin

                – secuencia de comandos

                exception

                when NUMERIC_ERROR|CONSTRAIN_ERROR => PUT(“Numeric or Constrain error ocurred”);

                when STORAGE_ERROR => PUT(“Ran out of space”);

                . . .

                when others =>

                PUT (“Something else went wrong”);

                end;

                . . .

                En  el  ejemplo  se  envía  un  mensaje  de  acuerdo  al  tipo  de  excepción.  Nótese  la  similitud  con  el

                comando  case.  Cada  when  es  seguido  por  uno  o  más  nombres  de  excepciones     separados  por  barras verticales. Como es usual, podemos escribir others; con las restricciones y significado análogos al comando case.

                Los  manejadores  de  excepciones  pueden  aparecer  al  final  de  un  bloque,  cuerpo  de  subprograma,

                cuerpo de paquete o cuerpo de una tarea, y tienen acceso a todas las entidades declaradas en la unidad. Los ejemplos anteriores mostraban un bloque degenerado que no contiene   una parte declarativa.   Una versión correcta para determinar TOMORROW como una función sería

                function TOMORROW(TODAY:DAY) return DAY is begin

                return DAY´SUCC(TODAY);

                exception

                when CONSTRAIN_ERROR =>

                return  DAY´FIRST;

                end TOMORROW;

                Es importante recalcar que el control nunca vuelve a la unidad donde se originó la excepción. La secuencia de comandos que sigue a => reemplaza la que queda de la unidad en cuestión y así completa su ejecución. Por lo tanto, un manejador de excepciones dentro de una función debería contener una instrucción return para entregar un resultado de “emergencia”.

                Una instrucción goto no puede transferir en control desde una unidad a uno de sus manejadores o viceversa, o desde un manejador a otro. Sin embargo, aparte de esta restricción, las instrucciones dentro de un manejador pueden ser tan complejas como se requiera.

                Un  manejador  declarado  al  final  del  cuerpo  de  un  paquete  se  aplica  sólo  a  la  secuencia  de inicialización  del  paquete  y  no  a  los  subprogramas  del  paquete.  Estos  últimos  deberían  tener  sus  propios manejadores.

                ¿Qué ocurre si se origina una excepción para la cual la unidad involucrada no posee un manejador?

                La respuesta es que la excepción se propaga   dinámicamente. Esto   significa que la ejecución de la unidad termina y la excepción es traspasada al punto en el cual se realizó la llamada   a la unidad. En el caso de un bloque se busca un manejador en la unidad que lo contenga.

                En el caso de un subprograma, la llamada termina y se busca un manejador en la unidad desde la que

                se llamó al subprograma. Este proceso se repite hasta que se llega a una unidad que contenga el manejador apropiado o se alcanza al nivel superior, es decir, se llega al programa principal y se obtendrá un mensaje ad- hoc del ambiente de ejecución (run time environment).

                Es importante entender que las excepciones se propagan dinámica y no estáticamente. Es decir, una excepción que no es manejada por un subprograma es propagada a la unidad que llama al subprograma y no a

                la unidad que contiene su declaración (el que puede o no ser el mismo).

                Si las instrucciones de un manejador a su vez originan una excepción, la unidad es terminada y la excepción se propaga a la unidad llamadora: los manejadores no entran en loop.

                10.2. Declaración y generación de excepciones

                En general, no es una buena práctica el utilizar las excepciones predefinidas para detectar situaciones inusuales,  porque  no  se  garantiza  que  las  excepciones  han  surgido  por  las  situaciones  que  se  están modelando, y no por alguna otra situación anómala.

                Como ejemplo consideremos el paquete STACK de la sección 8.1. Si llamamos a PUSH cuando la pila está llena, entonces  la instrucción TOP:= TOP + 1; conducirá a CONSTRAIN_ERROR. Análogamente, tendremos una excepción   del mismo tipo con TOP:= TOP – 1; con la función POP. Puesto que ninguno de

                los  subprogramas  tiene  manejador  de  excepción,  esta  se  propagará  a  la  unidad  que  los  llama.  Entonces, podríamos escribir

                declare

                use STACK;

                begin

                . . .

                PUSH(M);

                . . .

                N:=POP;

                . . .

                exception

                when CONSTRAIN_ERROR =>

                – ¿manipulación incorrecta de la pila?

                end;

                y   el   uso   incorrecto   de   la   pila   hará   que   el   control   se   transfiera   al   manejador   de   la   excepción CONSTRAIN_ERROR.  Sin  embargo,  no  existe  garantía  de  que  la  excepción  se  origine  debido  al  uso incorrecto  de  la  pila;  puesto  que  alguna  otra  situación  dentro  del  bloque  y  ajena  al  uso  de  la  pila  podría provocar una excepción CONSTRAIN_ERROR

                Una solución mejor es generar una excepción especialmente declarada para indicar el uso incorrecto

                de la pila. Entonces, el paquete podría reescribirse de la siguiente manera

                package STACK is

                ERROR: exception;

                procedure PUSH(X:INTEGER);

                function POP return INTEGER;

                end STACK;

                package body STACK is

                MAX: constant INTEGER:= 100; S: array (1 .. MAX) of INTEGER; TOP:INTEGER range 0 .. MAX;

                procedure PUSH(X:INTEGER) is begin

                if TOP = MAX then

                raise ERROR;

                end if;

                TOP := TOP + 1; S(TOP):=X;

                end PUSH;

                function POP return INTEGER is begin

                if TOP = 0 then

                raise ERROR;

                end if;

                TOP := TOP – 1;

                return S(TOP +1);

                end POP;

                begin

                end STACK;

                TOP:=0;

                Una  excepción  se  declara  de  manera  similar  a  una variable  y  es  generada  explícitamente  por  una

                instrucción raise junto con el nombre de la excepción. Las reglas de   manejo y propagación son las mismas que las de las excepciones predefinidas. Ahora podemos escribir

                declare

                begin

                use STACK;

                . . .

                PUSH(M);

                . . .

                N:=POP;

                . . .

                exception

                when ERROR =>

                – uso incorrecto de la pila.

                when others   =>

                – alguna otra cosa errónea.

                end;

                (Nótese que si no se hubiese colocado la cláusula use habría sido necesario referirse a la excepción como

                STACK.ERROR.)

                Ya sabemos como declarar una excepción y como originarla ¿Pero qué deberíamos colocar dentro

                del manejador de la excepción? Además de reportar que ha habido un error en el uso de la pila, deberíamos dejar la pila en un estado aceptable, sin embargo hasta ahora no hemos provisto la forma de hacer esto. Sería útil agregar un procedimiento RESET al paquete STACK. Otra cosa que sería necesario hacer es devolver todos los recursos que habían sido tomados en el bloque, para así evitar que sean retenidos inadvertidamente. Supongamos, por ejemplo, que también habíamos estado utilizando el paquete PRINTER_MANAGER que contiene entre otros procedimientos ASSIGN_PRINTER (que permiten asignar una impresora al proceso que llama al procedimiento) y RETURN_PRINTER (que devuelve la impresora al sistema).

                Además, sería conveniente limpiar la pila y devolver la impresora en caso de ocurrir cualquier otra excepción.  Para  realizar  esto  lo  mejor  sería  declarar  un  procedimiento  CLEAN_UP.  Entonces,  el  bloque quedaría

                declare

                use STACK, PRINTER_MANAGER; MY_PRINTER: PRINTER;

                procedure CLEAN_UP is begin

                end;

                RESET;

                RETURN_PRINTER(MY_PRINTER);

                begin

                ASSIGN_PRINTER(MY_PRINTER);

                . . .

                PUSH(M);

                . . .

                N:=POP;

                . . .

                RETURN_PRINTER(MY_PRINTER);

                exception

                when ERROR  =>  PUT(“STACK used incorrectly”); CLEAN_UP;

                when others      =>  PUT(“Something else went wrong”); CLEAN_UP;

                end;

                (Se ha asumido que en el paquete STACK se ha declarado un procedimiento RESET.)

                A  veces  las  acciones  que  se  deben  tomar  ante  una excepción  se  deben  realizar  por  niveles.  En  el ejemplo anterior se devuelve la impresora y se limpia la pila, pero probablemente se requiera que el bloque completo  sea  desechado.  Podríamos  indicar  esto  generando  una  excepción  como  la  acción  final  del manejador.

                excepcion

                when ERROR =>  PUT(“Stack used incorrectly”); CLEAN_UP;

                raise ANOTHER_ERROR;

                when others =>

                . . .

                end;

                De este modo la excepción ANOTHER_ERROR se propagará a la unidad que contenga el bloque.

                A veces es conveniente manejar una excepción y luego propagar la misma excepción. Esto puede hacerse escribiendo simplemente

                raise;

                Esto es particularmente útil cuando se manejan varias excepciones con un solo manejador debido a que no es posible nombrar explícitamente la excepción que ha ocurrido. Entonces, podríamos tener

                . . .

                when others =>  PUT (“Something else went wrong”); CLEAN_UP;

                end;

                raise;

                El ejemplo de la pila muestra un uso legítimo de excepciones. La excepción ERROR debería ocurrir

                muy rara vez y sería, por ende, poco conveniente chequear esta condición cada vez que se pudiera producir. Para  hacer  esto  seguramente  tendríamos  que  agregar  un  parámetro  adicional  (en  modo  out)                                   de  tipo BOOLEAN  al  procedimiento  PUSH  para  indicar  que  algo  no  funcionó  correctamente  y luego  chequear  el valor de esta parámetro después de cada llamada. En el caso de POP el cambio sería mayor puesto que ya no podría ser una función debido al uso de un parámetro de modo out.  Entonces, la especificación del paquete quedaría

                package STACK is

                procedure PUSH(X: in INTEGER; B: out BOOLEAN);

                procedure POP(X: out INTEGER; B: out BOOLEAN);

                end;

                y para usarlo deberíamos escribir

                declare

                begin

                end;

                use STACK;

                OK: BOOLEAN;

                . . .

                PUSH(M,OK);

                if not OK then  . . .      end if;

                . . .

                POP(N, OK);

                if not OK then  . . .   end if;

                . . .

                Es claro que en este caso el uso de excepciones contribuye a una mejor estructuración del programa.

                Finalmente, nótese que nada impide generar excepciones predefinidas. Por ejemplo en la sección 7.1

                cuando se analizaba la función INNER (pág. 40) se dijo que probablemente la mejor manera de controlar que

                los   límites   de   los   dos   parámetros   fuesen   los   mismos   era   generando   explícitamente   la   excepción

                CONSTRAIN_ERROR.

                10.3.  Ambito de validez de las excepciones

                En gran parte las excepciones siguen las mismas reglas de validez que las otras entidades (variables, tipos, subprogramas). Una excepción  puede ocultar o ser ocultada por otra declaración; puede hacerse visible mediante la notación punto, etc.  Sin embargo, son diferentes en otros aspectos. No podemos declarar arreglos

                de excepciones, y no pueden ser componentes de registros, parámetros de subprogramas, etc. De hecho, son sólo etiquetas, identificadores de una situación.

                Una característica importante de las excepciones es que no son creadas dinámicamente a medida que

                se  ejecuta  el  programa,  sino  que  se  les  debe  conceptualizar  como  existentes  a  través  de  toda  la  vida  del programa. Esto se relaciona con la forma en que las excepciones se propagan dinámicamente por la cadena de ejecución, y no estáticamente por la cadena determinada por las reglas de ámbito de validez. Una excepción puede ser propagada fuera de su ámbito de validez, pero sólo puede ser manejada anónimamente por others. Veamos el ejemplo siguiente

                declare

                procedure P is

                X: exception;

                begin

                begin

                end P;

                P;

                raise X;

                exception

                when others =>

                – Aquí se maneja la excepción X

                end;

                El procedimiento P declara y genera la excepción X, pero no la maneja. Cuando llamamos a P, la excepción

                X es propagada al bloque que llama a P donde es manejada anónimamente.

                Incluso es posible propagar una excepción  fuera de su rango de validez, donde se vuelve anónima, y luego volver a donde puede ser manejada explícitamente por su nombre. Consideremos

                declare

                package P is procedure F; procedure H;

                end P; procedure G is begin

                P.H;

                exception

                when others=>

                raise;

                end G,

                package body P is

                X: exception; procedure  F is begin

                G;

                exception

                when  X =>

                PUT(“Got it”);

                end F; procedure H is begin

                begin

                end;

                end P;

                P.F;

                end H;

                raise X,

                En  el  bloque  se  declara  un  paquete  P  que  contiene  los  procedimientos  F  y  H;  y  también  un

                procedimiento G. El bloque llama a F (que está en P), el que llama a G (que está fuera de P), el que a su vez llama a H (que está en P). El procedimiento H genera la excepción X cuyo ámbito de validez es el cuerpo de

                P. El procedimiento H no maneja X , entonces esta se propaga a G (que llamó a H). El procedimiento G está fuera del paquete P, es decir, la excepción X está fuera de su ámbito de validez; a pesar de ello G maneja la excepción   anónimamente  y  la  propaga.  G  es  llamado  por  F,   por  lo  tanto,  X  es  propagada  de  vuelta  al paquete y puede ser manejada explícitamente  por F.

                Otro característica de las excepciones la muestra el siguiente ejemplo de una función recursiva. A diferencia de las variables, no se obtiene una nueva excepción por cada llamada recursiva. Cada activación recursiva hace referencia a la misma excepción.

                procedure F(N:INTEGER) is

                X: exception;

                begin

                if N=0 then

                raise X;

                else

                end if;

                exception

                F(N-1);

                end F;

                when X =>

                PUT(“Got it”);

                raise;

                when others =>

                null;

                Supongamos que ejecutamos F(4);   se tendrán las llamadas recursivas F(3), F(2), F(1) y F(0). Cuando F es

                llamado  con  parámetro  cero  genera  la  excepción  X,  la  maneja:  escribe  un  mensaje  de  confirmación  y  la propaga. La activación recursiva que llama (en este caso F(1)) recibe la excepción y nuevamente la maneja, y

                así para todas las llamadas. Por lo tanto, el mensaje se escribe 5 veces y finalmente la excepción se propaga anónimamente.  Obsérvese que si cada activación recursiva hubiese creado su propia (diferente) excepción el mensaje se hubiese escrito sólo una vez.

                En todos los ejemplos que hemos visto las excepciones se generan en instrucciones. Sin embargo, también pueden generarse en declaraciones. Por ejemplo, la declaración

                N:POSITIVE:=0;

                generaría CONSTRAIN_ERROR porque el valor inicial de N no satisface la restricción 1 .. INTEGER´LAST

                del subtipo POSITIVE. Una excepción generada en una declaración no es manejada por   un manejador (si existe) de la unidad que la contiene, sino que se propaga inmediatamente a nivel superior. Esto significa que

                en  cualquier  manejador  estamos  seguros  que  todas  las  declaraciones  de  la  unidad  fueron  elaboradas satisfactoriamente y, por lo tanto, no hay riesgo de utilizar algo que no existe.

                11. Genéricos

                En   este   capítulo   se   describe   el   mecanismo   genérico   (generic)   que   permite   parametrizar subprogramas y paquetes con tipos y subprogramas así como valores y objetos.

                11.1. Declaraciones e Instanciaciones

                Uno  de  los  problemas  con  un  lenguaje  tipificado,  como  Ada,  es  que  todos  los  tipos  deben  ser determinados  en  tiempo  de  compilación.    Esto  significa  naturalmente  que  no  podemos  pasar  tipos  como parámetros en tiempo de ejecución.  Pero con frecuencia  llegamos a la situación en que la lógica de una pieza

                de programa es independiente de los tipos involucrados y por lo tanto pareciese ser innecesario el repetirla para todos los diferentes tipos para los cuales pudiésemos desear aplicarla.  Un ejemplo simple está propuesto por el procedimiento SWAP.

                procedure SWAP(X, Y: in out Real) is

                T: Real;

                begin

                end;

                T:=X; X:=Y; Y:=T;

                Es claro que la lógica es independiente del tipo de los valores que están siendo intercambiados.   Si

                también  quisiésemos  intercambiar  enteros  o  booleanos  podríamos  escribir  otros  procedimientos,  pero  esto sería tedioso.  El mecanismo genérico nos permite superar esto.  Podemos declarar:

                generic

                type ITEM is private;

                procedure EXCHANGE(X,Y: in out ITEM);

                procedure EXCHANGE(X,Y: in out ITEM) is

                T:ITEM;

                begin

                end;

                T:=X; X:=Y; Y:=T,

                El  subprograma  EXCHANGE  es  un  subprograma  genérico  y  actúa  como  una  plantilla  (template).

                La  especificación  del  subprograma  es  precedida  por  la  parte  formal  genérica  consistente  de  la  palabra reservada generic seguida de una lista (posiblemente vacía) de parámetros genéricos formales.  El cuerpo del subprograma  está  escrito  igual  que  siempre  pero  hay que  notar  que,  en  caso  de  un subprograma genérico, debemos escribir la especificación y el cuerpo separadamente.

                El procedimiento genérico no puede ser llamado directamente, pero a partir de él  podemos crear un

                procedimiento  efectivo  mediante  el  mecanismo  conocido  como  instanciación  genérica.   Por  ejemplo, podríamos escribir:

                procedure SWAP is new EXCHANGE(REAL);

                En esta declaración se indica  que SWAP debe ser obtenido de la plantilla descrita por EXCHANGE. Los  parámetros  genéricos  reales    son  proporcionados  en  una  lista  de  parámetros  en  la  forma  usual.    El parámetro real (en el ejemplo es el tipo REAL)  corresponde al parámetro formal ITEM.

                De modo que ahora hemos creado el procedimiento SWAP actuando sobre el tipo REAL y podemos por consiguiente llamarlo en la forma usual.  Podemos crear nuevas instanciaciones como:

                procedure SWAP is new EXCHANGE(INTEGER);

                procedure SWAP is new EXCHANGE(DATE);

                y muchas más.  Nótese que estamos creando nuevas sobrecargas de SWAP, las cuales pueden ser distinguidas por sus tipos de parámetros del mismo modo que si las hubiésemos escrito en detalle.

                Superficialmente,  puede  parecer  que  el  mecanismo  genérico  es  simplemente  una  substitución  de texto  y  en  efecto,  en  este  sencillo  ejemplo,  el  comportamiento  es  el  mismo.    Sin  embargo  la  diferencia importante se relaciona con el significado de los identificadores utilizados   en el cuerpo genérico, y que no son ni parámetros ni objetos locales.  Tales identificadores no locales poseen significados de acuerdo a donde

                fue declarado el  cuerpo genérico  y no  donde éste es instanciado.  Si se usara la simple substitución de texto,

                los identificadores no locales podrían, por supuesto, tomar su significado en el punto de instanciación y esto podría producir  resultados distintos de los  esperados.

                Así como podemos escribir subprogramas genéricos también podemos tener paquetes genéricos.  Un ejemplo  simple  de  esto  es  entregado  por  el  paquete  STACK.   El  problema  con  ese  paquete,  es  que  sólo trabaja sobre tipos INTEGER aunque, por supuesto, la misma lógica se aplica sin distinción del tipo de los

                valores manipulados.  También podemos aprovechar  la oportunidad para  hacer de MAX un parámetro de la misma forma, de tal modo que no estamos atados a un límite arbitrario de 100.  Escribimos:

                generic

                MAX:POSITIVE;

                type ITEM is private;

                package STACK is

                procedure PUSH(X: ITEM);

                function POP return ITEM;

                end STACK;

                package body STACK is

                S: array (1..MAX) of ITEM; TOP: INTEGER  range 0..MAX;

                – el resto como antes, pero  donde aparecía  INTEGER

                – ahora aparece ITEM

                end STACK;

                Ahora podemos crear y usar una pila de un tipo y un tamaño particular mediante la instanciación del paquete genérico de la siguiente forma:

                declare

                begin

                end;

                package MY_STACK is new STACK(100, REAL);

                use MY_STACK;

                …. PUSH(X);

                …. Y:=POP;

                ….

                El  paquete  MY_STACK,  que  es  el  resultado  de  la  instanciación,  se  comporta  como  un  paquete

                escrito directamente de la forma normal.  La cláusula use nos permite referirnos directamente tanto a PUSH

                como a POP.  Si hiciéramos una instanciación posterior

                package ANOTHER_STACK is new STACK(50, INTEGER);

                use ANOTHER_STACK;

                entonces PUSH y POP son sobrecargas que pueden ser distinguidas por el tipo entregado por el contexto. Por  supuesto,  si  ANOTHER_STACK  también  fuera  declarado  con  el  parámetro  genérico  real                                     REAL, entonces deberíamos usar notación punto para distinguir las instancias PUSH y POP a pesar de las cláusulas use.

                Tanto las unidades genéricas y las instanciaciones pueden ser unidades de biblioteca.  De este modo,

                habiendo   puesto   el  paquete  genérico  STACK  en  la  biblioteca  de  programas  se  podría  realizar  una instanciación y compilarla separadamente..

                with STACK;

                package BOOLEAN_STACK is new STACK(200, BOOLEAN);

                Si agregáramos una excepción de nombre ERROR al paquete, de tal modo que la declaración del paquete genérico fuese:

                generic

                MAX: POSITIVE;

                type ITEM is private;

                package STACK is

                ERROR: exception; procedure PUSH(X: ITEM); function POP return ITEM;

                end STACK;

                entonces cada instanciación debería dar origen a una excepción distinta y debido a que las excepciones no pueden ser sobrecargadas  naturalmente tendríamos que usar la notación punto para distinguirlos.

                Podríamos, por supuesto, hacer la excepción ERROR común a todas las instanciaciones definiéndola como   global para todo el paquete genérico.   Esta y el paquete genérico podrían quizá ser declarados dentro

                de otro  paquete.

                package ALL_STACK is

                ERROR: exception;

                generic

                MAX: POSITIVE;

                type ITEM is private;

                package  STACK is

                procedure PUSH(X: ITEM);

                function POP return ITEM;

                end STACK;

                end ALL_STACKS;

                package body ALL_STACK is package body STACK is

                ….

                end STACK;

                end ALL_STACK;

                Esto ilustra la ligación de los identificadores globales con las unidades genéricas.  El significado de ERROR queda determinado en el lugar de la declaración genérica, independiente del significado que pudiese tener en el  punto de instanciación.

                Los ejemplos anteriores han ilustrado parámetros formales, los cuales eran tipos y también enteros.

                En   efecto,   los   parámetros   formales   genéricos   pueden   ser   cualquiera   de  los  parámetros  aplicables  a subprogramas; pero  también pueden ser tipos y subprogramas.

                En el caso de los parámetros ya conocidos que también se aplican a subprogramas, estos pueden ser

                de modo in o in out, pero no out.  Como con los subprogramas, in es tomado por omisión (como  MAX en el ejemplo anterior).

                Un parámetro genérico in actúa como una constante cuyo valor es entregado por el parámetro real correspondiente.   Se  permiten  expresiones  por  omisión  como  en  los  parámetros  de  subprogramas;  tal expresión es evaluada durante la instanciación si no se suministran los parámetros reales del   mismo modo que en los subprogramas.

                Un parámetro in out, actúa como una variable que renombra el parámetro real correspondiente.   El parámetro  real  debe  por  tanto  ser  el  nombre  de  una  variable  y  su  identificación  ocurre  en  el  punto  de instanciación.

                Nuestro   último   ejemplo   en   esta   sección   ilustra   el   anidamiento   de   genéricos.       El   siguiente procedimiento  genérico  realiza  un  intercambio  cíclico  de  tres  valores  y  está  escrito  en  términos  del procedimiento genérico EXCHANGE.

                generic

                type THING is private;

                procedure CAB(A, B, C: in out THING);

                procedure CAB(A, B, C: in out THING) is

                procedure SWAP is new EXCHANGE(ITEM => THING);

                begin

                SWAP(A, B); SWAP(A, C);

                end CAB;

                Aunque el anidamiento está permitido, este no debe ser recursivo. Ejercicio.

                1.   Escriba la  declaración de un paquete genérico que implemente el tipo abstracto de datos PILA (es decir,

                que se puedan definir variables de tipo PILA) de forma tal que se pueda variar el tamaño de la pila y el tipo que se pueda almacenar. Si se instanciasen dos paquetes:PILA_REALES y PILA_ENTEROS. ¿Qué problema  habría  al  declarar,  por  ejemplo  X:PILA?  ¿Cómo  se  solucionaría  el  problema?  ¿Habría problemas al usar directamente POP y PUSH?

                2.   Escriba  un  paquete  genérico  que  permita  definir  variables  de  tipo  ARREGLO  a  las  cuales  se  le  puede indicar  el  largo           y  el  tipo  de  sus  elementos.  Sobre  objetos  tipo  ARREGLO  se  pueden  realizar  las siguientes acciones: colocar (colocar un valor en una cierta posición), ordenar, invertir, primero (entrega

                el primer elemento) y último (entrega el último elemento).

                11.2. Subprogramas  como parámetros

                Los parámetros genéricos también pueden ser subprogramas.   En algunos lenguajes, como Algol y

                Pascal,  los  parámetros  de  subprogramas  pueden  a  su  vez  ser   subprogramas.   Esta  facilidad  es  útil  para

                aplicaciones  matemáticas  como  la  integración.   En  Ada,  los  subprogramas  sólo  pueden  ser  parámetros  de unidades genéricas de modo que para estas aplicaciones se usa el mecanismo genérico.

                Podríamos tener una función genérica

                generic

                with function F(X: REAL) return REAL;

                function INTEGRATE (A, B: REAL) return REAL;

                la cual evalúa

                b

                ò f ( x)dx

                a

                para  integrar  una  función  en  particular  debemos  instanciar  INTEGRATE  con  nuestra  función  como  un

                parámetro genérico real.  Así, supongamos que necesitamos integrar la función

                et  sin t entre los límites 0 y P

                entonces escribiríamos

                function G(T: REAL) return REAL is begin

                end;

                return EXP(T)*SIN(T);

                function INTEGRATE_G is new INTEGRATE(G);

                y nuestro problema queda resuelto mediante la expresión

                INTEGRATE_G(0.0, P)

                Nótese  que  un  parámetro  subprograma  formal  es  como  una  declaración  normal  de  subprograma precedida por with.  (La palabra with al inicio es necesaria para evitar una ambigüedad sintáctica y no posee otro propósito.)   La correspondencia entre subprogramas formales y reales es tal que el subprograma formal actúa sólo como un nuevo nombre para el  subprograma real.

                Ejercicio.

                Dada la función

                generic

                with function F(X: REAL) return REAL;

                function SOLVE return REAL;

                que encuentra una raíz de la ecuación f(x) = 0, muestre como encontraría la raíz de la ecuación

                ex  + x = 7

                ¿Cómo haría que el tipo del parámetro de la función F también pudiese definirse durante la instanciación?

                12. Tareas

                El  último  tema  principal  a  ser  introducido  es  tareas.  Esto  ha  sido  dejado  para  el  final,  no  porque  no  sea importante,  pero  si  porque,  aparte  de  la  interacción  con  excepciones,  es  una  parte  bastante  independiente dentro del lenguaje.

                12.1 Paralelismo

                Hasta  ahora  sólo  hemos  considerado  programas  secuenciales  en  los  cuales  las  instrucciones  son ejecutadas  en  orden.  En  muchas  aplicaciones  es  conveniente  escribir  un  programa  con  varias  actividades paralelas las cuales interactúan como se requiera. Esto es particularmente cierto en programas que interactúan

                en tiempo real con procesos físicos en el mundo real.

                En Ada, las actividades paralelas se describen por medio de tareas. En casos simples una tarea es léxicamente  descrita  por  una  forma  muy  similar  a  un  paquete.  Esto  consiste  en  una  especificación  que describe  la  interfaz  presentada  a  otras  tareas  y  un  cuerpo  que  describe  el  funcionamiento  dinámico  de  las tareas.

                task T is                                — especificación

                ….. end T;

                task body T is                     — cuerpo

                ….. end T;

                En  algunos  casos  una  tarea  no  presenta  interfaz  a  otras  tareas  en  cuyo  caso  la  especificación  se reduce sólo a

                task T;

                Como un ejemplo simple de paralelismo considere una familia que va a comprar ingredientes para una comida. Suponga que necesitan carne, ensalada y vino; y la compra de esos artículos puede ser hecha al llamar  a  los  procedimientos  BUY_MEAT,  BUY_SALAD  y  BUY_WINE  respectivamente.  El  proceso completo  podría ser representado por

                procedure SHOPPING is begin

                BUY_MEAT;

                BUY_SALAD; BUY_WINE;

                end;

                Sin embargo esta solución corresponde a la familia que compra cada artículo en secuencia. Sería más eficiente  repartirse  el  trabajo  de  modo  que,  por  ejemplo,  la  madre  compra  la  carne,  los  niños  compran  la ensalada y el padre compra el vino. Y se ponen de acuerdo para encontrarse quizás   en el estacionamiento. Esta solución paralela puede  representarse por

                procedure SHOPPING is task GET_SALAD;

                task body GET_SALAD is begin

                BUY_SALAD;

                end GET_SALAD;

                task GET_WINE;

                task body GET_WINE is begin

                BUY_WINE;

                end GET_WINE;

                begin

                BUY_MEAT;

                end SHOPPING;

                En  esta  formulación,  la  mamá  se  representa  como  el  proceso  principal  y   llama  a  BUY_MEAT directamente  desde  el  procedimiento  SHOPPING.  Los  niños  y  el  papá  se  consideran  como  procesos subordinados   y   ejecutan   las   tareas   localmente   declaradas   GET_SALAD   and   GET_WINE   las   cuales respectivamente llaman a los procedimientos BUY_SALAD y BUY_WINE.

                El ejemplo ilustra la declaración, activación y terminación de tareas. Una tarea es un componente de

                un programa como un paquete y se declara en forma similar dentro de un subprograma, bloque, paquete o en otro cuerpo de tarea. Una especificación de tarea puede también declararse en una especificación del paquete

                en cuyo caso el cuerpo de tarea debe declararse en el paquete del cuerpo correspondiente. Sin embargo, una especificación de tarea no puede declararse en la especificación de otra tarea, sino sólo en el cuerpo.

                La activación de una tarea es automática. En el ejemplo anterior las tareas locales se activan cuando

                la unidad de los padre alcanza el begin que sigue a la declaración de las tareas.

                Tal tarea terminará cuando alcance su final end. Así la tarea GET_SALAD llama al procedimiento

                BUY_SALAD y luego termina rápidamente.

                Una tarea declarada en la parte declarativa de un subprograma, bloque o cuerpo de tarea se dice que depende de esa unidad. Una regla importante es que una unidad no puede ser dejada hasta que todas las tareas dependientes hayan terminado. Esta regla asegura que los objetos declarados en la unidad, y por esto mismo potencialmente  visible  a  las  tareas  locales,  no  pueden  desaparecer  mientras  exista  una  tarea  que    pueda accesarlos.

                Es importante darse cuenta que se considera que el programa principal es llamado por una hipotética “tarea  principal”.  Ahora  podemos  indicar  la  secuencia  de  acciones  cuando  esta  tarea  principal  llama  al procedimiento SHOPPING. Primero se declaran las tareas GET_SALAD y GET_WINE y luego cuando la tarea principal alcanza el begin estas tareas dependientes son activadas en paralelo con la tarea principal. Las tareas dependientes llaman a sus respectivos procedimientos y finalizan. Mientras, la tarea principal llama a BUY_MEAT y luego alcanza el end de SHOPPING. Luego, la tarea principal   espera hasta que las tareas dependientes hayan terminado si es que todavía no lo han hecho. Esto corresponde a la madre esperando al padre y a los niños que vuelven con sus  compras.

                En el caso general la terminación ocurre en dos etapas. Decimos que una unidad se completa cuando alcanza su end final. Y se le considerará terminada   cuando todas las tareas dependientes, si hay algunas, también estén terminadas. Por supuesto, si una unidad no tiene tareas dependientes entonces se completa y termina al mismo tiempo.

                Ejercicio

                Reescriba el procedimiento SHOPPING de modo que contenga  3 tareas y así revele la simetría natural de la

                situación. (Es decir, que las tres acciones tienen la misma jerarquía.)

                12.2       El rendezvous

                En el ejemplo SHOPPING las tareas no interactuaron unas con otras una vez que han sido activadas, excepto por el hecho que la unidad padre tiene que esperar a que todas terminen. Sin embargo, lo que ocurre generalemente es que las tareas interactúan y se coordinan para la consecución de un fin común. En Ada esto

                se ejecuta por un mecanismo conocido como un rendezvous. Esto es similar a la situación humana donde dos personas se encuentran, ejecutan una transacción y luego continúan independientemente.

                Un rendezvous entre dos tareas ocurre como consecuencia de la llamada de una tarea  a una entrada (entry)  declarada  en  otra  tarea.  Una  entrada  se                                             declara  de  un  manera  similar  a  como  se  declara  un procedimiento en la especificación de un paquete.

                task T is

                entry E( . . . );

                end;

                Una entrada puede tener parámetros in, out  e in out  al igual  que un procedimiento. Sin embargo

                no  puede  entregar  un  resultado  como   una  función.  Una  entrada  es  llamada  de  una  manera  similar  a  un procedimiento

                T.E( . . .);

                Un nombre de tarea no puede aparecer en una cláusula use y  por este motivo se requiere la notación punto para llamar la entrada desde el exterior de la tarea. Por supuesto, una tarea local podría llamar a una entrada de sus padres directamente – se aplican las reglas de visibilidad y alcance usuales.

                Las  instrucciones  que  se  ejecutan     durante  un  rendezvous  por  cada  entrada  son  descritas  por instrucciones de aceptación (accept) correspondientes en el cuerpo de la tarea. Una instrucción de aceptación generalmente toma la forma

                accept E( . . . ) do

                - – secuencia de instrucciones

                end E;

                Los  parámetros  formales  de  la  entrada  E  son  repetidos  del  mismo  modo  que  el  cuerpo  de procedimiento  repite  los  parámetros  formales  de  su  declaración.  El  end  opcionalmente  es  seguido  por  el nombre  de  la  entrada.  Una  diferencia  importante  es  que  el  cuerpo  de  las  instrucciones  accept  es  sólo  una secuencia  de  instrucciones.  Cualquiera  declaración  local  o  manejo  de  excepción  deben  ser  suministradas escribiendo un bloque local.

                La diferencia más importante entre una llamada de entrada y una llamada de procedimiento es que en

                el caso de un procedimiento, la tarea que llama al procedimiento inmediatamente también ejecuta el cuerpo de procedimiento  mientras  en  el  caso  de  una  entrada,  una  tarea  llama  a  la  entrada  pero  la  instrucción  de aceptación  correspondiente  es  ejecutada  por  la  tarea  que  posee  la  entrada.  Además,  la  instrucción  de aceptación no puede ser ejecutada hasta que una tarea llama a la entrada y la tarea que posee la entrada “llega hasta” la instrucción de aceptación. Naturalmente uno de estos acontecimientos ocurrirá primero y la tarea que esta involucrada (la que llama) queda suspendida hasta que otra (la poseedora de la entrada)   llega a su instrucción  (accept)  correspondiente.  Cuando  esto  ocurre  se  ejecuta  la  secuencia  de  instrucciones  de  la instrucción de aceptación. A esta interacción se le llama  rendezvous.  El rendezvous se completa cuando se alcanza el fin  de la instrucción de aceptación, luego ambas tareas proceden en forma independiente.

                Podemos elaborar nuestro ejemplo de compra dando a la tarea GET_SALAD dos entradas, una para

                la madre para que de dinero a los niños para ensalada y una para recoger su ensalada después. Hacemos lo mismo para GET_WINE .

                También podemos reemplazar los procedimientos BUY_SALAD, BUY_WINE y BUY_MEAT por funciones que toman dinero como un parámetro y retorna el ingrediente apropiado. Nuestro procedimiento compra ahora  sería

                procedure SHOPPING is

                task GET_SALAD is

                entry PAY(M: in MONEY);

                entry COLLECT(S: out SALAD);

                end GET_SALAD;

                task body GET_SALAD is CASH: MONEY; FOOD:SALAD;

                begin

                accept PAY(M: in MONEY) do

                CASH:=M;

                end PAY;

                FOOD:=BUY_SALAD(CASH);

                accept COLLECT(S: out SALAD) do

                S:=FOOD;

                end COLLECT;

                end GET_SALAD;

                - – GET_WINE en forma similar

                begin

                end;

                GET_SALAD.PAY (50); GET_WINE.PAY (100); MM:=BUY_MEAT (200); GET_SALAD.COLLECT (SS); GET_WINE.COLLECT (WW);

                El  resultado  final  es  que  varios  ingredientes  terminen  en  las  variables  MM,  SS,  y  WW  cuyas

                declaraciones son dejadas a la imaginación.

                Conviene  analizar  el  comportamiento  lógico  del  ejemplo  presentado.  Tan  pronto  como  las  tareas GET_SALAD  y  GET_WINE  se  activan    encuentran  instrucciones  accept  y  esperan  hasta  que  la  tarea principal llame las entradas PAY en cada una de ellas. Después de llamar a la función BUY_MEAT, la tarea principal llama a las entradas COLLECT. Curiosamente, la madre no esta habilitada para entregar el dinero para el vino hasta después que halla entregado   el de la ensalada. Una situación análoga se presenta con la recolección.

                Como un ejemplo más abstracto consideremos   el problema de crear   una tarea que actúe como un

                simple buffer entre una o más tareas que producen itemes y una o más tareas que los consumen. Nuestra tarea intermedia puede mantener solo un ítem.

                task BUFFERING is

                end;

                entry PUT(X: in ITEM);

                entry GET(X: out ITEM);

                task body BUFFERING is

                V: ITEM;

                begin

                loop

                accept PUT(X: in ITEM) do

                V:=X;

                end PUT;

                accept GET(X: out ITEM) do

                X:=V;

                end GET;

                end loop;

                end BUFFERING;

                Entonces otras tareas pueden colocar  o adquirir itemes llamando

                BUFFERING.PUT(. . .); BUFFERING.GET(. . .);

                El  almacenamiento  intermedio  para  el  ítem  es  la  variable  V.  El  cuerpo  de  tarea  es  una  iteración infinita que contiene una instrucción de aceptación para PUT seguido por GET. De este modo la tarea acepta

                en forma alternada llamadas de PUT y GET las cuales llenan y vacían la variable V.

                Varias tareas diferentes pueden llamar a PUT y GET y consecuentemente tendrán que ser enfiladas. Cada  entrada  (entry)  tiene  asociada  una  fila  de  tareas  a  la  espera  de   llamar  a  la  entrada   -  esta  fila  es procesada en modo FIFO y puede, por supuesto, estar  vacía en un momento dado. La cantidad de las tareas

                en la fila de la entrada E es dado por E’COUNT pero este atributo puede ser usado sólo dentro del cuerpo de

                la tarea que posea la entrada.

                Un entrada puede tener varias instrucciones de aceptación (generalmente sólo una). Cada ejecución

                de una instrucción de aceptación remueve una tarea a la fila.

                Note la asimetría intrínseca del   rendezvous. La tarea que se llama debe nombrar a la tarea llamada pero  no  viceversa.  Además,  muchas  tareas  pueden  llamar una  entrada  y  ser  puestas  en  fila  pero  una  tarea puede estar sólo en una fila a la vez .

                Una entrada puede no tener ningún parámetro, tal como

                entry SIGNAL;

                y podría luego ser llamada por

                T.SIGNAL;

                Una instrucción de aceptación no necesita tener cuerpo como en

                accept SIGNAL;

                En  tal  caso  el  propósito  de  la  llamada  es  simplemente  efectuar  una  sincronización  y  no  pasar información.

                No  hay  restricciones     sobre  las  instrucciones  en  una  instrucción  accept.  Ellas  pueden  incluir llamadas de entradas, llamadas de subprogramas, bloques e incluso otras instrucciones de aceptación (pero no para la misma entrada). Por otro lado, una instrucción de aceptación no puede aparecer en el cuerpo de un subprograma,  sino  que  debe  estar  en  la  secuencia  de  instrucciones  de  la  tarea,  aunque   podría  estar  en  un bloque o dentro de otra instrucción de aceptación. La ejecución de una instrucción de retorno (return) en una instrucción de aceptación corresponde a alcanzar el final y por lo tanto termina el rendezvous.

                Una  tarea  puede  llamar  a  una  de  sus  propias  entradas  pero,  por  supuesto,  esto  provocará  un

                deadlock.  Esto  puede   parecer  disparatado  pero  los  lenguajes  de  programación  permiten  gran  cantidad  de cosas “tontas” tales como una iteración infinita, etc.

                Ejercicios

                1.  Escriba el cuerpo de una tarea cuya especificación es

                task BUILD_COMPLEX is

                entry PUT_RL(X: REAL);

                entry PUT_IM(X: REAL);

                entry GET_COMP(X: out COMPLEX);

                end;

                y  que  alternadamente  crea  un  número  complejo  por  medio  de  las  lamadas  a  PUT_RL  y  PUT_IM  y  luego envíe el resultado en una llamada de GET_COMP.

                2.  Escriba el cuerpo de una tarea cuya especificación sea

                task CHAR_TO_LINE is

                entry PUT(C: in CHARACTER);

                entry GET(L: out LINE);

                end;

                donde

                type LINE is array (1 . . 80) of CHARACTER;

                La tarea actúa como un buffer que alternadamente construye una línea aceptando llamadas sucesivas

                de PUT y luego entrega una línea completa en una llamada de GET.

                3. En INTERNET existe una página donde se puede jugar un juego de apuestas colectivo que consiste en que

                se le hacen preguntas a un personaje y se debe determinar su identidad.

                Existe un personaje enmascarado al cual se le pueden formular preguntas, a las cuales puedo o no responder  (por  ejemplo,  no  responderá  si  se  le  pregunta  su  nombre).  Para  participar,  una  persona  debe inscribirse: hace su apuesta y recibe una clave que le permite participar en el juego. Tiene derecho a una o más preguntas (no necesariamente consecutivas) y por cada una de ellas puede formular una identificación

                del  personaje.  Si  acierta,  recibe  20  veces  su  apuesta.  Las  personas  que  se  han  inscrito  y  no  alcanzan  a participar pierden su apuesta.

                El programa que controla el juego tiene dos tareas paralelas:  una que controla la inscripción y otra que controla el juego propiamente tal. Cuando alguien gana, las inscripciones se suspenden.

                Escrito en ADA. Etiquetas: . Deja un Comentario »

                SQL

                SELECTSELECT [DISTINCT] <lista_columnas> | *

                FROM <lista_tablas>

                [WHERE <predicado>]

                EJ: Visualizar todos los vuelos que tengan como origen o destino Cáceres.

                SELECT *

                FROM VUELOS

                WHERE ORIGEN=’CACERES’

                OR DESTINO=’CACERES’

                EJ: Visualizar todos los vuelos que tengan como origen Madrid o Londres y como destino Londres o Madrid.

                SELECT *

                FROM VUELOS

                WHERE (ORIGEN=’MADRID’

                AND DESTINO=’LONDRES’)

                OR (ORIGEN=’LONDRES’

                AND DESTINO=’MADRID’)

                Claúsula IN

                Expresa la pertenencia del valor de una columna a un determinado conjunto de valores.

                EJ: Seleccionar aquellos vuelos que tengan como origen Madrid, Barcelona o Sevilla.

                SELECT *

                FROM VUELOS

                WHERE ORIGEN IN (‘MADRID’,'BARCELONA’,'SEVILLA’)

                ó también

                SELECT *

                FROM VUELOS

                WHERE ORIGEN=’MADRID’ OR ORIGEN=’BARCELONA’ OR ORIGEN=’SEVILLA’

                EJ: Visualizar todos los vuelos existentes excepto aquellos que llegan a Londres o a Copenhague.

                SELECT *

                FROM VUELOS

                WHERE DESTINO NOT IN (‘LONDRES’,'COPENHAGUE’)

                Claúsula BETWEEN

                Sirve para establecer o expresar un rango de valores. Obedece a la siguiente sintaxis:

                <nombre_columna> BETWEEN valor1 AND valor2

                El rango será [valor1, valor2], extremos incluidos.

                EJ: Recuperar todos los vuelos que salgan entre las 6 y las 12 de la mañana.

                SELECT *

                FROM VUELOS

                WHERE HORA_SALIDA BETWEEN ’06.00.00′

                AND ’12.00.00′

                ó también

                SELECT *

                FROM VUELOS

                WHERE HORA_SALIDA >= ’06.00.00′

                AND HORA_SALIDA <= ’12.00.00′

                EJ: En la columna NUM_VUELO representaré los vuelos con 6 caracteres. Los dos primeros caracteres indicarán la compañía a la que pertenece cada vuelo (IB6Iberia, BA6British Airways), los cuatro caracteres siguientes corresponderán al número de vuelo. Bajo estas condiciones recupérense todos los vuelos que no pertenecen a IBERIA.

                SELECT *

                FROM VUELOS

                WHERE NUM_VUELO NOT BETWEEN ‘IB0000′

                AND ‘IB9999′

                Claúsula LIKE

                Sirve para especificar, con la ayuda de metasímbolos, cadenas de caracteres que comparten ciertos caracteres en común. Los metasímbolos que serán utilizados son:

                % Equivale a una cadena de caracteres de longitud comprendida entre 0 y n.

                ‘AB%’ AB, ABCDE, AB 497

                _ Equivale a un único carácter

                ‘A_B’ A B, A4B, AJB

                EJ: Recuperar todos los vuelos pertenecientes a la compañía IBERIA.

                SELECT *

                FROM VUELOS

                WHERE NUM_VUELOS LIKE ‘IB%’

                ó también

                SELECT *

                FROM VUELOS

                WHERE NUM_VUELOS LIKE ‘IB_ _ _ _’

                Expresiones aritméticas

                +, -, *, /

                Pueden ser utilizadas tanto después de SELECT como después de WHERE. En el primer caso trabajarían sobre columnas y en el segundo sobre filas.

                EJ: Visualizar la longitud y la envergadura de todos los aviones, expresando las magnitudes en pies (en la base de datos está almacenado en metros, para pasar 1 metro a pies se ha de multiplicar por 3.28), y la velocidad de crucero en mph(está en Km/h, habrá que dividir por 1.6).

                SELECT LONGITUD*3.28, ENVERGADURA*3.28, VELO_CRUC/1.6

                FROM AVIONES

                ┌──┐ ┌──┐ ┌──┐

                Etiquetas 6       └──┘ └──┘ └──┘

                ——–     ——–      ——-

                ——–      ——–      ——-

                ——–      ——-       ——

                En DB/2 de IBM las etiquetas toman los nombres de las columnas (col1, col2, col3)

                En SQL-SERVER las etiquetas quedarían así (LONGITUD*3.28, ENVERGADURA*3.28, VELO_CRUC/1.6)

                EJ: Relación entre la longitud y la envergadura de todos los aviones.

                SELECT LONGITUD/ENVERGADURA

                FROM AVIONES

                EJ: Seleccionar aquellos aviones cuya longitud supere a su envergadura en más de un 10%.

                SELECT *

                FROM AVIONES

                WHERE LONGITUD > ENVERGADURA*1.10

                Funciones de columna

                Son funciones que operan con todas las filas que cumplen la condición expuesta en la claúsula WHERE. Su resultado es un único valor. Sintaxis:

                11) <f_columna> ([DISTINCT] <nombre_columna>)

                21) <f_columna> (<expresión>), donde <expresión> es una expresión aritmética en la cual debe participar, al menos, una columna.

                31) COUNT(*)

                Funciones

                <f_columna>:

                MIN: Calcula el valor mínimo de una columna.

                MAX: Calcula el valor máximo de una columna.

                AVG: Calcula la media aritmética de una columna.

                SUM: Calcula la suma de todos los campos de una columna.

                COUNT: Cuenta el n1 de filas de una columna.

                A B
                3 5
                2 8
                3 7
                4 3

                COUNT(A)=COUNT(B)

                COUNT(A)=4, COUNT(B)=4

                El COUNT de dos columnas de una misma tabla es igual. COUNT(*) sirve para obtener el n1 de filas.

                EJ: Seleccionar los valores mínimo y máximo de la columna que almacena las velocidades de crucero.

                SELECT MIN(VELO_CRUC), MAX(VELO_CRUC)

                FROM AVIONES

                EJ: Averiguar a que hora parte el primer vuelo hacia Madrid.

                SELECT MIN (HORA_SALIDA)

                FROM VUELOS

                WHERE DESTINO=’MADRID’

                Regla que cumplen las funciones de columna

                La función de columna sólo podrá especificarse detrás de la particula SELECT o en la claúsula HAVING, pero nunca dentro de la claúsula WHERE.

                EJ: Se desea saber cuál es el vuelo que tiene la mínima hora de salida.

                SELECT *

                FROM VUELOS

                WHERE HORA_SALIDA=(SELECT MIN(HORA_SALIDA)

                FROM VUELOS)

                Claúsula GROUP BY-HAVING

                Sirve para dividir una tabla en grupos de filas que comparten características comunes. La sintaxis es:

                SELECT <lista_columnas>, <funciones_de_columna>

                FROM <lista_tablas>

                [WHERE <predicado>]

                [GROUP BY <lista_columnas>]

                [HAVING <predicado>]

                EJ: Efectúese una SELECT que visualice el mínimo valor de hora de salida para cada uno de los diferentes destinos.

                SELECT DISTINCT DESTINO

                FROM VUELOS

                SELECT MIN(HORA_SALIDA)

                FROM VUELOS

                WHERE DESTINO LIKE ‘%’

                A continuación se muestra un ejemplo de lo que no se debe hacer:

                SELECT MIN(HORA_SALIDA)

                FROM VUELOS

                WHERE DESTINO IN (SELECT DISTINCT DESTINO

                FROM VUELOS)

                Sentencia GROUP BY:

                SELECT DESTINO, MIN(HORA_SALIDA)

                FROM VUELOS

                GROUP BY DESTINO

                Tabla VUELOS 6 Tabla auxiliar 6                  Tabla x ‘MADRID’

                WHERE           GROUP BY DESTINO          Tabla y ‘BARCELONA’

                Tabla z ‘SEVILLA’

                <f_columna>

                GROUP BY crea una serie de subtablas compuestas por filas con el mismo valor para la columna de agrupamiento (en este ejemplo la columna DESTINO). Se aplicarán a continuación funciones de columna sobre cada subtabla de forma independiente.

                MADRID, x

                BARCELONA, y

                SEVILLA, z

                No se puede poner en GROUP BY un campo que no se haya incluido en la sentencia SELECT.

                 

                EJ: Obtener el origen del vuelo para cada uno de los vuelos que tienen la mínima hora de salida para cada uno de los destinos.

                EJ: Obtener el número de vuelos que existen para cada uno de los orígenes.

                SELECT ORIGEN, COUNT(*)

                FROM VUELOS

                GROUP BY ORIGEN

                Claúsula HAVING

                Permite elegir aquellos grupos que se quieren visualizar.

                EJ: Visualizar los grupos que tienen para cada uno de los orígenes la mínima hora de salida siendo anterior a las 12 horas.

                SELECT ORIGEN, MIN(HORA_SALIDA)

                FROM VUELOS

                GROUP BY ORIGEN

                HAVING MIN(HORA_SALIDA) < ’12.00′

                HAVING no interferirá en la agrupación por filas de GROUP BY.

                EJ: Se desea seleccionar la hora de salida más temprana para cada origen y destino.

                SELECT ORIGEN, DESTINO, MIN(HORA_SALIDA)

                FROM VUELOS

                GROUP BY ORIGEN, DESTINO

                EJ: Visualizar los orígenes que tengan más de dos vuelos.

                SELECT ORIGEN

                FROM VUELOS

                GROUP BY ORIGEN

                HAVING COUNT(*) > 2

                EJ: Visualizar los vuelos de IBERIA que tengan más de 150 plazas libres.

                SELECT NUM_VUELO, SUM(PLAZAS_LIBRES)

                FROM RESERVAS

                GROUP BY NUM_VUELO

                HAVING NUM_VUELO LIKE ‘IB%’

                AND SUM(PLAZAS_LIBRES)>150

                ó también

                SELECT NUM_VUELO, SUM(PLAZAS_LIBRES)

                FROM RESREVAS

                WHERE NUM_VUELO LIKE ‘IB%’

                GROUP BY NUM_VUELO

                HAVING NUM_VUELO ‘IB%’

                AND SUM(PLAZAS_LIBRES)>150

                TRATAMIENTO DE NULOS

                Operaciones aritméticas

                Cualquier operación aritmética sobre un campo nulo nos devolverá como resultado un valor nulo.

                Tomemos como ejemplo la siguiente tabla:

                NULOS

                COL_A COL_B
                15 10
                35 35
                140 NULL
                NULL 100
                NULL NULL
                7 110
                33 60
                NULL NULL
                NULL NULL

                SELECT COL_A+COL_B

                FROM NULOS

                COL_A+COL_B

                25

                70

                NULL

                NULL

                NULL

                117

                93

                NULL

                NULL

                Funciones de columna

                Ignoran los campos NULL, exceptuando la función COUNT.

                SELECT AVG(COL_A)

                SELECT SUM(COL_A)/COUNT(*)

                AVG(COL_A)=46

                SUM(COL_A)/COUNT(*)=25.5

                Comparaciones

                Dos valores nulos no son iguales ni son distintos, sino indeterminados.

                SELECT *

                FROM NULOS

                WHERE COL_A=COL_B

                COL_A COL_B

                35        35

                SELECT *

                FROM NULOS

                WHERE COL_A<>COL_B

                COL_A COL_B

                15        10

                140      NULL

                NULL    100

                7          110

                33        60

                SELECT *

                FROM NULOS

                WHERE COL_A IS NULL

                Esta orden visualiza todas las filas en las que el campo perteneciente a la columna COL_A es nulo.

                Ordenación

                Dependiendo del sistema gestor en uso los valores nulos serán los de mayor o los de menor peso.

                DB/2 de IBM

                NULL 6 Mayor peso. en ordenación ascendente serán los últimos.

                SELECT COL_A

                FROM NULOS

                ORDER BY COL_A

                COL_A

                7

                15

                33

                35

                140

                NULL

                NULL

                NULL

                NULL

                SQL-SERVER

                NULL 6 Menor peso. En ordenación ascendente serán los primeros.

                SELECT COL_A

                FROM NULOS

                ORDER BY COL_A

                COL_A

                NULL

                NULL

                NULL

                NULL

                7

                15

                33

                33

                140

                DISTINCT

                No elimina los valores nulos repetidos.

                SELECT DISTINCT COL_A

                FROM NULOS

                COL_A

                15

                35

                140

                NULL

                NULL

                7

                33

                NULL

                NULL

                Indices únicos

                Sobre una columna de índice único sólo está permitida la existencia de un valor nulo.

                CREATE UNIQUE INDEX IXNULOS

                ON NULOS

                (COL_A)

                Devolvería un error, ya que existe más de un campo con NULL.

                Para este caso los nulos se interpretan como valores iguales.

                GROUP BY

                Todos los nulos quedarán agrupados en el mismo grupo.

                SELECT COL_A, COUNT(*)

                FROM NULOS

                GROUP BY COL_A

                COL_A   COUNT(*)

                15                    1

                35                    1

                140                  1

                NULL                4

                7                      1

                33                    1

                Todos los valores NULL se agrupan y COUNT devuelve el número de filas que tenían NULL en COL_A.

                SUBSELECT

                Responde a la siguiente sintaxis:

                SELECT <lista_columnas>

                FROM <lista_tablas>

                WHERE <nombre_columna> <CONCATENADOR> (SELECT <nombre_columna>

                FROM <lista_tablas>

                WHERE <Predicado>)

                <CONCATENADOR>

                Puede ser un operador de comparación o la claúsula IN

                Operadores de comparación: >,<,>=,<=,=,<>

                Restricciones: ha de exigirse que el resultado de la Subselect sea un único valor al usar como concatenador un operador de comparación. Si usamos IN puede devolver más de un valor.

                Cada Select se ejecuta una única vez, desde la más interna, hasta la más externa.

                EJ: Se desea recuperar las plazas libres que hay en cada vuelo MADRID-LONDRES del día 20/02/92.

                {Las plazas libres es un campo de la tabla de reservas. En la tabla de vuelos tenemos el origen y el destino de cada vuelo.}

                SELECT *

                FROM RESERVAS

                WHERE FECHA_SALIDA=’20.02.1992′

                AND NUM_VUELO IN(SELECT NUM_VUELO

                FROM VUELOS

                WHERE ORIGEN=’MADRID’

                AND DESTINO=’LONDRES’)

                ANY, ALL

                Se usan para poder utilizar operadores de comparación con subselects que nos devuelvan más de un valor único como resultado.

                SELECT <lista_columna>

                FROM <lista_tablas>

                WHERE <nombre_columna> <CONCATENADOR> {ANY/ALL} (<Subselect>)

                Una expresión ANY es cierta si lo es para algún valor de los que devuelve la Subselect.

                Una expresión ALL es cierta si lo es para todos los valores que devuelve la Subselect.

                3>ANY(2,5,7) 6 Cierto

                3=ANY(2,5,7) 6 Falso

                3>ALL(2,5,7) 6 Falso

                3<ALL(9,10,11) 6 Cierto

                EJ: Se quiere recuperar los aviones cuya longitud sea mayor que la envergadura de todos ellos.

                SELECT *

                FROM AVIONES

                WHERE LONGITUD > ALL (SELECT ENVERGADURA

                FROM AVIONES)

                ó también

                SELECT *

                FROM AVIONES

                WHERE LONGITUD > (SELECT MAX(ENVERGADURA)

                FROM AVIONES)

                =ANY e IN tienen la misma función.

                3=ANY(2,3,5) y 3 IN (2,3,5) devuelven ambos Cierto.

                Subselects correlacionadas

                Son un tipo especial de subselect. La sintaxis es similar:

                SELECT <lista_columnas>

                FROM <nombre_tabla_externa>

                WHERE <nombre_columna> <CONCATENADOR> (SELECT <nombre_columna>

                FROM <nombre_tabla_interna>

                WHERE <Predicado>)

                En <Predicado> habrá una sentencia del tipo

                <nombre_columna_tabla_interna> <operador> <nombre_columna_tabla_externa>

                Las formas de ejecutar una subselect ordinaria y una correlacionadas son diferentes. Las subselects correlacionadas obedecen al siguiente algoritmo:

                ALGORITMO Subselect_Correlacionada

                1 Seleccionar fila de tabla externa

                2 Ejecutar SELECT interno

                3 Evaluar la condición del WHERE externo

                - Cierto: la fila seleccionada en 1 será una fila de salida

                4 Si existe alguna fila más en la tabla externa ir al paso 1

                EJ: Se desea recuperar las reservas cuyo número de plazas libres sea mayor que la media para ese mismo vuelo.

                SELECT *

                FROM RESERVAS, A

                WHERE PLAZAS_LIBRES > (SELECT AVG(PLAZAS_LIBRES)

                FROM RESERVAS

                WHERE NUM_VUELO=A.NUM_VUELO)

                RESERVAS

                NUM_VUELO    FECHA_SALIDA PLAZAS_LIBRES

                IB740               20.02.92           5

                IB740               25.02.92           15

                IB740               03.03.92           10

                AVG(PLAZAS_LIBRES)=10

                Alias

                Es un sobrenombre que se le da a una tabla y que debe ser único para toda la consulta. Se escribe dejando un blanco detrás del nombre de la tabla a la cual se va a calificar.

                EJ: Se quiere recuperar los aviones que tienen menos de 1 hora y cuarto de recorrido como término medio.

                VUELOS

                NUM_VUELO    ORIGEN           DESTINO          DISTANCIA

                B747                MADRID           LONDRES        10000

                B747                MADRID           PARIS              4000

                AVIONES

                NUM_VUELO    VELO_CRUC

                …………             …………

                v=e/t, t>e/v, 1.25>e/v, v*1.25 > AVG(DISTANCIA)

                SELECT AVIONES     SELECT VUELOS

                SELECT *

                FROM AVIONES

                WHERE 1.25*VELO_CRUC > (SELECT AVG(DISTANCIA)

                FROM VUELOS

                WHERE NUM_VUELO=AVIONES.NUM_VUELO)

                EXISTS-NOT EXISTS

                Se define para comprobar la existencia o ausencia del valor devuelto por una Subselect. Una expresión con EXIST devuelve Cierto si la Subselect nos devuelve al menos un valor.

                WHERE EXISTS (<Subselect>) 7 Cierto

                EJ: Seleccionar toda la información de vuelos para aquellos que tengan origen Madrid y en los que queden plazas libres.

                SELECT *

                FROM VUELOS

                WHERE ORIGEN=’MADRID’

                AND EXISTS (SELECT *

                FROM RESERVAS

                WHERE PLAZAS_LIBRES > 0

                AND NUM_VUELO=VUELOS.NUM_VUELO)

                EJ: Obtener los tipos de avión y capacidades para aquellos en los que queden menos de 30 plazas libres (JOIN).

                ORDER BY

                 

                Se define para ordenar la salida de una consulta por los campos que se especifiquen a continuación.

                Sintaxis:

                 

                SELECT

                FROM

                WHERE

                GROUP BY

                HAVING

                ORDER BY

                 

                ORDER BY <especificación de columna><orden>{,<especificación de columna><orden>}

                 

                <especificación de columna>=<nombre de columna>|<posición de columna>

                <orden>=ASC|DESC

                 

                Ej: Obtener el número de plazas libres que quedan para cada vuelo y ordenar el resultado de más a menos plazas libres. Para igual número de plazas ordénese por número de vuelo.

                 

                SELECT NUM_VUELO, SUM(PLAZAS-LIBRES)

                FROM RESERVAS

                GROUP BY NUM_VUELO

                ORDER BY 2 DESC, NUM_VUELO

                UNION-UNION ALL

                 

                Se define para recuperar, usando una única consulta, información que se obtiene a partir de más d una consulta.

                Sintaxis:

                <SELECT>

                UNION [ALL]

                <SELECT>

                {UNION[ALL]

                <SELECT>}

                 

                Características:

                Cada SELECT devuelve un conjunto de filas. La unión será la tabla resultado.

                Condiciones de cada estructura SELECT:

                - Todas deben ser iguales o compatibles una a una. Esto supone que por cada columna tengamos un único tipo de dato.

                - Pueden ser completas (WHERE, GROUP BY, …), exceptuando la claúsula ORDER BY, que se ubicará al final de la última SELECT.

                UNION sin ALL proporciona un resultado sin filas duplicadas.

                Ej: Sacar una lista de todas aquellas ciudades para las que haya vuelo, ordenadas alfabéticamente.

                 

                SELECT ORIGEN

                FROM VUELOS

                UNION

                SELECT DESTINO

                FROM VUELOS

                ORDER BY 1

                Catálogo del sistema o diccionario de datos:

                 

                Es el alma de un sistema gestor. Se define como un conjunto de tablas que forman una base de datos, y son definidas y mantenidas automáticamente por el sistema gestor. Sirven para almacenar información sobre los objetos definidos por los usuarios.

                 SELECT *

                FROM VUELOS

                 

                1- El sistema busca en el catálogo si existe la tabla VUELOS.

                2- Verifica si el usuario tiene acceso a esa información.

                3- Se pregunta cuáles y cuántas columnas tiene la tabla VUELOS.

                DB2 IB

                SYSTABLES: una fila por cada tabla definida en la instalación.

                SYSCOLUMNS: una fila por cada columna definida.

                SYSINDEXES: una fila por cada índice definido.

                SYSVIEW: una fila por cada vista.

                SYSTABAUTH: una fila por cada autorización definida.

                Todas las tablas son directamente consultadas por usuarios autorizados.

                ADM (Administrador): es la persona que concede autorizaciones a los usuarios.

                Un usuario autorizado puede efectuar operaciones del tipo:

                SELECT *

                FROM SYSTABLES

                WHERE NAME=’RESERVAS’

                NAME              DBNAME          CARD

                RESERVAS      AEROPUERTO ……….

                DBNAME: Nombre de la BdD a la que pertenece la tabla.

                CARD: N1 de filas de la tabla.

                OWNER: Usuario creador de la tabla.

                SELECT *

                FROM SYSCOLUMNS

                WHERE DBNAME=’RESERVAS’

                Nos da la información sobre todas las columnas que pertenecen a la tabla reservas.

                NAME              TBNAME          COL_NO          COL_TYPE       LENGTH           NULLS

                NUM_VUELO    RESERVAS      1                      CHAR               6                      N

                FECHA_SALIDA RESERVAS    2                      DATE               8                      N

                TBNAME: Nombre de la tabla.

                COL_NO: Posición de la columna en la tabla.

                COL_TYPE: Tipo de dato

                LENGTH: Longitud del dato de la columna.

                NULLS: Indica si se permite valor nulo.

                Ej: Obténgase la última hora de salida para cada destino de los vuelos realizados por aviones capaces de almacenar más combustible que un tercio de la media que pueden almacenar los demás aviones.

                 

                SELECT DESTINO, MAX(HORA_SALIDA)

                FROM VUELOS

                WHERE TIPO_AVION IN (SELECT TIPO

                FROM A

                WHERE COMBUSTIBLE>1/3*(SELECT AVG(COMBUSTIBLE)

                FROM AVIONES

                WHERE TIPO<>A.TIPO)

                Ej: Crear una vista sobre la tabla vuelos con las columnas ORIGEN y DESTINO para aquellos vuelos que no sean de IBERIA. Visualizar el contenido de la lista para los vuelos que no partan de Madrid. Borrar la vista.

                 

                CREATE VIEW V_VUELOS

                (V_ORIGEN, V_DESTINO)

                AS SELECT ORIGEN, DESTINO

                FROM VUELOS

                WHERE NUM_VUELO NOT LIKE ‘IB%’

                SELECT *

                FROM V_VUELOS

                WHERE V_ORIGEN<>’MADRID’

                DROP VIEW V_VUELOS

                Ej: Visualice los tipos de avión , el doble de su longitud y la mitad de su envergadura, para aquellos aviones con envergadura mayor que la media y que realizan vuelos desde o hacia Barcelona, ordenándolos de mayor a menor longitud.

                 

                SELECT TIPO_AVION, 2*LONGITUD, .5*ENVERGADURA

                FROM AVIONES, VUELOS

                WHERE ENVERGADURA>(SELECT AVG(ENVERGADURA)

                FROM AVIONES)

                AND

                AVIONES.TIPO_AVION=VUELOS.TIPO_AVION

                AND

                (ORIGEN=’BARCELONA’ OR DESTINO=’BARCELONA)

                ORDER BY 2 DESC

                Ej: Visualice las tres primeras letras de los orígenes y destinos de los vuelos realizados por aviones con longitud mayor con longitud mayor que la media y envergadura menor que 2/3 de la máxima envergadura, ordenados alfabéticamente por destino.

                 

                SUBSTRING (SQL)

                (SUBSTRNG), (DB2)

                SUBSTRING (string, posición, n1caracteres)

                9

                nom_col / cadena con comillas (“)

                SELECT SUBSTRING (ORIGEN, 1, 3), SUBSTRING (DESTINO, 1, 3)

                FROM VUELOS

                WHERE TIPO_AVION IN (SELECT TIPO

                FROM AVIONES

                WHERE LONGITUD > (SELECT AVG(LONGITUD)

                FROM AVIONES)

                AND ENVERGADURA*3/2 < (SELECT MAX(ENVERGADURA)

                FROM AVIONES)

                ORDER BY 2

                Ej: Visualice el total de plazas libres por número de vuelo para aquellos realizados desde Madrid a Barcelona o Sevilla y que recorran una distancia mayor que la media de todos los vuelos que salen de Madrid, ordenándolos de menor a mayor.

                 

                SELECT SUM(PLAZAS_LIBRES), NUM_VUELO

                FROM RESERVAS, VUELOS

                WHERE RESERVAS, NUM_VUELO=VUELOS.NUM_VUELO

                AND ORIGEN=’MADRID’

                AND DESTINO IN (‘BARCELONA’, ‘SEVILLA’)

                AND DISTANCIA > (SELECT AVG(DISTANCIA)

                FROM VUELOS

                WHERE ORIGEN=’MADRID’)

                ORDER BY 1

                Ej: Obtener para cada número de vuelo el total de plazas libres de los vuelos que recorran distancias menores que 2/3 de la media de las distancias recorridas por vuelos de otras compañías.

                SELECT NUM_VUELOS, SUM(PLAZAS_LIBRES)

                FROM RESERVAS, VUELOS 7 V

                WHERE RESERVAS.NUM_VUELO=VUELOS.NUM_VUELOS

                AND DISTANCIA*3/2 < (SELECT AVG(DISTANCIA)

                FROM VUELOS

                WHERE SUBSTRING (NUM_VUELO, 1, 2) <>

                SUBSTRING (VUELOS.NUM_VUELO, 1, 2)

                TEORIA DE LA NORMALIZACION

                 

                Introducción:

                 

                Nos basaremos en la siguiente tabla:

                AUTORES-LIBROS
                NOMBRE NACION COD_LIB TITULO EDITOR
                Date USA 999 IBD AW
                Ad.Mig. ESP 888 CyD RM
                Ma.Piat. ITA 888 CyD RM
                Date USA 777 BdD AW

                Bibliografía: DIseño y Gestión de Bases de Datos. Angle Lucas.

                Se plantean una serie de problemas:

                Redundancia: cuando un autor tiene varios libros, se repite la nacionalidad.

                 

                Anomalías de modificación: Si Ad.Mig. y Ma.Piat. desean cambiar de editor, se modifica en los 2 lugares. A priori no podemos saber cuántos autores tiene un libro. Los errores son frecuentes al olvidar la modificación de un autor. Se pretende modificar en un sólo sitio.

                Anomalías de inserción: Se desea dar de alta un autor sin libros, en un principio. NOMBRE y COD_LIB son campos clave, una clave no puede tomar valores nulos.

                Teoría de la normalización:

                 

                La teoría de la normalización ofrece una serie de reglas para efectuar una modelización óptima.

                La tabla anterior debería dividirse en 3 tablas:

                AUTORES (NOMBRE, NACION)

                LIBROS (COD_LIB, TITULO, EDITOR)

                ESCRIBE (NOMBRE, COD_LIB)

                 

                En los años 70 Codd creó las bases de la teoría de la normalización. A cada regla de la teoría la denominó forma normal. Codd creó las formas normales 10, 20 y 30. La 30 forma normal originó problemas. Boyce ayudo a solventarlos con la f.n. de Boyce-Codd (FNBC). A finales de los 70 Fagin creó las formas normales 40 y 50.

                Las formas normales se basan en el concepto de dependencia, que comprende las restricciones definidas sobre los atributos de una relación. Existen diferentes tipos de dependencia:

                - Dependencias funcionales (Formas normales 10, 20 y 30 y FNBC)

                - Dependencias multivaluadas (40 forma normal)

                - Dependencia de JOIN (50 forma normal)

                Formas normales

                 

                10 forma normal: es una restricción inherente del modelo relacional. Se decie que una tabla está en 10 forma normal si no existen en ella grupos repetitivos.

                Una tabla no puede tener en un campo más de un valor.

                TITULO            AUTOR

                CyD                 Ad.Mig.

                Ma.Piat.

                Hay un grupo repetitivo. De este modo la tabla no es plana y no está en 10 forma normal. Para convertirla a 10 forma normal:

                TITULO            AUTOR

                CyD                 Ad.Mig.

                CyD                 Ma.Piat.

                20 forma normal: partimos de la idea de dependencia funcional: Un atributo o conjunto de atributos B depende funcionalmente de A sí y sólo si a cada valor de A le corresponde un único valor de B:

                A 6 B <=> a cada valor de A le corresponde un único valor de B

                A          B

                x1        y1

                x2        y2

                x3        y3

                Ej: DNI depende funcionalmente de NOMBRE y NOMBRE de DNI

                 

                DNI 6 NOMBRE

                NOMBRE 6 DNI

                NOMBRE 76 DNI

                Ej: DIRECCION depende funcionalmente de DNI, pero DNI no depende funcionalmente de DIRECCION

                 

                DNI 6 DIRECCION

                DIRECCION ─/6 DNI

                Ej: TITULO, LIBRO no dependen funcionalmente de DNI, AUTOR, porque un autor puede escribir varios libros

                 

                DNI, AUTOR ─/6 TITULO, LIBRO

                Ej: Se tiene una base de datos de pluriempleados:

                 

                Atributos: DNI, EMPRESA, SUELDO

                 

                DNI ─/6 EMPRESA

                DNI ─/6 SUELDO

                Se puede concatenar atributos, obteniendo:

                 

                DNI, EMPRESA 6 SUELDO

                Sueldo es el atributo implicado que depende de DNI y EMPRESA juntos, que son atributos implicantes. También:

                 

                DNI 6 NOMBRE, DIRECCION

                Las 3 primeras formas normales más la forma normal de Boyce-Codd se basan en dependencias funcionales obedenciendo al siguiente teorema:

                Dada una relación R con un conjunto de atributos A que cumple R(A), x6y, es posible una descomposición en dos tablas de la siguiente manera:

                 

                R(A), x6y

                 

                R(x,y) es una relación compuesta por los atributos que forman la dependencia funcional

                R(A-y) es una relación compuesta por los atributos de R excluyendo el atributo implicado

                 

                R(A)=R(x, y)    R(A-y) el JOIN de ambas forma la relación original.

                 

                Las relaciones a partir de ahora se definirán como un conjunto de atributos con dependencias funcionales R(A, DF).

                Para normalizar la tabla habrá que conocer todas las dependencias funcionales, pero en la relación que nos den sólo tendremos algunas, a partir de las cuales podremos hallar el resto.

                Aplicaremos las propiedades de las dependencias funcionales para obtener todo el conjunto de posibles dependencias funcionales que puedan existir en la relación.

                Al conjunto inicial de depndencias funcionales lo llamaremos F, conjunto a partir del cual obtendremos el resto de depndencias funcionales. A cada nueva dependencia funcional obtenida a partir de F la llamaré f. Al nuevo conjunto que contenga todas las dependencias funcionales que obtenga el llamaré F+. Una vez hallado F+ podré aplicar las formas normales de la teoría de la normalización.

                Dependencia funcional derivada

                 

                Dado un conjunto F de dependencias funcionales se dice que f deriva de F (F├ f) si f se obtiene a partir de F.

                Cierre de un conjunto de dependencias funcionales

                Se define Cierre (F+) como el conjunto de todas las dependencias funcionales implicadas por F o halladas a partir de F.

                Propiedades de las dependencias funcionales

                a) Axiomas

                b) Propiedades propiamente dichas

                 

                a) Axiomas

                a.1) Axioma reflexivo

                 

                Si Y está incluido en X entonces X 6 Y (Si Y c X => X 6 Y)

                Ej: CODPROV c CODPOSTAL

                 

                CODPOSTAL 6 CODPROV

                A un código postal le corresponde un único código de provincia.

                a.2) Aumentatividad

                Si X 6 Y y Z c W => XW 6 YZ

                Se demuestra del siguiente modo: Z c W equivale a W 6 Z. Si tenemos X 6 Y y W 6 Z     podemos afirmar que XW 6 YZ

                a.3) Transitividad

                Si X 6 Y y Y 6 Z => X 6 Z

                b) Propiedades propiamente dichas

                 

                b.1) Unión

                 

                X 6 Y y X 6 Z => X 6 YZ

                Demostración:

                Si X 6 Y (aumentatividad con X) => X 6 XY

                Si X 6 Z (aumentatividad con Y) => XY 6 XZ

                Si X 6 XY y XY 6 YZ (transitividad) => X 6 YZ

                b.2) Pseudotransitividad

                 

                X 6 Y y WY 6 Z => WX 6 Z

                Demostración:

                Si X 6 Y (aumentatividad con W) => WX 6 WY

                Si WX 6 WY y WY 6 Z (transitividad) => WX 6 Z

                b.3) Descomposición

                 

                X 6 Y y Z c Y => X 6 Z

                Demostración:

                Si  Z c Y (axioma reflexivo) => Y 6 Z

                Si X 6 Y y Y 6 Z (transitividad) => X 6 Z

                Dependencia funcional total

                 

                El conjunto de atributos Y tiene dependencia funcional total con X si Y tiene dependencia funcional con X (X 6 Y) y además no existe ningún subconjunto Z de X (Z c X) con el cual Y tenga dependencia funcional (Z ─/6 Y).

                Diagramas de dependencias funcionales

                 

                Son una herramienta que sirve para tener una visión general de los datos y de las dependencias funcionales entre ellos. S e representa en forma de grafo con los implicantes de las dependencias funcionales en un rectángulo, de los que salen flechas hacia los implicados.

                Ej: Dado:         ABC 6 MNS

                M 6 N

                BC 6 OPR

                O 6 P

                C 6 Q               , obtener el diagrama de dependencias funcionales.

                 

                A          C          AB 6 C

                 

                B                     B 6 C

                 

                es una dependencia funcional total.

                Ej: Hallar si las siguientes dependencias funcionales son totales:

                 

                a) DNI, EMPRESA 6 SUELDO

                 

                b) DNI, EMPRESA 6 NOMBRE

                 

                 

                SUPERCLAVE Y CLAVE

                 

                Superclave (SK): es el atributo o conjunto de atributos tales que en una relación R(A, DF) se cumple que SK 6 A. SK es el implicante capaz de implicar a la tabla completa.

                Ej: En una tabla compuesta por X, Y, Z y W, si W es superclave se cumple que:

                 

                W 6 X

                W 6 Y

                W 6 Z

                 

                Clave (K): es el atributo o conjunto de atributos tales que en una relación R(A, DF) es superclave y además no existe ningún subconjunto K’ c K tal que K’ 6 A

                K, R(A, DF), K ═ SK /\ no existe K’ c K / K’ 6 A

                Debe tener una dependencia funcional total con los atributos de la tabla. Si tenemos:

                WZ 6 A

                WZ 6 B

                WZ 6 C

                pero

                Z ─/6 A

                Z ─/6 B

                Z ─/6 C

                la clave es la mínima superclave, no descomponible en claves menores.

                20 forma normal: se dice que una relación está en 20 forma normal si cumple las siguientes condiciones:

                1) Está en 10 forma normal

                2) Cada atributo no principal o secundario (no forma parte de la clave), tiene una dependencia funcional total con la clave.

                Ej: AB 6 C

                     B 6 D

                 

                A          C

                 

                B          D

                 

                D depende funcionalmente de B sólo y debería depender de AB para estar en 20 forma normal. Para convertirlo en 20 forma normal se descompone en 2 tablas:

                Tabla 1: clave con dependencias totales

                Tabla 2: parte de la clave implicante con dependencias parciales

                A

                C         B          D

                B

                 

                Esto cumple el teorema 11 enunciado en el capítulo (Descomposición por JOIN)

                Ej: Pasar a 20 forma normal la siguiente tabla:

                 

                DNI                              NOMBRE

                 

                EMPLEADO                  SUELDO

                 

                DNI

                SUELDO                     DNI                  NOMBRE

                EMPLEADO

                Dependencia funcional transitiva: se cumple si:

                A   6   B

                B ─/6 A            =>        A 6 C

                B   6   C

                Gráficamente:

                30 forma normal: se dice que una tabla está en 30 forma normal si está en 20 forma normal y además cumple que ningún atributo no principal depende transitivamente de la clave.

                Ej: Pasar a 30 forma normal:

                A 6 B               B 6 A               C 6 D

                A 6 C               B 6 C               C 6 E

                A 6 D               B 6 D

                A 6 E               B 6 E

                A y B son claves candidatas a principales. Elegimos una de las 2, por ejemplo A.

                B

                A          C

                D

                E

                B

                A                     D

                C

                E

                no está en 30 forma normal porque existen atributos no principales que dependen transitivamente de la clave de la relación.

                Para pasarlo a 30 forma normal lo descompongo en 2 tablas:

                10 tabla: clave con dependencias no transitivas.

                B

                A

                C

                20 tabla: clave con dependencias transitivas.

                D

                C

                E

                Ej: Tenemos las siguientes dependencias de la tabla ALUMNOS:

                 

                N1MATRICULA 6 AULA, GRUPO

                GRUPO 6 AULA

                 

                Pasarlo a 30 forma normal.

                Descomponemos en 2 tablas:

                10 tabla: clave con dependencias no transitivas.

                B

                A

                C

                20 tabla: dependencias transitivas.

                D

                C

                E

                Ej: Pasar a 30 forma normal las siguientes dependencias de la tabla alumnos:

                 

                N1MATRICULA 6 AULA, GRUPO

                GRUPO 6 AULA

                Forma normal de Boyce-Codd: Trata de resolver los problemas que origina la 30 forma normal. Se dice que una relación R está en FNBC sí y sólo si todo determinante o todo implicante (conjunto de atributos a la izquierda de la relación) es clave.

                Ej:

                 

                A

                C

                B

                 

                Está en 30 forma normal, pero no en FNBC.

                AB 6 C

                C 6 B

                AB 6 B

                Para pasar a FNBC una relación R en la cual existe una dependencia del tipo X 6 Y siendo X un atributo no principal y siendo Y un atributo principal, descomponemos R en 2 proyecciones:

                R1 formada por los atributos X e Y 6 R1=(X, Y)

                R2 formada por todos los atributos de R exceptuando Y 6 R2=(A – Y)

                Obtenemos:

                C         B

                A

                C

                Ej: Tenemos una tabla de un callejero:

                 

                CALLEJERO (DIRECCION, CIUDAD, C_POSTAL)

                 

                C_POSTAL 6 CIUDAD

                DIRECCION, CIUDAD 6 C_POSTAL

                 

                 

                C_POSTAL     CIUDAD

                DIRECCION

                C_POSTAL

                CIUDAD                                               DIRECCION

                C_POSTAL

                Dependencia multivaluada:

                 

                Sean A y B dos dubconjuntos distintos de atributos de una tabla T se dice que A tiene una dependencia multivaluada con B ó que A multidetermina a B ó que B depende multivaluadamente de A (A 66 B) ai para cada valor de A tenemos un conjunto, bien sea de valores de B que son independientes de los demás atributos, o la relación.

                1. A <= B

                2. Independientemente del resto de atributos de A

                Ej:

                Los profesores de una facultad mparten varias asignaturas y una asignatura es impartida por varios profesores.

                Una asignatura tiene varios textos y un texto puede utilizarse en varias asignaturas,        independientemente del profesor que las imparte.

                PROFESOR ASIGNATURA TEXTO
                ANA DIGITALES T1
                ANA DIGITALES T2
                LUIS DIGITALES T1
                LUIS DIGITALES T2
                LUIS COMUNICACIONES T2
                LUIS COMUNICACIONES T3

                Asignatura 66 Profesor

                a) Cada asignatura tiene definidos varios profesores

                b) Se cumple

                Asignatura 66 Texto

                a) Cada asignatura tiene asignado más de un texto

                b) Se cumple

                Siempre que se dé una dependencia X 66 Y tiene que darse una dependencia X 66 A – (X U Y).

                Para que se dé debe tener más de 2 atributos.

                Profesor 6/6 Texto

                a) Para cada profesor hay definidos más de un texto

                b) Depende de Asignatura

                Profesor 66 Asignatura

                a) Un profesor tiene asignadas varias asignaturas

                b) No se puede dar por una serie de teoremas matemáticos

                40 Forma Normal: Una tabla está en 40FN si está en 30FN y se cumple que las únicas dependencias multivaluadas existentes son las existentes con los atributos secundarios.

                Cuando no existen dependencias multivaluadas y la tabla está en 30FN para pasar a 40FN tendremos en cuenta el teorema de FAGIN: “Una tabla T con los atributos A, B, C se puede descomponer sin pérdida de información en 2 proyecciones: T1 con los atributos A y B y T2 con los atributos A y C, sólo si A multidetermina a B y C (A 66 B / C).

                Para pasar a 40FN si existe una dependencia multivaluada X 66 Y la dividimos en 2 tablas.

                1. R1 (x, y)

                2. R2 (A – y)

                R1 (x, y)

                R: x 66 y

                R2 (A – y)

                Ej:

                 

                Asignatura 66 Texto

                Asignatura 66 Profesor

                 

                 

                (asignatura,texto)        (asignatura, profesor)

                 

                R (Asignatura, Texto, Profesor)

                1. Asignatura 66 Texto, x

                2. Asignatura 66 Profesor, y

                R1 (Asignatura, Texto, x)

                R2 (Asignatura, Profesor, y)

                Dependencia de JOIN

                 

                Se dice que una relación T formada por los atributos A1, A2, …, An tiene una dependencia con sus proyecciones T1, T2, …, Tn si T=T1  T2  T3  …  Tn

                T A B C T1 A B T2 B C
                a1 b1 c1 a1 b1 b1 c1
                a2 b1 c1 a2 b1 b2 c1
                a1 b2 c1 a1 b2 b1 c2
                a1 b1 c2

                T<> T1  T2

                T1  T2 A B C
                a1 b1 c1
                a1 b1 c2
                a2 b1 c1
                a2 b1 c2
                a1 b2 c1

                La 40 fila (a2, b1, c2) es una tupla intrusa o espúrea.

                Hay otra proyección que hace que se cumpla:

                T3 A C
                a1 c1
                a2 c1
                a1 c2

                T=T1  T2  T3

                50 Forma Normal: Se dice que una tabla está en 50FN si está en 40FN y además toda dependencia de JOIN está implicada por las claves de la tabla. Las columnas de enlace deben ser los atributos que componen la clave.

                En la siguiente tabla no existen dependencias funcionales. Bebida ─/6 Camarero.

                T Clientes Bebidas Camarero
                Lopez Cerveza Juan
                Lopez Fanta Luis
                Garcia Fanta Juan
                Perez Cerveza Juan

                Dependencias de JOIN:

                T=T1   T2   T3

                Columna enlace: T1   T2 = Cliente

                Los tres atributos forman la clave. T no está en 50 forma normal. Si las columnas de enlace son las columnas de la clave entonces está en 50 forma normal. Para pasarlo a 50 forma normal habrá que descomponer T en sus proyecciones.

                EMPLEADOS (DNI, NOMBRE, DIRECCION, NSS, FISS, CATEGORIA)

                T1 (DNI, NOMBRE, DIRECCION)

                T2 (DNI, NSS, FISS)

                T3 (DNI, CATEGORIA)

                EMPLEADO = T1   t2   t3

                T está en 50 forma normal, aún así es factible descomponerla en sus proyecciones.

                Ej: Competiciones

                 

                En una prueba hay varios árbitros.

                Un árbitro puede arbitrar en diferentes pruebas.

                Un atleta puede competir en diferentes pruebas.

                En un único país no pueden existir n1 de pasaportes iguales, pero sí si son de países diferentes.

                Pueden existir distintos atletas con el mismo nombre.

                 

                Atributos:

                 

                NPa:    N1 de pasaporte del atleta

                Na:       Nombre del atleta

                da:       Dirección del atleta

                dpa:     Dirección postal del atleta

                ca:       Ciudad del atleta

                Pra:     Provincia del atleta

                Pa:       País del atleta

                cpa:     Código de país del atleta

                Fn:       Fecha de nacimiento del atleta

                Sa:       Sexo del atleta

                cp:       Código de la prueba

                np:       Nombre de la prueba

                ma:      Marca del atleta

                NPar:   N1 de pasaporte del árbitro

                Nar:     Nombre del árbitro

                Ej: Barco de pasajeros

                 

                Un barco pertenece a un propietario.

                Se construye en un sólo astillero.

                El código es único dentro del país.

                Pueden existir barcos diferentes con el mismo código si son de distintos países.

                Un barco es de un sólo tipo.

                Los marineros del barco sólo tienen una dirección y un código dentro del barco.

                Un marinero puede tener varios teléfonos.

                No existen ciudades repetidas.

                Dos pasajeros pueden tener el mismo camarote en un barco.

                Un pasajero puede embarcar en varios barcos en distintas fechas.

                Dos barcos pueden haber tenido a un mismo marinero, pero nunca al mismo tiempo.


                Ej: Coleccionistas de sellos

                 

                El código de sello particular es único para la colección del propietario.

                El código internacional es único para cada tipo de sello emitido, del cual pueden existir varios ejemplares.

                Dos coleccionistas pueden tener ejemplares de un mismo tipo de sello, pero cada uno tendrá un código particular del sello que podría ser el mismo.

                No existen 2 DNIs, ciudades, provincias o países iguales.

                Pueden existir nombres iguales.

                Un propietario pude tener varios teléfonos.

                 

                Atributos:

                 

                CS:      Código particular del sello para el propietario actual

                CI:        Código internacional del sello

                PS:      Pais del sello

                CP:      Código del país

                VE:       Valor de emisón del sello

                AE:       Año de emisión del sello

                EP:       Estado político del país en el año de emisión del sello

                CE:       Código del estado político

                CLS:    Clase de sello

                CCL:    Código de la clase de sello

                EC:       Estado de conservación del sello

                CC:      Código del estado de conservación del sello

                DNIP:   DNI del propietario del sello

                NP:      Nombre del propietario del sello

                DP:      Dirección del propietario del sello

                CiP:     Ciudad del propietario del sello

                CPP:    Código postal del propietario del sello

                PP:      Provincia del propietario del sello

                PaP:    País del propietario del sello

                TP:       Teléfono del propietario del sello

                DNIA:   DNI del antiguo propietario del sello

                SQL Embedido

                 

                1. SQL autocontenido

                2. SQL embedido

                SQL embedido:

                 

                Sentencia de SQL que se utiliza dentro de un programa llamado anfitrión, escrito en cualquier lenguaje.

                Tendremos tablas con datos de entrada y de salida.

                Las sentencias de SQL serán sentencias embedidas en el programa anfitrión.

                Características:

                * Todas las sentencias SQL van a estar enmarcadas por:

                EXEC SQL

                <sentencias>

                END-EXEC

                * Antes de utilizar un compilador para manejar SQL embedido es necesario pasar el programa fuente por un precompilador:

                 

                PROG              precompilador                        PROG FUENTE compilador      PROG      LINKER

                FUENTE                                              MODIFICADO                                      OBJETO

                ├6 sintaxis sentencias SQL

                ├6 comentar sentencias SQL (para que las ignore el compilador)

                └6 sustituir sentencias SQL por llamadas a rutinas de librerías

                 

                * Manejo de variables de programa dentro de sentencias SQL:

                Se pone dos puntos (:) delante del nombre de la variable.

                Ej: Para un vuelo, visualizar plazas libres y n1 de vuelo (datos de salida), sabiendo el origen, destino, hora de salida y fecha de salida (datos de entrada).

                 

                INICIO

                escribir ‘Introducir ORIGEN, DESTINO, FECHA, HORA’

                leer ORIGEN, DESTINO, FECHA, HORA

                EXEC SQL

                SELECT RESERVAS.NUM_VUELO, PLAZAS_LIBRES

                INTO :VUELO, :P LAZAS

                FROM RESERVAS, VUELOS

                WHERE RESERVAS.NUM_VUELO=VUELOS.NUM_VUELO

                AND ORIGEN=:ORIGEN

                AND DESTINO=:DESTINO

                AND FECHA=:FECHA

                AND HORA=:HORA

                END-EXEC

                escribir VUELO, PLAZAS

                FIN

                 

                INTO sirve para determinar las variables de salida.

                Devuelve en la salida 1 sóla fila. En caso de que la salida devuelva más de una fila tendremos los cursores.

                Cursor:

                Es una estructura de datos tabular (en forma de tabla) que sirve para almacenar un número          indeterminado de filas.

                Sólo permite manejar una única fila a la vez, mediante un puntero.

                Para manejar cursores en SQL embedido hay 4 etapas:

                1) Definición del cursor

                 

                Se define junto a la declaración de variables del programa anfitrión:

                EXEC SQL

                DECLARE

                <nom_cursor> CURSOR

                FOR

                <sent_select>

                END-EXEC

                2) Abrir cursor

                 

                Rellena de filas la estructura del cursor:

                EXEC SQL

                OPEN <nom_cursor>

                END-EXEC

                3) Recuperar filas (1)

                 

                EXEC SQL

                FETCH <nom_cursor> {avanza el puntero una posición}

                INTO <lista_variables> {una sóla variable por columna}

                END-EXEC

                Cuando se hace un OPEN se rellena la estructura y el puntero se posiciona anterior a la primera fila. El primer FETCH posiciona el cursor en la primera fila.

                4) Cerrar cursor

                 

                EXEC SQL

                CLOSE <nom_cursor> {liberando memoria}

                END-EXEC

                Ej: Realizar un programa que presente todos aquellos vuelos existentes entre un origen y un destino determinados.

                 

                Pasos:

                 

                1. Declaración de variables

                2. Petición de información de origen y destino

                3. Abrir cursor

                4. Recuperar primera fila del cursor

                5. Comprobar si existe alguna fila. Si no existe ir a 9

                6. Escribir fila en pantalla

                7. Recuperar siguiente fila

                8. Ir a 5

                9. Fin

                 

                 

                 

                 


                VARIABLES

                ORIGEN, DESTINO, HORA, VUELO: STRING

                EXEC SQL

                DECLARE lista_vuelos CURSOR

                FOR SELECT NUM_VUELO, HORA_SALIDA

                FROM VUELOS

                WHERE ORIGEN=:ORIGEN

                AND DESTINO=:DESTINO

                END-EXEC

                INICIO

                ESCRIBIR ‘INTRODUZCA ORIGEN Y DESTINO’

                LEER ORIGEN, DESTINO

                EXEC SQL

                OPEN lista_vuelos

                END-EXEC

                EXEC SQL

                FETCH lista_vuelos

                INTO :VUELO, :HORA

                END-EXEC

                {Variable predefinida SQL-CODE: nos da información sobre la ejecución de cada sentencia en SQL:

                SQL-CODE < 0 6 Error

                SQL-CODE = 100 6 CURSOR vacío

                SQL-CODE > 0 6 Warning}

                MIENTRAS SQL-CODE <> 100

                ESCRIBIR VUELO, HORA

                EXEC SQL

                FETCH lista_vuelos

                INTO :VUELO, :HORA

                END-EXEC

                FIN_MIENTRAS

                EXEC SQL

                CLOSE lista_vuelos

                END-EXEC

                FIN

                INTEGRIDAD E INTEGRIDAD REFERENCIAL

                 

                1. Introducción

                2. Integridad referencial

                2.1 DDL

                2.2 DML

                3. Disparadores

                4. Reglas semánticas

                 

                1. INTRODUCCION

                 

                Integridad: característica que nos permite tener coherencia y veracidad en la información.

                Existen ciertas operaciones de SQL que pueden hacer peligrar la integridad de la operación:

                - Inserción

                - Borrado

                - Modificación

                T.PADRE          EMPLEADO     DNI       Clave Principal

                depende

                cod_fam

                T.HIJA              FAMILIARES    DNI_EMP         Clave Ajena

                Puede que borremos un empleado y olvidemos eliminar los familiares.

                Tipos de integridad:

                 

                Integridad de dominio: restringimos los valores que puede tomar un atributo respecto a su dominio, por ejemplo EDAD 6 18 – 65.

                Integridad de entidad: la clave primaria de una entidad no puede tener valores nulos y siempre deberá ser única, por ejemplo DNI.

                Integridad referencial: las claves ajenas de una tabla hija se tienen que corresponder con la clave primaria de la tabla padre con la que se relaciona. Por ejemplo, en familiares necesitaremos el DNI de empleado, que es la clave ajena de la tabla.

                2. INTEGRIDAD REFERENCIAL

                 

                Clave principal: conjunto de atributos capaz de diferenciar cada tupla de la tabla.

                Clave ajena: Clave en la tabla padre.

                2.1 DDL

                 

                Se basa en la definición de la clave principal y de la clave ajena de la tabla.

                CREATE TABLE EMPLEADO

                (PRIMARY KEY DNI,

                DNI CHAR(8) NOT NULL,

                NOMBRE VARCHAR(30) NOT NULL,   {columnas de la tabla, clave incluida}

                .  .  .

                )

                CREATE TABLE FAMILIARES

                (PRIMARY KEY COD_FAMILIAR,

                COD_FAMILIAR CHAR(4) NOT NULL,

                NOM_FAMILIAR CHAR(30) NOT NULL,

                DNI_EMPL CHAR(8) NOT NULL,

                .  .  .

                )

                FOREIGN KEY REL_EMPL_FAM

                DNI_EMPL REFERENCE EMPLEADO

                RESTRICT

                ON DELETE     CASCADE

                SET NULL

                2.2 DML

                 

                Tendremos una serie de reglas para:

                2.2.1 Inserción

                2.2.2 Actualización

                 

                Implícitas o determinadas por el sistema.

                 

                2.2.3 Borrado

                Explícita o definida por el usuario de tres que proporciona el sistema.

                2.2.1 Regla de inserción

                 

                Se ejecuta sobre la tabla hija.

                Sólo se podrá insertar una fila en la tabla hija si el valor de clave ajena de esa fila es un valor nulo o un valor de los existentes en la clave primaria de la tabla padre.

                2.2.2 Regla de actualización

                 

                Se ejecuta sobre las tablas padre o hija.

                Tabla padre: no se puede modificar el valor de clave primaria de la tabla padre si existe alguna fila en la tabla hija que lo referencie en la clave ajena.

                Tabla hija: el valor de clave ajena de la tabla hija sólo se puede modificar si el nuevo valor que va a adoptar es un valor nulo o es igual a un valor de clave primaria de la tabla padre.

                2.2.3 Regla de borrado

                 

                Esta regla se especifica al crear la tabla padre y se activará cuando se intente eliminar una fila de la tabla.

                CREATE TABLE

                . . .

                FOREIGN KEY

                . . .

                                     RESTRICT

                 

                ON DELETE    CASCADE

                 

                                     SET NULL

                Es una regla explícita, ya que el usuario puede elegir la regla de borrado que desee, a partir de 3 reglas que nos va a proporcionar el sistema.

                a) RESTRICT

                No se puede borrar una fila de la tabla padre si existen filas en la tabla hija cuyos valores de clave ajena sean iguales a valores de clave primaria de la fila que queremos borrar.

                b) CASCADE

                 

                Cada vez que se borre una fila de la tabla padre se eliminarán aquellas filas de la tabla hija que tengan como valor de clave ajena el mismo valor que el de la clave primaria de la fila que se quiere borrar.

                c) SET NULL

                 

                Cada vez que se elimine una fila de la tabla padre se actualizarán los campos de la clave ajena de la tabla hija a valores nulos para aquellas filas que tengan en el campo de la clave ajena el mismo valor que la clave primaria de la fila de la tabla padre que se quiere borrar.

                Casos específicos de integridad:

                 

                Integridad auto-referencial

                Una tabla es al mismo tiempo padre e hija de si misma.

                Ej: Dada la tabla T_EMP con las siguientes columnas:

                 

                COD_EMP        NOMBRE          COD_EMP_JEFE

                 

                Todos los valores que aparezcan en la última columna existirán también en la primera, por tanto COD_EMP_JEFE se convierte en clave ajena de la tabla.

                Ciclos de integridad referencial

                Conjunto de tablas que funcionará como tablas padres e hijas unas de otras, formando un camino cerrado.

                Ej: Tenemos 3 tablas:

                 

                COCHES (MATRICULA, MARCA, COD_DIRECTOR)

                 

                DIRECTORES (COD_DIRECTOR, NOMBRE_DIR, COD_SECRETARIA)

                 

                SECRETARIAS(COD_SECRETARIA, NOMBRE_SEC, MATRICULA)

                 

                COCHES es tabla hija de DIRECTORES

                DIRECTORES es tabla hija de SECRETARIAS

                SECRETARIAS es tabla hija de COCHES

                COCHES

                DIRECTORES                          SECRETARIAS

                Forma un ciclo de integridad referencial.

                Por haber ciclos debemos incorporar restricciones de borrado, para un correcto funcionamiento de la integridad referencial.

                Restricciones de borrado

                 

                a) Integridad auto-referencial

                 

                Siempre que tengamos una tabla con integridad auto-referencial debemos definir un borrado en cascada.

                Tabla EMPLEADO

                 

                COD_EMP

                COD_EMP_JEFE
                A00 -
                B01 A00
                C02 B01
                D03 C02

                DELETE FROM EMPLEADO

                WHERE COD_EMP_JEFE >= ‘B01′

                Con RESTRICT no se puede borrar la 30 fila, porque C02, clave primaria, está siendo referenciada en la fila 40 como clave ajena. Salta a la 4 fila y la borra.

                DELETE FROM EMPLEADO

                WHERE COD_EMP_JEFE IS NULL

                 

                Con SET NULL borraría la primera fila, porque su clave ajena es NULL,  pasaría a la 20 y como su clave ajena es igual a la clave primaria de la fila borrada pondría la clave ajena a NULL. Como le hemos dicho que borre aquellas filas que tengan por clave ajena un valor nulo, ahora borraría esta fila y así sucesivamente hasta eliminar la tabla completa.

                b) Ciclos de integridad referencial

                DELETE FROM T3

                WHERE FKT2 IS NULL

                 

                Restricciones de borrado en ciclos:

                 

                CICLO DE 2 TABLAS: ninguna de ellas podrá tener definida la opción de borrado en cascada.

                CICLO DE MAS DE 2 TABLAS: Sólo una de las tablas podrá tener la opción de borrado en cascada.

                El esquema anterior no cumple las restricciones expuestas.

                c) Tablas conectadas por múltiples caminos (con más de una tabla padre)

                 

                 

                 

                 

                 

                 

                 

                 

                 

                 

                 

                 

                 

                 

                 

                 

                 

                DELETE FROM T2

                WHERE PKT2 = ‘T2A’

                 

                Restricciones de borrado en tablas conectadas por múltiples caminos:

                 

                Los dos caminos que llegan a una tablas deben tener definida siempre la misma regla de borrado, que debe ser RESTRICT o CASCADE.

                En el ejemplo funcionaría bien por el primer camino, pero por el segundo hay restrinción.

                Ej. propuesto: Pruébese que ocurriría si entre T3 y T1 hay SET NULL y entre T2 y T1 hay SET NULL.

                 

                Ventajas del uso de las reglas de integridad referencial proporcionadas por el sistema frente a la implementación de estas por parte del usuario.

                 

                1. Al usar las proporcionadas por el sistema los analistas tienen menor responsabilidad.

                2. Habrá un incremento en la productividad y un decremento en los costes de la aplicación, porque el número de pruebas a realizar será menor si la integridad referencial es implementada por el propio sistema, el cual nos garantizará el correcto funcionamiento de las reglas de integridad referencial. El tiempo de desarrollo de la aplicación será menor.

                3. La información será más coherente, ya que está garantizado que todas las filas de las tablas cumplen las normas de integridad referencial.

                3. DISPARADORES O ‘TRIGGERS’

                 

                Son un conjunto de sentencias que el sistema ejecuta a la hoara de efectuar una inserción, actualización o borrado en una tabla. Para definir un TRIGGER necesitamos:

                - Nombre de la tabla sobre la que actuará el trigger

                - Sentencia que activará el trigger (inserción, actualización o borrado)

                - Acciones que realizará el trigger

                DB2 no incorpora la posibilidad de definir triggers, pero SQL sí. Las reglas de integridad referencial se emulan en SQL mediante la implementación de triggers.

                 

                 

                 

                 


                Emulación de inserción en tabla hija mediante el uso de trigger en SQL:

                 

                Según la regla deintegridad referencial, no se puede efectuar una inserción en una tabla hija a menos que exista un valor de clave primaria igual al de clave ajena de la fila que se desea insertar.

                Definición del trigger:

                DEFINE TRIGGER insercion

                ON INSERT OF HIJA

                 

                El trigger comparará el valor de clave ajena de la fila a insertar con el valor de clave primaria de la tabla hija cada vez que se intente insertar una fila.

                Implementación del trigger:

                (if (SELECT COUNT (#)

                FROM PADRE, INSERTED

                WHERE PADRE.CL_PRIMARIA = INSERTED.CL_AJENA) = 0)      Condición que activa el trigger

                begin

                PRINT ‘ERROR’

                ROLLBACK TRANSACTION        Acciones que ejecuta el trigger si se activa

                end

                 

                SELECT COUNT  devuelve el número de filas de la tabla padre con un valor de clave primaria igual a la clave ajena de la fila a insertar.

                El sistema proporciona una tabla de almacenamiento temporal (INSERTED) donde se guardarán las filas a insertar.

                Si CL_AJENA = CL_PRIMARIA, permite la inserción, en caso contrario deshace la operación con ROLLBACK TRANSACTION.

                El trigger correspondiente a la operación de actualización sería similar.

                Escrito en SQL. Etiquetas: . Deja un Comentario »

                Algoritmo Huffman de compresion y descompresion

                Attribute VB_Name = “HuffmanCoding”
                ‘################################################################
                ‘ Huffman Coding Compression / Decompression Algorithm
                ‘ Created 1 August 2000 by James Vincent Carnicelli

                ‘ NOTES

                ‘ The Huffman algorithm, named after its inventor, was created
                ‘ around about 1952.  It’s the method used by most commercial
                ‘ compression utilities, like PKZIP, and even by the JPEG image
                ‘ file format.  It’s generally thought to offer an average of
                ‘ 50% compression, given a typical mix of text and binary data.
                ‘ For long strings that contain lots of repeating characters or
                ‘ only a handful of different characters, the compression ratio
                ‘ could get as high as 80%.  While efficient, this algorithm is
                ‘ not guaranteed to result in a compressed string that is
                ‘ smaller than the original source.

                ‘ This is a less-than-optimal implementation of this compression
                ‘ algorithm.  It’s very simple to use in your programs (even if
                ‘ it is difficult to understand how it works).  You need only
                ‘ call:

                ‘         Compressed = HuffmanEncode(SourceText, [Force])

                ‘ passing in the text you want compressed.  If the compressed
                ‘ version is actually larger than the original source, this
                ‘ algorithm spits out a special string that contains a four-
                ‘ byte header and the original source string, so the resulting
                ‘ string will always be at most four bytes larger than the
                ‘ source string.  If you pass in True for Force, the result will
                ‘ always be huffman-encoded, bypassing this neat optimization.
                ‘ Be aware that the output is binary data, so it might not work
                ‘ nicely with some things like text boxes, certain Windows
                ‘ API calls, and some SQL and database field formats.

                ‘ To decode a string encoded with HuffmanEncode, simply call
                ‘ the following:

                ‘         UncompressedText = HuffmanDecode(Compressed)

                ‘ One cool application of this algorithm is encryption.  Because
                ‘ Huffman coding relies on variable-bit-length character
                ‘ representations, it’s next to impossible to decrypt a string
                ‘ compressed with this algorithm without recognizing the
                ‘ lookup tables in the header as the key to decrypting it.  You
                ‘ could even strip out this lookup table and keep it as a
                ‘ private key to be shared only with those you want.  Without
                ‘ the lookup table, even someone equiped with this very code
                ‘ would not likely be able to decrypt the string.

                ‘ One last thing.  While I’ve tested this algorithm with plain
                ‘ text strings and even some binary files, I don’t know how
                ‘ much data you can cram into the compression engine before it
                ‘ breaks.  With luck, it’s something like 2GB.  In that case,
                ‘ though, this would be pretty slow.  Also, I have not proven
                ‘ beyond a doubt that this won’t choke on some data, so I would
                ‘ encourage you to do so to your satisfaction before putting
                ‘ this into full production.  Be sure to let me know if you find
                ‘ anything interesting.
                ‘################################################################

                Option Explicit

                Private Enum HuffmanTreeNodeParts
                htnWeight = 1
                htnIsLeaf = 2
                htnAsciiCode = 3
                htnBitCode = 4
                htnLeftSubtree = 5
                htnRightSubtree = 6
                End Enum

                ‘Compress the text.
                Public Function HuffmanEncode(Text As String, Optional Force As Boolean) As String
                Dim TextLen As Long, Char As Byte, i As Long, j As Long
                Dim CodeCounts(255) As Long, BitStrings(255), BitString
                Dim HuffmanTrees As Collection
                Dim HTRootNode As Collection, HTNode As Collection
                Dim NextByte As Byte, BitPos As Integer, Temp As String

                ‘Initialize for processing.
                TextLen = Len(Text)
                Set HuffmanTrees = New Collection

                ‘Is there anything to encode?
                If TextLen = 0 Then
                HuffmanEncode = “HE0″ & vbCr  ‘Version 0 = Plain text
                Exit Function  ‘No point in continuing
                End If

                HuffmanEncode = “HE2″ & vbCr  ‘Version 1

                ‘Count how many times each ASCII code is encountered in text.
                For i = 1 To TextLen
                Char = Asc(Mid(Text, i, 1))
                CodeCounts(Char) = CodeCounts(Char) + 1
                Next

                ‘Initialize the forest of Huffman trees; one for each ASCII
                ‘character used.
                For i = 0 To UBound(CodeCounts)
                If CodeCounts(i) > 0 Then
                Set HTNode = NewNode
                S HTNode, htnAsciiCode, Chr(i)
                S HTNode, htnWeight, CDbl(CodeCounts(i) / TextLen)
                S HTNode, htnIsLeaf, True

                ‘Now place it in its reverse-ordered position.
                For j = 1 To HuffmanTrees.Count + 1
                If j > HuffmanTrees.Count Then
                HuffmanTrees.Add HTNode
                Exit For
                End If
                If HTNode(htnWeight) >= HuffmanTrees(j)(htnWeight) Then
                HuffmanTrees.Add HTNode, , j
                Exit For
                End If
                Next
                End If
                Next

                ‘Now assemble all these single-level Huffman trees into
                ‘one single tree, where all the leaves have the ASCII codes
                ‘associated with them.
                If HuffmanTrees.Count = 1 Then
                Set HTNode = NewNode
                S HTNode, htnLeftSubtree, HuffmanTrees(1)
                S HTNode, htnWeight, 1
                HuffmanTrees.Remove (1)
                HuffmanTrees.Add HTNode
                End If
                While HuffmanTrees.Count > 1
                Set HTNode = NewNode
                S HTNode, htnRightSubtree, HuffmanTrees(HuffmanTrees.Count)
                HuffmanTrees.Remove HuffmanTrees.Count
                S HTNode, htnLeftSubtree, HuffmanTrees(HuffmanTrees.Count)
                HuffmanTrees.Remove HuffmanTrees.Count
                S HTNode, htnWeight, HTNode(htnLeftSubtree)(htnWeight) + HTNode(htnRightSubtree)(htnWeight)

                ‘Place this new tree it in its reverse-ordered position.
                For j = 1 To HuffmanTrees.Count + 1
                If j > HuffmanTrees.Count Then
                HuffmanTrees.Add HTNode
                Exit For
                End If
                If HTNode(htnWeight) >= HuffmanTrees(j)(htnWeight) Then
                HuffmanTrees.Add HTNode, , j
                Exit For
                End If
                Next
                Wend
                Set HTRootNode = HuffmanTrees(1)
                AttachBitCodes BitStrings, HTRootNode, Array()
                For i = 0 To UBound(BitStrings)
                If Not IsEmpty(BitStrings(i)) Then
                Set HTNode = BitStrings(i)
                Temp = Temp & HTNode(htnAsciiCode) _
                & BitsToString(HTNode(htnBitCode))
                End If
                Next
                HuffmanEncode = HuffmanEncode & Len(Temp) & vbCr & Temp

                ‘The next part of the header is a checksum value, which
                ‘we’ll use later to verify our decompression.
                Char = 0
                For i = 1 To TextLen
                Char = Char Xor Asc(Mid(Text, i, 1))
                Next
                HuffmanEncode = HuffmanEncode & Chr(Char)

                ‘The final part of the header identifies how many bytes
                ‘the original text strings contains.  We will probably
                ‘have a few unused bits in the last byte that we need to
                ‘account for.  Additionally, this serves as a final check
                ‘for corruption.
                HuffmanEncode = HuffmanEncode & TextLen & vbCr

                ‘Now we can encode the data by exchanging each ASCII byte for
                ‘its appropriate bit string.
                BitPos = -1
                Char = 0
                Temp = “”
                For i = 1 To TextLen
                BitString = BitStrings(Asc(Mid(Text, i, 1)))(htnBitCode)
                ‘Add each bit to the end of the output stream’s 1-byte buffer.
                For j = 0 To UBound(BitString)
                BitPos = BitPos + 1
                If BitString(j) = 1 Then
                Char = Char + 2 ^ BitPos
                End If
                ‘If the bit buffer is full, dump it to the output stream.
                If BitPos >= 7 Then
                Temp = Temp & Chr(Char)
                ‘If the temporary output buffer is full, dump it
                ‘to the final output stream.
                If Len(Temp) > 1024 Then
                HuffmanEncode = HuffmanEncode & Temp
                Temp = “”
                End If
                BitPos = -1
                Char = 0
                End If
                Next
                Next
                If BitPos > -1 Then
                Temp = Temp & Chr(Char)
                End If
                If Len(Temp) > 0 Then
                HuffmanEncode = HuffmanEncode & Temp
                End If

                ‘If it takes up more space compressed because the source is
                ‘small and the header is big, we’ll leave it uncompressed
                ‘and prepend it with a 4 byte header.
                If Len(HuffmanEncode) > TextLen And Not Force Then
                HuffmanEncode = “HE0″ & vbCr & Text
                End If
                End Function

                ‘Decompress the string back into its original text.
                Public Function HuffmanDecode(ByVal Text As String) As String
                Dim Pos As Long, Temp As String, Char As Byte, Bits
                Dim i As Long, j As Long, CharsFound As Long, BitPos As Integer
                Dim CheckSum As Byte, SourceLen As Long, TextLen As Long
                Dim HTRootNode As Collection, HTNode As Collection

                ‘If this was left uncompressed, this will be easy.
                If Left(Text, 4) = “HE0″ & vbCr Then
                HuffmanDecode = Mid(Text, 5)
                Exit Function
                End If

                ‘If this is any version other than 2, we’ll bow out.
                If Left(Text, 4) <> “HE2″ & vbCr Then
                Err.Raise vbObjectError, “HuffmanDecode()”, _
                “The data either was not compressed with HE2 or is corrupt”
                End If
                Text = Mid(Text, 5)

                ‘Extract the ASCII character bit-code table’s byte length.
                Pos = InStr(1, Text, vbCr)
                If Pos = 0 Then
                Err.Raise vbObjectError, “HuffmanDecode()”, _
                “The data either was not compressed with HE2 or is corrupt”
                End If
                On Error Resume Next
                TextLen = Left(Text, Pos – 1)
                If Err.Number <> 0 Then
                On Error GoTo 0
                Err.Raise vbObjectError, “HuffmanDecode()”, _
                “The header is corrupt”
                End If
                On Error GoTo 0
                Text = Mid(Text, Pos + 1)
                Temp = Left(Text, TextLen)
                Text = Mid(Text, TextLen + 1)
                ‘Now extract the ASCII character bit-code table.
                Set HTRootNode = NewNode
                Pos = 1
                While Pos <= Len(Temp)
                Char = Asc(Mid(Temp, Pos, 1))
                Pos = Pos + 1
                Bits = StringToBits(Pos, Temp)
                Set HTNode = HTRootNode
                For j = 0 To UBound(Bits)
                If Bits(j) = 1 Then
                If HTNode(htnLeftSubtree) Is Nothing Then
                S HTNode, htnLeftSubtree, NewNode
                End If
                Set HTNode = HTNode(htnLeftSubtree)
                Else
                If HTNode(htnRightSubtree) Is Nothing Then
                S HTNode, htnRightSubtree, NewNode
                End If
                Set HTNode = HTNode(htnRightSubtree)
                End If
                Next
                S HTNode, htnIsLeaf, True
                S HTNode, htnAsciiCode, Chr(Char)
                S HTNode, htnBitCode, Bits
                Wend

                ‘Extract the checksum.
                CheckSum = Asc(Left(Text, 1))
                Text = Mid(Text, 2)

                ‘Extract the length of the original string.
                Pos = InStr(1, Text, vbCr)
                If Pos = 0 Then
                Err.Raise vbObjectError, “HuffmanDecode()”, _
                “The header is corrupt”
                End If
                On Error Resume Next
                SourceLen = Left(Text, Pos – 1)
                If Err.Number <> 0 Then
                On Error GoTo 0
                Err.Raise vbObjectError, “HuffmanDecode()”, _
                “The header is corrupt”
                End If
                On Error GoTo 0
                Text = Mid(Text, Pos + 1)
                TextLen = Len(Text)

                ‘Now that we’ve processed the header, let’s decode the actual data.
                i = 1
                BitPos = -1
                Set HTNode = HTRootNode
                Temp = “”
                While CharsFound < SourceLen
                If BitPos = -1 Then
                If i > TextLen Then
                Err.Raise vbObjectError, “HuffmanDecode()”, _
                “Expecting more bytes in data stream”
                End If
                Char = Asc(Mid(Text, i, 1))
                i = i + 1
                End If
                BitPos = BitPos + 1

                If (Char And 2 ^ BitPos) > 0 Then
                Set HTNode = HTNode(htnLeftSubtree)
                Else
                Set HTNode = HTNode(htnRightSubtree)
                End If
                If HTNode Is Nothing Then
                ‘Uh oh.  We’ve followed the tree to a Huffman tree to a dead
                ‘end, which won’t happen unless the data is corrupt.
                Err.Raise vbObjectError, “HuffmanDecode()”, _
                “The header (lookup table) is corrupt”
                End If

                If HTNode(htnIsLeaf) Then
                Temp = Temp & HTNode(htnAsciiCode)
                If Len(Temp) > 1024 Then
                HuffmanDecode = HuffmanDecode & Temp
                Temp = “”
                End If
                CharsFound = CharsFound + 1
                Set HTNode = HTRootNode
                End If

                If BitPos >= 7 Then BitPos = -1
                Wend
                If Len(Temp) > 0 Then
                HuffmanDecode = HuffmanDecode & Temp
                End If
                If i <= TextLen Then
                Err.Raise vbObjectError, “HuffmanDecode()”, _
                “Found extra bytes at end of data stream”
                End If

                ‘Verify data to check for corruption.
                If Len(HuffmanDecode) <> SourceLen Then
                Err.Raise vbObjectError, “HuffmanDecode()”, _
                “Data corrupt because check sums do not match”
                End If
                Char = 0
                For i = 1 To SourceLen
                Char = Char Xor Asc(Mid(HuffmanDecode, i, 1))
                Next
                If Char <> CheckSum Then
                Err.Raise vbObjectError, “HuffmanDecode()”, _
                “Data corrupt because check sums do not match”
                End If
                End Function

                ‘—————————————————————-
                ‘ Everything below here is only for supporting the two main
                ‘ routines above.
                ‘—————————————————————-

                ‘Follows the tree, now built, to its end leaf nodes, where the
                ‘character codes are, in order to tell those character codes
                ‘what their bit string representations are.
                Private Sub AttachBitCodes(BitStrings, HTNode As Collection, ByVal Bits)
                If HTNode Is Nothing Then Exit Sub
                If HTNode(htnIsLeaf) Then
                S HTNode, htnBitCode, Bits
                Set BitStrings(Asc(HTNode(htnAsciiCode))) = HTNode
                Else
                ReDim Preserve Bits(UBound(Bits) + 1)
                Bits(UBound(Bits)) = 1
                AttachBitCodes BitStrings, HTNode(htnLeftSubtree), Bits
                Bits(UBound(Bits)) = 0
                AttachBitCodes BitStrings, HTNode(htnRightSubtree), Bits
                End If
                End Sub

                ‘Turns a string of ’0′ and ’1′ characters into a string of bytes
                ‘containing the bits, preceeded by 1 byte indicating the
                ‘number of bits represented.
                Private Function BitsToString(Bits) As String
                Dim Char As Byte, i As Long
                BitsToString = Chr(UBound(Bits) + 1)  ‘Number of bits
                For i = 0 To UBound(Bits)
                If i Mod 8 = 0 Then
                If i > 0 Then BitsToString = BitsToString & Chr(Char)
                Char = 0
                End If
                If Bits(i) = 1 Then  ‘Bit value = 1
                ‘Mask the bit into its proper position in the byte
                Char = Char + 2 ^ (i Mod 8)
                End If
                Next
                BitsToString = BitsToString & Chr(Char)
                End Function

                ‘The opposite of BitsToString() function.
                Private Function StringToBits(StartPos As Long, Bytes As String)
                Dim Char As Byte, i As Long, BitCount As Long, Bits
                Bits = Array()
                BitCount = Asc(Mid(Bytes, StartPos, 1))
                StartPos = StartPos + 1
                For i = 0 To BitCount – 1
                If i Mod 8 = 0 Then
                Char = Asc(Mid(Bytes, StartPos, 1))
                StartPos = StartPos + 1
                End If
                ReDim Preserve Bits(UBound(Bits) + 1)
                If (Char And 2 ^ (i Mod 8)) > 0 Then   ‘Bit value = 1
                Bits(UBound(Bits)) = 1
                Else  ‘Bit value = 0
                Bits(UBound(Bits)) = 0
                End If
                Next
                StringToBits = Bits
                End Function

                ‘Remove the specified item and put the specified value in its place.
                Private Sub S(Col As Collection, Index As HuffmanTreeNodeParts, Value)
                Col.Remove Index
                If Index > Col.Count Then
                Col.Add Value
                Else
                Col.Add Value, , Index
                End If
                End Sub

                ‘Creates a new Huffman tree node with the default values set.
                Private Function NewNode() As Collection
                Dim Node As New Collection
                Node.Add 0  ‘htnWeight
                Node.Add False  ‘htnIsLeaf
                Node.Add Chr(0)  ‘htnAsciiCode
                Node.Add “”  ‘htnBitCode
                Node.Add Nothing  ‘htnLeftSubtree
                Node.Add Nothing  ‘htnRightSubtree
                Set NewNode = Node
                End Function

                Rutinas de manipulación de bits

                KB de Microsoft. Article ID: Q185554

                Rutinas :

                - BitMask : devuelve una máscara empleada por las otras rutinas.
                - BitSet : Pone un bit a uno o a cero.
                - BitFlip : Cambia el estado de un bit.
                - BitTest : Devuelve el estado de un bit.
                - ArrayBitSet : Pone un bit a uno o a cero.
                - ArrayBitFlip : Cambia el estado de un bit.
                - ArrayBitTest : Devuelve el estado de un bit.

                Las rutinas Array asumen 32 bits por elemento, comenzando con los bits del 0 al 31 en el primero, del 32 al 63 en el segundo, etc. La matriz no necesita comenzar en el elemento cero.

                Estas rutinas tienen uno a más de los siguientes parámetros :
                - X : un long que contiene los 32 bits a manipular.
                - A() : una matriz conteniendo los bits a manipular.
                - Value : TRUE para poner un bit a uno y FALSE para ponerlo a cero.
                - N : número que indica el bit a manipular. En el caso de una variable long va desde el 0 al 31. En el caso de una matriz va desde el 0 hasta el (numero de elementos * 32) -1. Si el valor de N está fuera del rango se ignora. En el caso de una función se devuelve FALSE.

                Las funciones BitTest y ArrayBitTest devuelven TRUE (-1) si el bit está a 1 y FALSE (0) si está a 0.

                Codigo:
                
                Function BitMask(ByVal N As Long) As Long Dim I As Long, Mask As Long If N < 0 Or N > 31 Then BitMask = 0 ElseIf N = 31 Then BitMask = &H80000000 Else: Mask = 1 For I = 1 To N Mask = Mask + Mask Next I BitMask = Mask End If End Function Sub BitSet(X As Long, ByVal N As Long, ByVal Value As Boolean) If Value Then X = X Or BitMask(N) Else: X = X And Not BitMask(N) End If End Sub Sub BitFlip(X As Long, ByVal N As Long) X = X Xor BitMask(N) End Sub Function BitTest(X As Long, ByVal N As Long) As Boolean ` Return False if invalid N BitTest = (X And BitMask(N)) <> 0 End Function Sub ArrayBitSet(A() As Long, ByVal N As Long, ByVal Value As Boolean) Dim Element As Integer Element = N \ 32 + LBound(A) If Element <= UBound(A) And N >= 0 Then BitSet A(Element), N Mod 32, Value End If End Sub Sub ArrayBitFlip(A() As Long, ByVal N As Long) Dim Element As Integer Element = N \ 32 + LBound(A) If Element <= UBound(A) And N >= 0 Then BitFlip A(Element), N Mod 32 End If End Sub Function ArrayBitTest(A() As Long, ByVal N As Long) As Boolean ` Returns False if invalid N. Dim Element As Integer Element = N \ 32 + LBound(A) If Element <= UBound(A) And N >= 0 Then ArrayBitTest = BitTest(A(Element), N Mod 32) Else ArrayBitTest = False End If End Function

                Basándonos en estas rutinas de Microsoft es sencillo crearnos una para desplazar bits a la izquierda y a la derecha, similar a los operadores << y >> de C.

                Codigo:
                
                Function DesplazaLongIzda(ByVal numero As Long, pos As Long) As Long Dim i As Long For i = 31 To pos Step -1 BitSet numero, i, BitTest(numero, i - pos) Next i For i = pos - 1 To 0 Step -1 BitSet numero, i, False Next i DesplazaLongIzda = numero End Function Function DesplazaLongDcha(ByVal numero As Long, pos As Long) As Long Dim i As Long For i = 0 To 31 - pos BitSet numero, i, BitTest(numero, i + pos) Next i For i = 31 - pos To 31 BitSet numero, i, False Next i DesplazaLongDcha = numero End Function
                Escrito en C++, Visual Basic. Etiquetas: . Deja un Comentario »

                Ejemplo de Microsoft Transfer Control; Cliente FTP en VB

                Ejemplo que permite descargar y subir archivos a un Ftp utilizando el control Microsoft Transfer controlEl ejemplo es como muestra el gráfico:

                vista del formulario de ejemplo para bajar y subir archivos de un servidor ftp

                Para agregar el Control Inet, seleccionar desde la ficha componentes Microsoft Internet Transfer control

                vista de la ventana de componentes de visual basic para insertar al proyecto el control Inet

                También agregar los siguiente controles:

                • Un control commonDialog llamado CommonDialog1 para seleccionar la ruta del archivo a subir y también para descargar
                • Cinco controles TextBox: txt_servidor, txt_Usuario, txt_Pass, txt_local y txt_Remoto: Esto es para el nombre del servidor ftp, para el nombre de Usuario y contraseña del Login y para las rutas de los archivos.
                • Un control ListBox llamado List1 para ver el estado.
                • Cuatro CommandButton: El Command1 para Subir un fichero, Command2 para descargar, Command3 para seleccionar un fichero del disco a subir, y Command4 para seleccionar ruta y nombre del archivo a descargar a disco.

                Código fuente en un formulario:

                Option Explicit
                
                Private Sub Mostrar_Estado_FTP(ByVal estado As String)
                
                    List1.AddItem estado
                
                    List1.ListIndex = List1.ListCount - 1
                
                End Sub
                
                Private Sub Command2_Click()
                
                Dim El_Host As String
                
                If txt_Remoto = "" Then
                
                       MsgBox " No hay archivo para descargar", vbInformation
                
                      Exit Sub
                
                    End If
                
                List1.AddItem " ..Descargando "
                
                        'Asigna la Url, es decir el nombre del Host FTP
                
                        El_Host = "ftp://" & txt_servidor
                
                With Inet1
                
                .URL = El_Host
                
                'nombre de usuario y password de la cuanta FTP
                
                        .UserName = txt_Usuario
                
                        .Password = txt_Pass
                
                'DEscarga el archivo indicado con el comando Get
                
                        Call .Execute(, "Get " & txt_Remoto & " " & txt_local)
                
                        DoEvents
                
                End With
                
                End Sub
                
                Private Sub Command1_Click()
                
                Dim El_Host As String
                
                If txt_local = "" Then
                
                       MsgBox " No hay archivo pra subir", vbInformation
                
                      Exit Sub
                
                    End If
                
                List1.AddItem " ..Subiendo archivo "
                
                El_Host = "ftp://" & txt_servidor
                
                With Inet1
                
                'Asigna la Url, es decir el nombre del Host FTP
                
                        .URL = El_Host
                
                'nombre de usuario y password de la cuanta FTP
                
                        .UserName = txt_Usuario
                
                        .Password = txt_Pass
                
                'Escribe el fichero en el servidor con el comando Put
                
                        Call .Execute(, "Put " & txt_local & " " & txt_Remoto)
                
                DoEvents
                
                End With
                
                End Sub
                
                'botón para seleccionar fichero a subir al Ftp
                
                Private Sub Command3_Click()
                
                With CommonDialog1
                
                .DialogTitle = " Seleccione el archivo a subir"
                
                     .ShowOpen
                
                If .FileName = "" Then
                
                        txt_local = ""
                
                        Exit Sub
                
                     Else
                
                        txt_local = .FileName
                
                     End If
                
                End With
                
                End Sub
                
                'botón para seleccionar la ruta y nombre de archivo a descargar
                
                Private Sub Command4_Click()
                
                With CommonDialog1
                
                .DialogTitle = " Seleccione ruta y nombre del archivo a descargar"
                
                     .ShowSave
                
                If .FileName = "" Then
                
                        txt_Remoto = ""
                
                        Exit Sub
                
                     Else
                
                        txt_Remoto = .FileName
                
                     End If
                
                End With
                
                End Sub
                
                Private Sub Inet1_StateChanged(ByVal State As Integer)
                
                Select Case State
                
                'Dependiendo del valor recibido de State _
                
                         muestra en el List1 la información de estado
                
                Case 0: Mostrar_Estado_FTP " Nothing "
                
                        Case 1: Mostrar_Estado_FTP " Resolviendo Host "
                
                        Case 2: Mostrar_Estado_FTP " Host Resuelto "
                
                        Case 3: Mostrar_Estado_FTP " ..Conectando a: " & txt_servidor
                
                        Case 4: Mostrar_Estado_FTP ".. Conectado a " & txt_servidor
                
                        Case 5: Mostrar_Estado_FTP " Petición"
                
                        Case 6: Mostrar_Estado_FTP " ..enviando petición"
                
                        Case 7: Mostrar_Estado_FTP " Recibiendo Respuesta "
                
                        Case 8: Mostrar_Estado_FTP " Respuesta recibida "
                
                        Case 9: Mostrar_Estado_FTP " ..Desconectando "
                
                        Case 10: Mostrar_Estado_FTP " Estado : Desconectado"
                
                        Case 11: Mostrar_Estado_FTP " Error: " & Inet1.ResponseInfo
                
                        Case 12: Mostrar_Estado_FTP Inet1.ResponseInfo
                
                Case Else: Mostrar_Estado_FTP " Estado -> " & Format$(State)
                
                    End Select
                
                DoEvents
                
                End Sub
                
                Private Sub Form_Load()
                
                Command1.Caption = "Subir archivo "
                
                Command2.Caption = " Descargar archivo "
                
                Me.Caption = "Ejemplo del control Inet para descargar y subir ficheros"
                
                End Sub
                Escrito en Visual Basic. Etiquetas: . Deja un Comentario »

                Cliente Ftp con el Api wininet

                Este es un programa de ejemplo con el código fuente para acceder a un servidor vía Ftp y poder realizar operaciones básicas utilizando el api de windows


                Las opciones principales son:

                • Subir y bajar archivos
                • Eliminar y renombrar carpetas y archivos
                • Listar los mismos en un control ListView (no es nada del otro mundo)
                • algunas otras opciones

                El ejemplo utiliza un módulo de clase que tiene las funciones descritas arriba.

                Nota , El programa mientras lo hacía, no lo llegué a testear a fondo, solo lo probé con mi servidor Ftp y no me surgió errores, con lo cual no quiere decir que no los tenga, en todo caso si le encontrás alguno y querés avisarme, enviame un mail para ver si lo puedo ojear y poder corregirlo :)

                Imágen del ejemplo:

                vista del formulario con el control Listview que lista los archivos remotos del FTP y los demás comandos del programa

                Código fuente del módulo de clase:

                1. Private Const MAX_PATH = 260
                2. ‘Constante para el atributo de directorio
                3. Private Const FILE_ATTRIBUTE_DIRECTORY = &H10
                4. ‘— tipos de archivos — para el Upload y Download
                5. Private Const FTP_TRANSFER_TYPE_UNKNOWN = &H0
                6. Private Const FTP_TRANSFER_TYPE_ASCII = &H1
                7. Private Const FTP_TRANSFER_TYPE_BINARY = &H2
                8. ‘Puerto FTP
                9. Private Const INTERNET_DEFAULT_FTP_PORT = 21
                10. Private Const INTERNET_SERVICE_FTP = 1
                11. ‘ Modo de conexión FTP
                12. Private Const INTERNET_FLAG_PASSIVE = &H8000000
                13. Private Const PassiveConnection As Boolean = True
                14. ‘— formas de entrar en internet —
                15. ‘ usa config del registro
                16. Private Const INTERNET_OPEN_TYPE_PRECONFIG = 0
                17. ‘ directo a internet
                18. Private Const INTERNET_OPEN_TYPE_DIRECT = 1
                19. ‘ via proxy
                20. Private Const INTERNET_OPEN_TYPE_PROXY = 3
                21. Private Const INTERNET_OPEN_TYPE_PRECONFIG_WITH_NO_AUTOPROXY = 4
                22. ‘Type para atributos de fecha y hora de archivos
                23. Private Type FILETIME
                24. dwLowDateTime As Long
                25. dwHighDateTime As Long
                26. End Type
                27. ‘Otros atributos de archivo tamaño, nombre, fecha etc..
                28. Private Type WIN32_FIND_DATA
                29. dwFileAttributes As Long
                30. ftCreationTime As FILETIME
                31. ftLastAccessTime As FILETIME
                32. ftLastWriteTime As FILETIME
                33. nFileSizeHigh As Long
                34. nFileSizeLow As Long
                35. dwReserved0 As Long
                36. dwReserved1 As Long
                37. cFileName As String * MAX_PATH
                38. cAlternate As String * 14
                39. End Type
                40. ‘ Declaraciones Apis
                41. ‘***************************************************************
                42. Private Declare Function InternetCloseHandle _
                43. Lib “wininet.dll” (ByVal hInet As Long) As Integer
                44. ‘Establece una conexión a internet para poder iniciar sesión Ftp
                45. Private Declare Function InternetConnect _
                46. Lib “wininet.dll” Alias “InternetConnectA” _
                47. (ByVal hInternetSession As Long, _
                48. ByVal sServerName As String, _
                49. ByVal nServerPort As Integer, _
                50. ByVal sUserName As String, _
                51. ByVal sPassword As String, _
                52. ByVal lService As Long, ByVal lFlags As Long, _
                53. ByVal lContext As Long) As Long
                54. ‘función api que Conecta al Ftp
                55. Private Declare Function InternetOpen _
                56. Lib “wininet.dll” Alias “InternetOpenA” _
                57. (ByVal sAgent As String, ByVal lAccessType As Long, _
                58. ByVal sProxyName As String, _
                59. ByVal sProxyBypass As String, _
                60. ByVal lFlags As Long) As Long
                61. ‘Función que Establece el path corriente
                62. Private Declare Function FtpSetCurrentDirectory _
                63. Lib “wininet.dll” Alias “FtpSetCurrentDirectoryA” _
                64. (ByVal hFtpSession As Long, _
                65. ByVal lpszDirectory As String) As Boolean
                66. ‘api que Recupera el path donde estamos ubicados
                67. Private Declare Function FtpGetCurrentDirectory _
                68. Lib “wininet.dll” Alias “FtpGetCurrentDirectoryA” _
                69. (ByVal hFtpSession As Long, _
                70. ByVal lpszCurrentDirectory As String, _
                71. lpdwCurrentDirectory As Long) As Long
                72. ‘Api para Crear un directorio
                73. Private Declare Function FtpCreateDirectory _
                74. Lib “wininet.dll” Alias “FtpCreateDirectoryA” _
                75. (ByVal hFtpSession As Long, _
                76. ByVal lpszDirectory As String) As Boolean
                77. ‘Api que Elimina un directorio del FTP
                78. Private Declare Function FtpRemoveDirectory _
                79. Lib “wininet.dll” Alias “FtpRemoveDirectoryA” _
                80. (ByVal hFtpSession As Long, _
                81. ByVal lpszDirectory As String) As Boolean
                82. ‘Para Borrar un fichero
                83. Private Declare Function FtpDeleteFile _
                84. Lib “wininet.dll” Alias “FtpDeleteFileA” _
                85. (ByVal hFtpSession As Long, _
                86. ByVal lpszFileName As String) As Boolean
                87. ‘Función que Renombra un fichero
                88. Private Declare Function FtpRenameFile _
                89. Lib “wininet.dll” Alias “FtpRenameFileA” _
                90. (ByVal hFtpSession As Long, _
                91. ByVal lpszExisting As String, _
                92. ByVal lpszNew As String) As Boolean
                93. ‘Recupera un archivo
                94. Private Declare Function FtpGetFile Lib “wininet.dll” _
                95. Alias “FtpGetFileA” (ByVal hConnect As Long, _
                96. ByVal lpszRemoteFile As String, _
                97. ByVal lpszNewFile As String, ByVal fFailIfExists As Long, _
                98. ByVal dwFlagsAndAttributes As Long, ByVal dwFlags As Long, _
                99. ByRef dwContext As Long) As Boolean
                100. ‘Escribe un archivo
                101. Private Declare Function FtpPutFile Lib “wininet.dll” _
                102. Alias “FtpPutFileA” (ByVal hConnect As Long, _
                103. ByVal lpszLocalFile As String, _
                104. ByVal lpszNewRemoteFile As String, ByVal dwFlags As Long, _
                105. ByVal dwContext As Long) As Boolean
                106. ‘Api Para manejar los errores
                107. Private Declare Function InternetGetLastResponseInfo _
                108. Lib “wininet.dll” Alias “InternetGetLastResponseInfoA” _
                109. (lpdwError As Long, ByVal lpszBuffer As String, _
                110. lpdwBufferLength As Long) As Boolean
                111. ‘Busca el primer archivo de un path
                112. Private Declare Function FtpFindFirstFile Lib “wininet.dll” _
                113. Alias “FtpFindFirstFileA” (ByVal hFtpSession As Long, _
                114. ByVal lpszSearchFile As String, _
                115. lpFindFileData As WIN32_FIND_DATA, _
                116. ByVal dwFlags As Long, ByVal dwContent As Long) As Long
                117. ‘api para buscar el siguiente archivo
                118. Private Declare Function InternetFindNextFile Lib “wininet.dll” _
                119. Alias “InternetFindNextFileA” (ByVal hFind As Long, _
                120. lpvFindData As WIN32_FIND_DATA) As Long
                121. Public Enum e_TipoTransferencia
                122. [ BINARIO ] = FTP_TRANSFER_TYPE_BINARY
                123. [ ASCII ] = FTP_TRANSFER_TYPE_ASCII
                124. [ DESCONOCIDO ] = FTP_TRANSFER_TYPE_UNKNOWN
                125. End Enum
                126. ‘Handle de la conexión Ftp
                127. Dim HandleConect As Long
                128. ‘Handle de la conexión a Internet
                129. Dim hOpen As Long
                130. ‘Variables locales
                131. Private m_DirectorioActual As String
                132. Private m_Usuario As String
                133. Private m_PassWord As String
                134. Private m_Servidor As String
                135. Private m_DirAnterior As String
                136. Private m_listView As ListView
                137. Private m_TipoTransferencia As Long
                138. Private m_form As Form
                139. Private ctrl As Object
                140. ‘Funciones Varias para el manejo de archivos y carpetas en el servidor Ftp
                141. ‘***********************************************************************
                142. ‘***********************************************************************
                143. ‘Rutina que conecta al Servidor Ftp
                144. Public Function ConectarFtp(Optional ControlStatus As Object _
                145. = Nothing) As Boolean
                146. ‘Verificamos que los datos de la cuenta estén establecidas, si no mostramos un _
                147. mensaje y salimos
                148. If m_Usuario = “” Or m_Servidor = “” Or m_PassWord = “” Then
                149. MsgBox “No se puede conectar. Verifique el Nombre de usuario,” _
                150. & “El nombre del Servidor y la contraseña que estén establecidas”, vbCritical
                151. ConectarFtp = False
                152. Exit Function
                153. End If
                154. Set ctrl = ControlStatus
                155. Status “…Intentando conectar a: ” & m_Servidor
                156. m_form.MousePointer = vbHourglass
                157. ‘Abrimos una conección a Internet
                158. hOpen = InternetOpen(vbNullString, _
                159. INTERNET_OPEN_TYPE_PRECONFIG, vbNullString, _
                160. vbNullString, 0)
                161. If hOpen = 0 Then
                162. Status “Error en la conexión a internet, compruebe la conexión”
                163. m_form.MousePointer = vbDefault
                164. ConectarFtp = False
                165. Exit Function
                166. End If
                167. ‘Conectamos al servidor FTP, pasandole los datos: login y servidor
                168. HandleConect = InternetConnect(hOpen, m_Servidor, _
                169. INTERNET_DEFAULT_FTP_PORT, m_Usuario, _
                170. m_PassWord, INTERNET_SERVICE_FTP, _
                171. IIf(PassiveConnection, INTERNET_FLAG_PASSIVE, 0), 0)
                172. If HandleConect = 0 Then
                173. Status “Error. Compruebe los datos del servidor Ftp sin son correctos”
                174. m_form.MousePointer = vbDefault
                175. ConectarFtp = False
                176. Exit Function
                177. End If
                178. Status “Conectado a: ” & m_Servidor
                179. m_form.MousePointer = vbDefault
                180. ConectarFtp = True
                181. End Function
                182. ‘Desconecta del servidor FTP
                183. ‘**************************************************
                184. Public Sub Desconectar()
                185. Dim ret As Long
                186. ‘cierra la conexion FTP
                187. ret = InternetCloseHandle(HandleConect)
                188. If ret = 0 Then Status “Error al desconectar”: Exit Sub
                189. ‘cierra la conexion a internet
                190. ret = InternetCloseHandle(hOpen)
                191. If ret = 0 Then Status “Error al desconectar”: Exit Sub
                192. Status “Desconectado de: ” & m_Servidor
                193. Class_Terminate
                194. End Sub
                195. ‘Recupera el directorio actual donde estamos ubicados
                196. ‘*****************************************************
                197. Public Function GetDirectorioActual() As String
                198. ‘Crea un buffer para el path
                199. m_DirectorioActual = String(MAX_PATH, 0)
                200. ‘Obtenemos el directorio actual
                201. ret = FtpGetCurrentDirectory(HandleConect, m_DirectorioActual, _
                202. Len(m_DirectorioActual))
                203. GetDirectorioActual = m_DirectorioActual
                204. End Function
                205. ‘Establecemos el Directorio Actual
                206. ‘****************************************************
                207. Public Sub CambiarDirectorio(PathDirectorio As String)
                208. Dim pData As WIN32_FIND_DATA
                209. Dim hFind As Long ‘handle usado para buscar fichs en FTP
                210. Dim ret As Long
                211. Dim strDir As String
                212. strDir = Replace(m_DirectorioActual, Chr(0), “”)
                213. If strDir = “/” And PathDirectorio = “../Subir un nivel” Then: Exit Sub
                214. m_form.MousePointer = vbHourglass
                215. If PathDirectorio = “../Subir un nivel” Then
                216. pos = InStrRev(strDir, “/”)
                217. strDir = Left(strDir, pos)
                218. ‘Cambia al Directorio Ftp al directorio especificado
                219. ret = FtpSetCurrentDirectory(HandleConect, strDir)
                220. If ret = 0 Then
                221. Status “Error al cambiar de directorio.”
                222. End If
                223. m_form.MousePointer = vbDefault
                224. Exit Sub
                225. End If
                226. ‘Cambia al Directorio especificado
                227. ret = FtpSetCurrentDirectory(HandleConect, strDir & “/” & PathDirectorio)
                228. If ret = 0 Then
                229. Status “Error al cambiar de directorio”
                230. End If
                231. m_form.MousePointer = vbDefault
                232. End Sub
                233. ‘Crea un nuevo directorio
                234. ‘*******************************************
                235. Public Sub CrearDirectorio(NameDirectorio As String)
                236. ‘Creamos la nueva carpeta
                237. ret = FtpCreateDirectory(HandleConect, NameDirectorio)
                238. If Not ret Then
                239. Status “Error al crear el directorio, compruebe el nombre que sea válido”
                240. Else
                241. m_listView.ListItems.Add , , NameDirectorio, , “carpeta”
                242. m_listView.ListItems(m_listView.ListItems.Count).Selected = True
                243. m_listView.SetFocus
                244. End If
                245. End Sub
                246. ‘Crea o sube un nuevo Archivo.
                247. ‘********************************************
                248. Public Sub SubirArchivo(localArchivo As String, NombreArchivoRemoto As String)
                249. ‘ ( Upload ) Envía el fichero al servidor FTP
                250. ret = FtpPutFile(HandleConect, localArchivo, NombreArchivoRemoto, _
                251. m_TipoTransferencia, 0)
                252. If ret Then
                253. m_listView.ListItems.Add , , NombreArchivoRemoto, , “archivo”
                254. m_listView.ListItems(m_listView.ListItems.Count).Selected = True
                255. m_listView.SetFocus
                256. Else
                257. Status “Error al subir el fichero:” & NombreArchivoRemoto
                258. End If
                259. End Sub
                260. ‘Renombra un archivo en el directorio Ftp corriente
                261. ‘****************************************************
                262. Public Sub RenombrarArchivo(Archivo As String, nuevoNombre As String)
                263. ret = FtpRenameFile(HandleConect, Archivo, nuevoNombre)
                264. If ret Then
                265. m_listView.SelectedItem.Text = nuevoNombre
                266. m_listView.SetFocus
                267. Else
                268. Status “Error al renombrar el fichero:” & nuevoNombre
                269. End If
                270. End Sub
                271. Public Sub ObtenerArchivo(ArchivoRemoto As String, ArchivoLocal As String, _
                272. Optional SobreEscribir As Boolean = False)
                273. ‘recupera fichero del servidor FTP: ArchivoRemoto es el nombre del archivo remoto
                274. ‘ArchivoLocal es el nombre y ruta donde se colocará el archivo en local
                275. ret = FtpGetFile(HandleConect, ArchivoRemoto, ArchivoLocal, _
                276. SobreEscribir, 0, m_TipoTransferencia, 0)
                277. If ret Then
                278. Status “Archivo descargado correctamente:”
                279. m_listView.SetFocus
                280. Else
                281. Status “Error al intentar descargar el fichero: ” & ArchivoRemoto
                282. End If
                283. End Sub
                284. ‘Eliminar Archivo del servidor
                285. Public Sub EliminarArchivo(Archivo As String)
                286. ret = FtpDeleteFile(HandleConect, Archivo)
                287. If Not ret Then
                288. Status “Error. No se pudo eliminar el archivo: ” & Archivo
                289. End If
                290. End Sub
                291. ‘Elimina un directorio
                292. ‘***********************************************************************
                293. Public Sub EliminarDirectorio(Directorio As String)
                294. ‘elimina el directorio
                295. ret = FtpRemoveDirectory(HandleConect, Directorio)
                296. If Not ret Then
                297. Status “Error. No se pudo eliminar el Directorio: ” & Directorio
                298. End If
                299. End Sub
                300. Private Sub Status(mensaje As String)
                301. On Error GoTo SubError
                302. ctrl = mensaje
                303. Exit Sub
                304. SubError:
                305. If Err.Number = 91 Then Resume Next
                306. End Sub
                307. ‘Lista los archivos el el LV
                308. ‘***********************************************************************
                309. Public Sub ListarArchivos()
                310. Dim pData As WIN32_FIND_DATA
                311. Dim hFind As Long ‘handle usado para buscar fichs en FTP
                312. Dim ret As Long ‘valor devuelto por API
                313. m_form.MousePointer = vbHourglass
                314. ‘crea un buffer
                315. pData.cFileName = String(MAX_PATH, 0)
                316. ‘busca el primer fichero
                317. hFind = FtpFindFirstFile(HandleConect, “*.*”, pData, 0, 0)
                318. m_listView.ListItems.Clear
                319. ‘Si Hfind vale 0 es porque no hay archivos ni directorios
                320. If hFind = 0 Then
                321. m_listView.ListItems.Add , , “../Subir un nivel”, , “carpeta”
                322. m_form.MousePointer = vbDefault
                323. Exit Sub
                324. End If
                325. m_listView.ListItems.Add , , “../Subir un nivel”, , “carpeta”
                326. If pData.dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY Then
                327. m_listView.ListItems.Add , , pData.cFileName, , “carpeta”
                328. Else
                329. m_listView.ListItems.Add(, , pData.cFileName, , “archivo”).SubItems(1) = _
                330. Round((pData.nFileSizeLow / 1024), 2) & ” Kb”
                331. End If
                332. ‘si no hay mas Archivos sale
                333. If hFind = 0 Then
                334. m_form.MousePointer = vbDefault
                335. Exit Sub
                336. End If
                337. Do
                338. ‘crea un buffer
                339. pData.cFileName = String(MAX_PATH, 0) ‘se llena con nulos
                340. ‘ busca el siguiente archivo
                341. ret = InternetFindNextFile(hFind, pData)
                342. ‘si no hay ficheros, no sigue
                343. If ret = 0 Then Exit Do
                344. If pData.dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY Or _
                345. pData.dwFileAttributes = 0 Then
                346. ‘Agrega el nombre del directorio al control ListView
                347. m_listView.ListItems.Add , , pData.cFileName, , “carpeta”
                348. Else
                349. ‘agrega el archivo y Muestra el tamaño del mismo
                350. m_listView.ListItems.Add(, , pData.cFileName, , “archivo”).SubItems(1) = _
                351. Round((pData.nFileSizeLow / 1024), 2) & ” Kb”
                352. End If
                353. Loop
                354. ‘Cerramos el handle de búsqueda anterior
                355. InternetCloseHandle hFind
                356. m_listView.Sorted = True
                357. m_form.MousePointer = vbDefault
                358. End Sub
                359. ‘Actualiza la lista de Archivos y directorios en el ListView
                360. ‘************************************************************
                361. Public Sub Actualizar()
                362. Dim pData As WIN32_FIND_DATA
                363. Dim hFind As Long ‘handle usado para buscar fichs en FTP
                364. Dim ret As Long ‘valor devuelto por API
                365. m_form.MousePointer = vbHourglass
                366. ‘crea un buffer
                367. pData.cFileName = String(MAX_PATH, 0)
                368. ‘busca el primer fichero
                369. hFind = FtpFindFirstFile(HandleConect, “*.*”, pData, 0, 0)
                370. m_listView.ListItems.Clear
                371. If hFind = 0 Then
                372. m_listView.ListItems.Add , , “../Subir un nivel”, , “carpeta”
                373. m_form.MousePointer = vbDefault
                374. Exit Sub
                375. End If
                376. m_listView.ListItems.Add , , “../Subir un nivel”, , “carpeta”
                377. If pData.dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY Then
                378. m_listView.ListItems.Add , , pData.cFileName, , “carpeta”
                379. Else
                380. m_listView.ListItems.Add(, , pData.cFileName, , “archivo”).SubItems(1) = _
                381. Round((pData.nFileSizeLow / 1024), 2) & ” Kb”
                382. End If
                383. ‘si no hay mas Archivos sale
                384. If hFind = 0 Then
                385. m_form.MousePointer = vbDefault
                386. Exit Sub
                387. End If
                388. Do
                389. ‘buffer
                390. pData.cFileName = String(MAX_PATH, 0) ‘se llena con nulos
                391. ‘ Busca el siguiente File
                392. ret = InternetFindNextFile(hFind, pData)
                393. ‘si no hay mas arhivos, no sigue buscando
                394. If ret = 0 Then Exit Do
                395. ‘Archivo
                396. If pData.dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY Or pData.dwFileAttributes = 0 Then
                397. m_listView.ListItems.Add , , pData.cFileName, , “carpeta”
                398. m_listView.ListItems.Add(, , pData.cFileName, , “carpeta”).ListSubItems(0) = _
                399. pData.ftCreationTime.dwHighDateTime
                400. Else
                401. m_listView.ListItems.Add(, , pData.cFileName, , “archivo”).SubItems(1) = _
                402. Round((pData.nFileSizeLow / 1024), 2) & ” Kb”
                403. End If
                404. Loop
                405. ‘Cerramos el handle de búsqueda
                406. InternetCloseHandle hFind
                407. m_listView.Sorted = True
                408. m_form.MousePointer = vbDefault
                409. End Sub
                410. Public Sub Inicializar(Formulario As Form)
                411. Set m_form = Formulario
                412. End Sub
                413. ‘Para los errores
                414. ‘************************************************+
                415. Private Sub ShowError()
                416. Dim lngNumError As Long
                417. Dim strMemoError As String
                418. Dim lngTamBuffer As Long
                419. ‘—————————–
                420. ‘Tamaño del buffer
                421. InternetGetLastResponseInfo lngNumError, _
                422. strMemoError, lngTamBuffer
                423. ‘crea buffer
                424. strMemoError = String(lngTamBuffer, 0)
                425. ‘Recupera informacion del error
                426. InternetGetLastResponseInfo lngNumError, _
                427. strMemoError, lngTamBuffer
                428. ‘Mostrar el error en un msgbox
                429. MsgBox “Error ” & CStr(lngNumError) & “: ” & strMemoError, _
                430. vbOKOnly Or vbCritical
                431. End Sub
                432. ‘Nombre de usuario de la cuenta Ftp
                433. ‘**********************************
                434. Public Property Get Usuario() As String
                435. Usuario = m_Usuario
                436. End Property
                437. Public Property Let Usuario(ByVal vNewValue As String)
                438. m_Usuario = vNewValue
                439. End Property
                440. ‘Nombre del servidor Ftp
                441. ‘***********************
                442. Public Property Get Servidor() As String
                443. Servidor = m_Servidor
                444. End Property
                445. Public Property Let Servidor(ByVal vNewValue As String)
                446. m_Servidor = vNewValue
                447. End Property
                448. ‘Contraseña de la cuenta FTP
                449. ‘***************************
                450. Public Property Get PassWord() As String
                451. PassWord = m_PassWord
                452. End Property
                453. Public Property Let PassWord(ByVal vNewValue As String)
                454. m_PassWord = vNewValue
                455. End Property
                456. ‘Establece el ListView donde listar los ficheros
                457. ‘***********************************************
                458. Public Property Get ListView() As ListView
                459. Set ListView = m_listView
                460. End Property
                461. Public Property Set ListView(ByVal vNewValue As ListView)
                462. Set m_listView = vNewValue
                463. End Property
                464. ‘Propiedad para el Modo de Transferencia que se va a utilizar
                465. ‘***************************************************************
                466. Public Property Get TipoTransferencia() As e_TipoTransferencia
                467. TipoTransferencia = m_TipoTransferencia
                468. End Property
                469. Public Property Let TipoTransferencia(NewData As e_TipoTransferencia)
                470. m_TipoTransferencia = NewData
                471. End Property
                472. Private Sub Class_Terminate()
                473. On Local Error Resume Next
                474. ‘Cerramos la sesión FTP y la conexión a internet
                475. InternetCloseHandle HandleConect
                476. InternetCloseHandle hOpen
                477. ‘Eliminamos las variables de objeto (Listview – Form )
                478. Set ctrl = Nothing
                479. Set ListView = Nothing
                480. Set m_form = Nothing
                481. End Sub
                Escrito en Visual Basic. Etiquetas: . 2 Comentarios »

                Que es Encriptación ?

                Toda encriptación se encuentra basada en un Algoritmo, la función de este Algoritmo es básicamente codificar la información para que sea indescifrable a simple vista , de manera que una letra “A” pueda equivaler a :“5x5mBwE” o bien a “xQE9fq”, el trabajo del algoritmo es precisamente determinar como será transformada la información de su estado original a otro que sea muy difícil de descifrar.

                Una vez que la información arrive a su destino final, se aplica el algoritmo al contenido codificado “5x5mBwE” o bien a “xQE9fq” y resulta en la letra “A” o según sea el caso, en otra letra. Hoy en día los algoritmos de encriptación son ampliamente conocidos,es por esto que para prevenir a otro usuario “no autorizado” descifrar información encriptada, el algoritmo utiliza lo que es denominado llave (“key”) para controlar la encriptación y decriptación de información. Algunos algoritmos son DES (algoritmo simétrico) AES que posiblemente suplantará a DES y uno de los más conocidos RSA (algoritmo asimétrico)

                Que función tiene la llave (“key”) ?

                Existen dos tipos de llaves (“key’s”) , pero la de mayor uso en Internet es denominada “public key” o algoritmo asimétrico. El nombre “public” proviene de su funcionamiento: existe una llave pública que es dada a conocer a cualquier persona que así lo desee (todo Internet), esta llave pública es utilizada por los emisores de mensajes para encriptar información , sin embargo, existe otra llave ( su pareja por llamarla de alguna manera) única que es conocida exclusivamente por el destinatario del mensaje, y es mediante esta llave única | secreta que el destinatario descifra (“decripta”) los mensajes encriptados por el emisor.

                Firmas Digitales (“Digital Signatures”)

                Una firma digital utiliza el mismo funcionamiento del “public key” o algoritmo asimétrico mencionado anteriormente.
                Como se mencionó, existe una “llave pública” y una “llave secreta”, en el caso de firmas digitales la llave pública que es ampliamente conocida es capaz de identificar si la información proviene de una fuente fidedigna.En otras palabras, la llave pública será capaz de reconocer si la información realmente proviene de la “llave secreta” en cuestión.Ejemplo:

                El departamento de compras posee las llaves públicas de todos los empleados de la compañía, si llega un pedimento con la dirección de email del Director de Finanzas, Cómo puede asegurarse el departamento de compras que en realidad esta persona realizó el pedimento y no alguna otra que sobrepuso el email ?. La llave secreta del director de finanzas debe de encontrarse solo en su computadora, por lo tanto al enviar el mensaje electrónico esta llave pública se añadió al email,y por lo tanto las llave publicas determinarán si la llave secreta coincide con la del director.

                Encriptación de Cadenas



                Algunos Procedimiento Visual Basic para Codificar y Decodificar Información

                Por Harvey Triana

                La Encriptación, es un tema de la programación bien interesante, de hecho se trata de una alta ciencia de la informática, que pasa por arte, inclusive hoy se trata de una tecnología. Encriptación es una palabra rara en español, quizá fea, no obstante se emplea en documentación técnica.

                Un Proyecto sobre Encriptación...

                Buscando en el cajón de los recuerdos, encontré un par de procedimientos para Codificación / Decodificación con QuickBasic. Desaforadamente desconozco al programador de tales procedimientos. Después de traducir las rutinas para que trabajasen en Visual Basic me di cuenta que tenían fallas de programación, sin embargo la documentación del código me llevo a captar la idea del autor y ponerla a funcionar perfectamente. Tales procedimientos se exponen en este documento.


                Algo de Teoría

                Recuerdo que alguien comentaba que codificar era muy sencillo, simplemente cambias unas letras por otras, y el receptor del mensaje conoce este secreto. También, aumentar o disminuir un número discreto a los códigos ASCII, suele ser otra sugerencia. Estas estrategias pasan a ser infantiles tratamientos del problema, y no presentan problema alguno para un decodificador experto y malo.

                La encriptación se hace a través de la aplicación de cierto tratamiento a los códigos ASCII de los mensajes, de manera que el tratamiento inverso será el único camino práctico para decodificar el mensaje.

                ¿Cómo se mantiene indescifrable una cadena codificada?. Se trata de combinar la clave de encriptación con el mensaje de manera que las probabilidades de decodificar el mensaje, sin conocer la clave, sean virtualmente infimas, es decir, el tan prolongado el trabajo de desciframiento que no existen esperanzas próximas. Por ejemplo cuando un cristal ornamental se rompe violentamente, es casi imposible volver a juntar las piezas para obtener la pieza original.

                Como una cita teórica, uno de los algoritmos de codificación se basa en la función números primos. Por ejemplo resulta fácil multiplicar dos números primos, sean a = 11927 y b = 20903, de manera que a • b = c = 249310081. Pero resulta muy difícil determinar a y b a partir de c. Matemáticamente esto se hace a través del procedimiento conocido como Descomposición Factorial. En el ejemplo c viene a ser la codificación, mientras a y b son la clave de decodificación. Esta estrategia es la base de un ingenioso y sofisticado sistema de encriptación llamado Criptosistema RSA (en honor a sus autores). de hecho este es el mejor sistema de encriptación y es posible que sea aplicado en las comunicaciones masivas de Internet en un futuro. Siguiendo con mí ejemplo, en un caso real, se trata de cifras de alrededor de 250 dígitos, lo cual teóricamente requiere de millones de años para descomponer factorialmente. Una anécdota citada en el libro Camino al Futuro (2ª edición) de Mr. Gates, cuenta que un reto publico de descifrar una clave de 129 dígitos fue superado al coordinar un trabajo de varias computadoras e Internet, para finalmente lograr la respuesta en menos de un año. En el reto los autores predecían que su mensaje jamás sería desvelado. El libro mencionado hace una exposición más detallada de este tema. Por supuesto, este articulo no llegará tan lejos.


                Dos Procedimientos de Encriptación

                La Encriptación tiene dos elementos: Un mensaje, y una clave privada de acceso entre el emisor y el receptor, con la cual se puede descifrar el mensaje. El procedimiento de encriptación produce un mensaje o archivo cifrado. Presento dos procedimientos escritos en Visual Basic, catalogados como verdaderas maneras de encriptación.


                Modelo EncryptionString

                EncryptionString, es un clásico sistema el cual toma el mensaje y una clave del usuario, y a través de una combinación de estos dos parámetros se produce una cadena codificada. Mantengo la explicación original del método:

                Texto a codificar:         ENCRYPTION
                Caracteres del Texto:      E   N   C   R   Y   P   T   I   O   N
                Códigos ASCII:             69  78  67  82  89  80  84  73  79  78
                Contraseña KEY:            K   E   Y   K   E   Y   K   E   Y   K
                Caracteres de KEY:         75  69  89  75  69  89  75  69  89  75
                Suma de Códigos ASCII:     144 147 156 157 158 169 159 142 168 153
                En caracteres:                "   œ      ?   ©   Ÿ   ?   ¨   TM
                
                Texto codificado:          "œ?©Ÿ?¨TM

                El modo para usar la función EncryptionString es el siguiente:

                '//Codifica
                TextoCodificado = EncryptString(TextoOriginal, Contraseña, ENCRYPT)
                
                '//Decodifica
                TextoOriginal = EncryptString(TextoCodificado, Contraseña, DECRYPT)

                La ventaja de esta técnica es que es muy flexible de usar e intuitiva. Sin tener la máxima seguridad, es muy segura. Escribir un programa para encriptar archivos resulta bastante simple por aquello de la contraseña. No ocurre lo mismo con el siguiente procedimiento: ChrTran.

                El código de EncryptionString es el siguiente:

                DefInt A-Z
                Option Explicit
                
                '//For Action parameter in EncryptString
                Public Const ENCRYPT = 1, DECRYPT = 2
                
                '---------------------------------------------------------------------
                ' EncryptString
                ' Modificado por Harvey T.
                '---------------------------------------------------------------------
                Public Function EncryptString( _
                    UserKey As String, Text As String, Action As Single _
                    ) As String
                    Dim UserKeyX As String
                    Dim Temp     As Integer
                    Dim Times    As Integer
                    Dim i        As Integer
                    Dim j        As Integer
                    Dim n        As Integer
                    Dim rtn      As String
                
                    '//Get UserKey characters
                    n = Len(UserKey)
                    ReDim UserKeyASCIIS(1 To n)
                    For i = 1 To n
                        UserKeyASCIIS(i) = Asc(Mid$(UserKey, i, 1))
                    Next
                
                    '//Get Text characters
                    ReDim TextASCIIS(Len(Text)) As Integer
                    For i = 1 To Len(Text)
                        TextASCIIS(i) = Asc(Mid$(Text, i, 1))
                    Next
                
                    '//Encryption/Decryption
                    If Action = ENCRYPT Then
                       For i = 1 To Len(Text)
                           j = IIf(j + 1 >= n, 1, j + 1)
                           Temp = TextASCIIS(i) + UserKeyASCIIS(j)
                           If Temp > 255 Then
                              Temp = Temp - 255
                           End If
                           rtn = rtn + Chr$(Temp)
                       Next
                    ElseIf Action = DECRYPT Then
                       For i = 1 To Len(Text)
                           j = IIf(j + 1 >= n, 1, j + 1)
                           Temp = TextASCIIS(i) - UserKeyASCIIS(j)
                           If Temp < 0 Then
                              Temp = Temp + 255
                           End If
                           rtn = rtn + Chr$(Temp)
                       Next
                    End If
                
                    '//Return
                    EncryptString = rtn
                End Function

                Modelo ChrTran

                ChrTran es violentamente complicado de violar, de hecho imposible. Virtualmente las probabilidades de descifrar un mensaje son del orden de 255! (255 factorial), un numero sin fin en la práctica (por ejemplo las calculadoras comunes soportan solo hasta 69!).

                Tengo que confesar que tuve que reescribir ChrTran porque presentaba errores de programación y mucho código para optimizar, el resultado es sorprendente. Ni que decir que no tenía en cuenta que en español usamos tildes y eñes.

                Como su abreviación lo dice ChrTran transpone caracteres, usa dos claves de 255 caracteres (la carta ASCII) y produce un texto codificado de origen aleatorio. Toma cada carácter del texto, encuentra su posición en la primera clave, e intercambia este carácter por el carácter en la misma posición de la segunda clave. Es complicado de asimilar.

                Lo más difícil de ChrTran es el manejo de las claves. La primera clave (la sarta de búsqueda) podría ser publica (mejor debiera decir debe ser publica). Mientras que la segunda clave es una sarta aleatoria de la carta ASCII.

                El modo de manejar ChrTran es el siguiente:

                ClaveAleatoria = RandomChart()
                
                '// Se podría usar la sarta de búsqueda, ClaveDeBúsqueda, como
                '// otra cadena aleatoria con ClaveDeBúsqueda = RandomChart()
                '// aqui se mantiene un nivel de Encriptación flexible, más no
                '// inseguro, al hacer ClaveDeBúsqueda como la secuencia 0 a 255
                '// de la carta ASCII:
                For i = 0 To 255
                    ClaveDeBúsqueda = ClaveDeBúsqueda + Chr$(i)
                Next
                
                '//Codifica
                TextoCodificado = ChrTran(TextoOriginal, ClaveDeBúsqueda, ClaveAleatoria)
                
                '//Decodifica
                TextoOriginal= ChrTran(TextoCodificado, ClaveAleatoria, ClaveDeBúsqueda)

                En la primera línea se usa RandonChart, la cual es una función que retorna la carta ASCII en un orden aleatorio.

                Como posiblemente se deduce, usar ChrTran para escribir un programa que trabaje encriptación, representa una labor bastante especializada, pero por supuesto nada del otro mundo.

                El código de ChrTran es el siguiente:

                DefInt A-Z
                Option Explicit
                
                '//---------------------------------------------
                '// Return a random string of ASCII Chart.
                '// Used by ChrTran. By Harvey T.
                '//---------------------------------------------
                Public Function RandomChart() As String
                    Dim Char    As String
                    Dim RndStr  As String
                    Dim n       As Integer
                
                    Randomize Timer
                    Do
                       Char = Chr$(Int(Rnd * 256))
                       If InStr(RndStr, Char) = 0 Then
                          n = n + 1
                          RndStr = RndStr + Char
                       End If
                    Loop Until n = 256
                
                    RandomChart = RndStr
                End Function
                
                '---------------------------------------------------------------------
                ' ChrTran
                ' Optimizing by Harvey T.
                '---------------------------------------------------------------------
                Public Function ChrTran(Text As String, SStr As String, EStr As String) As String
                    Dim i   As Integer
                    Dim rtn As String
                
                    For i = 1 To Len(Text)
                        rtn = rtn + Mid$(EStr, InStr(SStr, Mid$(Text, i, 1)), 1)
                    Next
                    ChrTran = rtn
                End Function

                Aplicación de Ejemplo

                La figura que encabeza este articulo muestra la cara de la aplicación EncryptProject, la cual es una herramienta que utiliza las técnicas de encriptación descritas y lo creé con el propósito de proteger código Visual Basic. Es decir, supón que trabajas en un proyecto grande, en un equipo de programación y deseas mantener en privado ciertos módulos. Generalmente yo organizo mis proyectos por módulos, en carpetas aparte, así, EncryptProject tiene la finalidad de encriptar los módulos Visual Basic de esta carpeta. Así, cuando voy a trabajar, decodifico, cuando me voy encripto.

                Si desea usar EncryptProject , debe tener en cuanta esta precuación:

                Backup your files (in another disc or with password) before encrypt project.
                No forget your password, it’s without it reversing !
                Encrypt only work over: Bas, Frm, Cls, Ctl, html files

                Es decir, como una percusión, debiera tener al menos una copia aparte del proyecto. Pero no se atemorice, el programa es seguro, lo peor que le podría pasar al que lo use es olvidar la contraseña (EncryptProject pide verificación de la contraseña antes de codificar). Recomiendo para empezar, hacer una prueba sencilla con un proyecto simple.

                Dos pasos y salimos. Hagamos una prueba simple. Cree una carpeta, copie o cree u proyecto Visual Basic en ella.


                Encriptando

                Abra EncryptProject, y escriba la trayectoria completa de la carpeta (si lo prefiere, arrastre un archivo cualquiera de la carpeta desde el Explorador de archivos de Windows a la caja de texto Project Folder).

                Escriba la contraseña y use el comando [Encrypt]. Se pedirá la verificación de la contraseña. Después de confirmar, se encriptará el proyecto.

                Encriptar en Visual Basic y Visual Basic.NET

                Aqui les dejo un buen articulo sacado de djk

                Una clase sencilla para encriptar cadenas

                 

                Uno de los problemas clásicos con los que nos enfrentamos los programadores es el de encriptar los datos. La cantidad de situaciones en las que necesitamos encriptar los datos es incontable.

                 

                Normalmente los datos se almacenan en una base de datos, y esta suele ser segura, pero las claves de conexión se encuentran en archivos de texto planos, XML … Incluso hay muchas bases de datos en las que la información de las claves con las que los usuarios se conectan a los sistemas corporativos estan sin encriptar, con lo que además podemos llegar a comprometer la seguridad de todo nuestro sistema.

                 

                Existen complejos algoritmos que permiten la encriptación de datos y que garantizan matemáticamente que nadie los puede desencriptar, como es el caso de MD5, utilizado para emitir certificados de seguridad en sistemas SSL. Tambien existen componentes de software que realizan el trabajo de encriptación por nosotros.

                 

                Estas soluciones tienen su parte negativa, por un lado los algoritmos seguros no se pueden desencriptar, por lo que no siempre son utiles. Por otro lado los componentes de software desarrollados por compañias pueden llegar a ser muy caros y además es necesario instalarlos en el los servidores, cosa que no siempre es posible.

                 

                La solución son los sistemas de clave pública-privada. Son sistemas en los que la encriptación se basa en un par de claves, con una clave se encriptan los datos y sólo se pueden desencriptar si se conoce la otra.

                 

                Nuestra clase esta basada en esta idea. Vamos a desarrollar una clase que escripta cadena basandose en un patrones. La clase está escrita en VB.NET, pero al utilizar unicamente tipos comunes podemos exportarla facimente a cualquier otro lenguaje.

                 

                Lo primero que vamos a hacer es crear la clase a la que vamos a llamar Encriptador.

                 

                Public Class Encriptador

                End Class

                 

                La idea de esta clase es definir dos patrones, cada uno con todos los caracteres del idioma, de la A-Z en mayúscula y minuscula, los numeros … y cada uno en un orden diferente y aleatorio. Después aplicaremos un sencillo algoritmo que nos haga correcponder cada elemento de un patrón con otro.

                 

                Lo primero que necesitamos definir los patrones,para ello tecleamos el abecedario y los números en una cadena de texto como esta:

                 

                ABCDEFGHIJKLMNÑOPQRSTVWXYZabcdefghijklmnñopqrstvwxyz1234567890

                 

                Y ahora la desordenamos hasta que quede ilegible. Asignamos estas cadenas a nuestras cadena de patrón. Definimos las cadenas a nivel de instancia.

                 

                Tambien declaramos dos métodos, uno para encriptar la cadena y otro para encriptar una única letra, que además recibe otros dos parámetros, uno denominado variable, que representa la longitud de la cadena a encriptar y otro a_indice, que representa el índice del caracter dentro de la cadena. Con ellas construiremos un sencillo algoritmo, que evitará que alguien pueda establecer una relación del tipo de “la a es la j encriptada” y que hará que cada letra se encripte de modo diferente dependiendo de la longitud de la cadena y de donde esté situada dentro de ella.

                 

                Public Class Encriptador

                Private patron_busqueda As String = “0ABIZ2ÑebDNOEcwGl6oSñixq1…”
                Private Patron_encripta As String = “vQÑO8dk1VgIPZxAR3UsLD6XBz…”
                ‘Los patrones están aquí sin terminar por falta de espacio.

                Public Function EncriptarCadena (ByVal cadena As String) As String

                End Function

                Private Function EncriptarCaracter (ByVal caracter As String, _
                ByVal variable As Integer, _
                ByVal a_indice As Integer) As String

                End Function

                End Class

                 

                 

                Ahora escribimos el código para EncriptarCadena, que sencillamente recorre la cadena letra a letra invocando al método de encriptar caracter, pasandole como parémetros el caracter, la longitud de la cadena y el índice de la iteración. El código es muy sencillo.

                 

                Public Function EncriptarCadena (ByVal cadena As String) As String

                Dim idx As Integer
                Dim result As String

                For idx = 0 To cadena.Length – 1
                result += EncriptarCaracter(cadena.Substring(idx, 1), cadena.Length, idx)
                Next
                Return result

                End Function

                 

                Ahora tenemos que escribir el método EncriptarCaracter, como hemos visto al declarar el método, recibe tres parámetros, el caracter que queremos encriptar, un entero variable (que será la longitud de la cadena a encriptar) y el indice que ocupa el caracter a encriptar dentro de la cadena que queremos encriptar. Esto nos va a permitir escribir un sencillo algoritmo para devolver el índice que le va a corresponder a nuestro caracter dentro del patron encriptado.

                 

                Private Function EncriptarCaracter (ByVal caracter As String, _
                ByVal variable As Integer, _
                ByVal a_indice As Integer) As String

                Dim caracterEncriptado As String, indice As Integer

                If patron_busqueda.IndexOf(caracter) <> -1 Then
                indice = (patron_busqueda.IndexOf(caracter) + variable + a_indice) Mod patron_busqueda.Length
                Return Patron_encripta.Substring(indice, 1)
                End If

                Return caracter

                End Function

                 

                La función busca el índice de mi caracter en la cadena denominada patron_busqueda, si lo encontramos (recordar que tenemos todas las letras y números), lo encriptamos, si no lo encontramos devolvemos el mismo caracter que hemos recibido como parámetro.

                 

                La encriptación en muy sencilla, ¡pero muy efectiva!, encontramos el lugar que ocupa dentro del patron de busqueda el caracter, le sumamos la longitud de la cadena y le sumamos por último el lugar que ocupa el caracter que estamos encriptando dentro de ella. Obtenemos el caracter correspondiente al índice calculado dentro del patron_encriptado. De este modo conseguimos que el mismo caracter se encripte de forma diferente en cada cadena, y que una pequeña modificación en la cadena cambie totalmente la encriptación.

                 

                Este algoritmo plantea un problema, ¿que ocurre cuando la suma de estos tres parámetro es superior a la longitud de los patrones?. Estariamos intentando estraer un elemento que supera los límites de las cadenas patrón. Para solucionar este problema trabajamos con módulos matemáticos. La operación módulo devuelve el resto de la división entera entre dos números. De este modo cuando nuestro índice supera los limites de la cadena, volvemos a la posición inicial.

                 

                Ejemplos con operaciones de modulos

                15 Mod 20 ==> devuelve 15

                20 Mod 20 ==> devuelve 0

                21 Mod 20 ==> devuelve 1

                45 Mod 20 ==> devuelve 5

                 

                Ya tenemos nuestra clase que encripta cadenas, vamos a escribir una pequeña aplicación de consola que pruebe nuestra clase.

                 

                Module Encriptador_Test

                Sub Main()

                Dim cadena As String
                Dim enc As Encriptador
                enc =New Encriptador()
                Do
                Console.WriteLine(“Introducca la cadena que quiera encriptar:”)
                cadena = Console.ReadLine()
                Console.WriteLine( ” ==> ” + enc.EncriptarCadena(cadena))
                Loop While cadena <> “”

                End Sub

                End Module

                 

                Vamos a ejecutar nuestro programa, y probamos a encriptar varias cadenas.

                 


                C:\VB.net\Encriptar\Encriptar\bin>encriptar.exe

                Introducca la cadena que quiera encriptar:
                Hola mundo! ==> 9QPz hpKIZ!

                Introducca la cadena que quiera encriptar:
                www.Devjoker.com ==> feo.Frfxy¥hb.jii

                Introducca la cadena que quiera encriptar:
                La misma letra varia aaaaaaaa! ==> YB WMb¤I JC¥yH Q¥CgP v7Ky8qj4!

                 

                Ahora necesitamos escribir lo métodos para desencriptar. Las declaraciones son parecidas.

                 

                 

                Public Function DesEncriptarCadena(ByVal cadena As String) As String

                End Function

                Private Function DesEncriptarCaracter(ByVal caracter As String, ByVal variable As Integer, _
                ByVal a_indice As Integer) As String

                End Function

                 

                 

                La implementación de los métodos tambien es sencilla, si bien el método de desencriptar caracter es algo más complicado, debido a la posibilidad de encontrar índices negativos. Aún así resultan faciles de comprender.

                 

                 

                Public Function DesEncriptarCadena(ByVal cadena As String) As String

                Dim idx As Integer
                Dim result As String

                For idx = 0 To cadena.Length – 1
                result+=DesEncriptarCaracter(cadena.Substring(idx, 1),cadena.Length,idx)
                Next
                return result
                End Function

                Private Function DesEncriptarCaracter(ByVal caracter As String, _
                ByVal variable As Integer, _
                ByVal a_indice As Integer) As String

                Dim indice As Integer

                If Patron_encripta.IndexOf(caracter) <> -1 Then
                If (Patron_encripta.IndexOf(caracter) – variable – a_indice) > 0 Then
                indice = (Patron_encripta.IndexOf(caracter) – variable – a_indice) Mod Patron_encripta.Length
                Else
                ‘La línea está cortada por falta de espacio
                indice = (patron_busqueda.Length) + ((Patron_encripta.IndexOf(caracter)
                - variable – a_indice) Mod Patron_encripta.Length)
                End If
                indice = indice Mod Patron_encripta.Length
                Return patron_busqueda.Substring(indice, 1)
                Else
                Return caracter
                End If

                End Function

                 

                Con esto ya tenemos escrito todo el código necesario. Como vereis no se trata de un sistema excesivamente seguro, pero para poder desencriptar nuestras cadenas es necesario conocer dos patrones de 64 caracteres, que además han sido generadas de forma aleatoria. Además este código es facilmente portable a cualquier plataforma, dado que se basa completamente en tipos sencillos, cadenas de caracteres, por lo que reescribir el codigo para una clase Java es muy facil.

                 

                El código necesario para que nuestra aplicación de consola desencripte es trivial, por lo que no lo comentaremos.

                 

                Bueno, espero que oa haya parecido interesante el articulo, saludos y hasta la proxima.

                Le salio competencia a XUL

                Ya hace un tiempo que XUL ( XML-based User-interface Language) hizo su aparaicion a travez de Mozilla firefox, ha estgado esperando lograr su estandarizacion, pero aprarecido algo que “talvez”, tire al agua esta joven tecnologia, pues una desventaja actual de XUL, es que el motor de interpretacion pricipal, es el propio nevegador, osea Firefox es interprete de XUL, existen intrepretes externos al propio Mozilla FireFox, como XULRUNNER, pero nada estandar, bueno, la competencia de XUL o la piedra en el camino por lo que se ve, es BINDOWS, lo que si BINDOWS NO ES FREE, ni nada por el estilo, ventaja para XUL??, quien sabe, Observen esto:

                Demos de BINDOWS

                Ejemplo de XUL

                Escrito en XUL. Etiquetas: . Deja un Comentario »

                Progamacion Orientada A Aspectos

                Lo ultimo y mas nuevo miren esto, el paradigma de la programacion orientada a los aspectos, vean esto

                 

                Ver mas y algo mas de info, y mas info, otro documento

                Creo que debere cambiar el titulo del blog :P

                El Efecto Hawthorne

                El Efecto Hawthorne

                    Entre 1927 y 1932 se realizó un estudio en la Hawthorne Plant de la Western
                Electric Company en Cicero, Illinois. En este estudio se investigó el efecto que
                producían en la productividad los cambios ambientales introducidos por los
                investigadores. Los investigadores comprobaban que aumentando el nivel de
                intensidad luminosa en la planta, se aumentaba la productividad. La sorpresa
                fue cuando se disminuyo la intensidad luminosa y se comprobó que la
                productividad también aumentaba. Prácticamente se podía cambiar cualquier
                aspecto de la metodología de trabajo de la planta y la productividad
                aumentaba.

                    ¿Cómo podía ser esto? La mayor conclusión de dicho estudio es que a los
                trabajadores les agradaba la atención recibida durante el estudio y se
                esforzaban por rendir más. Otra conclusión equivalente es que la novedad
                introducida por el nuevo sistema saca a los trabajadores de su letargo
                metodológico, que a la gente le gusta la novedad y le aburre hacer las cosas
                siempre igual.

                    A esto se le ha venido a denominar el Efecto Hawthorne.
                ¿No será este efecto lo que nos proporciona mejoras en todas y cada una de
                las nuevas metodologías? Esto parecería consistente con el resultado a largo
                plazo de estos sistemas, que son rápidamente olvidados y no parecen obtener
                tan buenos resultados cuando se convierten en la norma de trabajo.
                El libro Peopleware, uno de los libros sobre gestión de proyectos más
                influyente en nuestro campo, ya nos advierte de este efecto y nos avisa de
                cómo podemos aprovecharlo a nuestro favor: Empresas como Fujitsu han
                convertido la novedad en norma y en todos sus proyectos introducen algún
                aspecto experimental para evitar el tedio de los trabajadores.
                Estemos pues atentos a lo que cada sistema es capaz de aportar en nuestro
                trabajo diario, practiquemos un escepticismo creativo con dichos sistemas,
                aplicando aquello que realmente nos va a ayudar en nuestra tarea y dejando de
                lado las recetas milagrosas. Y no nos olvidemos de disfrutar en nuestro trabajo.
                El aburrimiento en el trabajo del programador es lo que realmente disminuye la
                productividad. Algunas pequeñas novedades introducidas en cada proyecto
                pueden hacer más por este que cualquier metodología existente.

                Articulo Publicado Originalmente aqui

                Refactorizacion

                Aqui les dejo un articulo encontrado por ahi, que habla de la refactorizacion, el “rehacer y sin modificar” , espero les siva, bye

                Refactorización en la práctica: peligros y soluciones

                            Jesús Pérez y Jorge Ferrer

                AgileSpain (http://www.agile-spain.com/)

                Introducción a la refactorización
                La refactorización es uno de los nuevos conceptos que se han introducido en la terminología del mundo del desarrollo apoyado por las metodologías ágiles. Extreme Programming, una de las metodologías ágiles más extendida, la incluye dentro del decálogo de prácticas que propone como fundamentales. Sin embargo la idea de refactorización no es nueva, lo que resulta novedoso es la forma en que se lleva a cabo, una forma ágil y agresiva, pero al tiempo ordenada y segura. Refactorizar es una idea apoyada por una técnica que debemos controlar si queremos llevar a cabo un diseño evolutivo y un desarrollo ágil de nuestra aplicación.

                        ¿Qué significa Refactorizar? La mejor definición la obtenemos de uno de los padres de esta técnica, Martin Fowler, que dijo: “Refactorizar es realizar modificaciones en el código con el objetivo de mejorar su estructura interna, sin alterar su comportamiento externo”. De esta definición extraemos ideas muy importantes y conceptos que a veces estaban equivocados. Refactorizar no es una técnica para encontrar y corregir errores en una aplicación, puesto que su objetivo no es alterar su comportamiento externo. De hecho, no modificar el comportamiento externo de la aplicación es uno de los pilares de cualquiera de las prácticas que forman parte de la técnica, para lo que en muchas ocasiones se hace uso de las pruebas unitarias. La esencia de esta técnica consiste en aplicar una serie de pequeños cambios en el código manteniendo su comportamiento. Cada uno de estos cambios debe ser tan pequeño que pueda ser completamente controlado por nosotros sin miedo a equivocarnos. Es el efecto acumulativo de todas estas modificaciones lo que hace de la Refactorización una potente técnica. El objetivo final de Refactorizar es mantener nuestro código sencillo y bien estructurado.
                Refactorizar nos propone seguir las técnicas matemáticas que consiguen reducir fórmulas muy complejas en fórmulas equivalentes pero más sencillas. En el entorno matemático estas simplificaciones consiguen eficiencia, además de facilitar el cálculo de problemas libre de errores, muy fáciles de cometer cuando las formulas son muy complejas. No obstante conseguir un código sencillo es una tarea muy compleja

                Como dice Carver Mead:
                “Es fácil tener una idea complicada. Es muy, muy complicado tener una idea simple.”

                ¿Por qué debemos Refactorizar nuestro código? Existen muchas razones por las que deberíamos adoptar esta técnica:

                           – Calidad. La más importante. Conseguir ser un buen profesional pasa inevitablemente por conseguir que tu trabajo sea de calidad. Refactorizar es un continuo proceso de reflexión sobre nuestro código que permite que aprendamos
                de nuestros desarrollos en un entorno en el que no hay mucho tiempo para mirar hacia atrás. Un código de calidad es un código sencillo y bien estructurado, que cualquiera pueda leer y entender sin necesidad de haber estado integrado en el equipo de desarrollo durante varios meses. Se acabaron los tiempos en que lo que imperaba eran esos programas escritos en una sola línea en la que se hacia de todo, tiempos en los que se valoraba la concisión aun a costa de la legibilidad.

                           – Eficiencia: Mantener un buen diseño y un código estructurado es sin duda la forma más eficiente de desarrollar. El esfuerzo que invirtamos en evitar la duplicación de código y en simplificar el diseño se verá recompensado cuando tengamos que realizar modificaciones, tanto para corregir errores como para añadir nuevas funcionalidades.

                            – Diseño Evolutivo en lugar de Gran Diseño Inicial: En muchas ocasiones los requisitos al principio del proyecto no están suficientemente especificados y debemos abordar el diseño de una forma gradual. Cuando tenemos unos requisitos claros y no cambiantes un buen análisis de los mismos puede originar un diseño y una implementación brillantes, pero cuando los requisitos van cambiando según avanza el proyecto, y se añaden nuevas funcionalidades según se le van ocurriendo a los participantes o clientes, un diseño inicial no es más que lo que eran los requisitos iniciales, algo generalmente anticuado. Refactorizar nos permitirá ir evolucionando el diseño según incluyamos nuevas funcionalidades, lo que implica muchas veces cambios importantes en la arquitectura, añadir cosas y borrar otras.

                          – Evitar la Reescritura de código: En la mayoría de los casos Refactorizar es mejor que rescribir. No es fácil enfrentarse a un código que no conocemos y que no sigue los estándares que uno utiliza, pero eso no es una buena excusa para empezar de cero. Sobretodo en un entorno donde el ahorro de costes y la existencia de sistemas lo hacen imposible.

                Refactorizar es por tanto un medio para mantener el diseño lo más sencillo posible y de calidad. ¿Qué se entiende por sencillo y de calidad? Kent Beck define una serie de características para lograra un código los mas simple posible:

                - El código funciona (el conjunto de pruebas de la funcionalidad de nuestro código pasan correctamente).
                - No existe código duplicado.
                - El código permite entender el diseño.
                - Minimiza el número de clases y de métodos.

                A pesar de todo ello, refactorizar parece ser muchas veces una técnica en contra del sentido común. ¿Por qué modificar un código que funciona? (si funciona no lo toques) ¿Por qué correr el riesgo de introducir nuevos errores?, ¿cómo podemos justificar el coste de modificar el código sin desarrollar ninguna nueva funcionalidad?
                No siempre es justificable modificar el código, no se reduce a una cuestión estética.

                Cada refactorización que realicemos debe estar justificada. Sólo debemos refactorizar cuando identifiquemos código mal estructurado o diseños que supongan un riesgo para la futura evolución de nuestro sistema. Si detectamos que nuestro diseño empieza a ser complicado y difícil de entender, y nos está llevando a un situación donde cada cambio empieza a ser muy costoso. Es en ese momento cuando debemos ser capaces de frenar la inercia de seguir desarrollando porque si no lo hacemos nuestro software se convertirá en algo inmantenible (es imposible o demasiado costoso realizar un cambio).

                       Tenemos que estar atentos a estas situaciones donde lo más inteligente es parar, para reorganizar el código. Se trata de dar algunos pasos hacia atrás que nos permitirán tomar carrerilla para seguir avanzando. Los síntomas que nos avisan de que nuestro código tiene problemas se conocen como “Bad Smells”.

                ¿En qué momento debemos Refactorizar?, ¿es necesario planificar tareas específicas de refactorización?
                La Refactorización no es un proceso que podemos aislar como una tarea. Es necesario que lo incluyamos como una actividad que realizaremos cuando estemos desarrollando. No tiene que ser exactamente en el mismo instante en que identifiquemos un “Bad Smell”, ya que como se ha apuntado anteriormente, la funcionalidad no debe verse modificada. Por ello, debemos acabar lo que estemos haciendo (recuerda, pasos pequeños). Una vez finalizado el paso que nos ocupa, es el momento de abarcar la refactorización que debe realizarse de forma natural como parte del mismo desarrollo.

                        La adopción de la refactorización como parte de nuestros desarrollos no resulta muchas veces sencilla, puesto que intuitivamente no proporciona la sensación de avanzar en la finalización de nuestro desarrollo. No obstante el tiempo empleado en refactorizaciones se verá muy justificado a la hora de seguir con el diseño, realizar modificaciones o buscar errores en el código para subsanarlos. Como dice el refrán: “Vísteme despacio que tengo prisa”.
                ¿Cuales son estos “Bad Smells”?, ¿cuáles son los síntomas de que la evolución de nuestro desarrollo se encamina hacia un caos?, ¿cuándo nuestro código empieza oler?. En [Fowler] podemos encontrar una lista de ellas como Código Duplicado, Métodos largos, Clases Largas, Cláusulas Switch, Comentarios, etc.

                       Existen herramientas en “Ant” como “checkstyle” que permiten automatizar la identificación de algunos “Bad Smell” (con “checkstyle” es posible fijar el numero máximo de líneas de un método, o el máximo número de parámetros de un método).

                        Una vez identificado el “Bad Smell” deberemos aplicar una refactorización que permita corregir ese problema. Para comenzar a Refactorizar es imprescindible que el proyecto tenga pruebas automáticas, tanto unitarias como funcionales, que nos permitan saber en cualquier momento al ejecutarlas, si el desarrollo sigue cumpliendo los requisitos que implementaba. Sin pruebas automáticas, Refactorizar es una actividad que conlleva un alto riesgo. Sin pruebas automáticas nunca estaremos convencidos de no haber introducido nuevos errores en el código al término de una refactorización, y poco a poco dejaremos de hacerlo por miedo a estropear lo que ya funciona. “Refactorizar sin tener un conjunto de pruebas asociado al proyecto debe dejárselo a los profesionales y no intentarlo en su casa”, claro que los profesionales lo primero que harán para Refactorizar será implementar las pruebas automáticas que cubran la funcionalidad que se ve cubierta por el código objetivo de la refactorización.

                        ¿Cómo debe aplicarse una refactorización?, ¿cuáles son los pasos a seguir?

                        La mejor forma de comprender el proceso es llevar a cabo una refactorización.
                Ejemplo de aplicación de una Refactorización, Refactorizar es una práctica, por lo que la mejor forma de entender esta técnica es aplicarla. Lejos de pretender enumerar aquí todos esos patrones, mostraremos uno de ellos a modo de ejemplo: Reemplazar los condicionales con polimorfismo.
                Esta refactorización nos convierte el tradicional autómata de estados realizado con “switchs” en algo extensible y orientado a objetos, más fácil de mantener y de arreglar al adoptar el patrón State. Para centrar ideas, supongamos que tenemos que actualizar un control de las puertas del suburbano de alguna ciudad. El problema es que está realizado a la antigua usanza, con un método que se encarga a base de “ifs” (que podemos ver como un caso degenerado de “switches” que funcionaría tanto con cadenas como con otro tipo de objetos) de pasar de un estado a otro:

                public class Metro {
                private int estado;
                static final private int PARADO = 0;
                static final private int EN_MARCHA = 1;
                static final private int PARANDO = 2;
                static final private int ARRANCANDO = 3;
                public void cambiaEstado() {
                if(estado==PARADO) {
                estado = ARRANCANDO;
                } else if(estado==EN_MARCHA) {
                estado = PARANDO;
                } else if(estado==PARANDO) {
                estado = PARADO;
                } else if(estado==ARRANCANDO) {
                estado = EN_MARCHA;
                }

                        else {
                throw new RuntimeException(“Estado desconocido”);
                }
                }
                }

                Tras cada paso de los que vamos a dar, ejecutaremos los tests para ver que se pasan convenientemente.
                Lo primero que deberíamos hacer sería separar las condiciones en un único método y subir dicho método lo más alto posible en la jerarquía. En nuestro ejemplo no es necesario dado que ya cumple las condiciones pedidas.
                A continuación creamos una clase por cada posible estado y sustituimos los “==” por “instanceof” y las variables estáticas enteras por referencias a instancias de esas clases.
                Obviamente, necesitaremos una interfaz base para nuestras clases de estado que por ahora será un simple marcador.

                Public interface Estado {}
                public class Parado implements Estado {}
                etc…
                public class Metro {
                private Estado estado;
                static final private Estado PARADO = new Parado();
                static final private Estado EN_MARCHA = new EnMarcha();
                static final private Estado PARANDO = new Parando();
                static final private Estado ARRANCANDO = new Arrancando();
                public void cambiaEstado() {
                if(estado instanceof Parado) {
                estado = ARRANCANDO;
                } else if(estado instanceof EnMarcha) {
                estado = PARANDO;
                } else if(estado instanceof Parando) {
                estado = PARADO;
                } else if(estado instanceof Arrancando) {
                estado = EN_MARCHA;
                } else {
                throw new RuntimeException(“Estado desconocido”);
                }
                }
                }

                Nuestro siguiente y penúltimo paso será proveer de control a las clases nuevas para que sean ellas las que decidan el paso de un estado a otro. Implementando el patrón Estado (las diferencias entre éste y el Estrategia son de interpretación), asignaremos un método a la interfaz creada que nos devuelva el siguiente estado, y daremos contenido a dicho método en las clases de estado:

                public interface Estado {
                public Estado siguiente();
                }

                public class Parado implements Estado {
                // Por eficiencia, implementamos ya el patrón singleton
                private static final Parado instance=new Parado();
                public static Parado getInstance() { return instance; }
                private Parado() {}
                public Estado siguiente() {
                return Arrancando.getInstance();
                }
                }
                etc…
                public class Metro {
                private Estado estado;
                public void cambiaEstado() {
                if(estado instanceof Parado) {
                estado = estado.siguiente();
                } else if(estado instanceof EnMarcha) {
                estado = estado.siguiente();
                } else if(estado instanceof Parando) {
                estado = PARADO;
                } else if(estado instanceof Arrancando) {
                estado = estado.siguiente();
                } else {
                throw new RuntimeException(“Estado desconocido”);
                }
                }
                }
                Por fin tenemos nuestro código casi refactorizado. Ahora nos falta eliminar los “ifs” inútiles y hacer desaparecer el “throw” que no se va a dar nunca si hemos implementado bien los distintos estados:
                public class Metro {
                private Estado estado;
                public void cambiaEstado() {
                estado = estado.siguiente();
                }
                }
                La refactorización ya ha terminado. Por la simplicidad del código que requiere un ejemplo escrito sería discutible si esta refactorización era necesaria. Pero en la práctica es muy habitual encontrar máquinas de estados más complejas codificadas en forma de switches muy difíciles de mantener. Esta refactorización demuestra cómo es posible organizar ese código paso a paso y con poco riesgo.
                Implantación de la refactorización
                Además de conocer los aspectos teóricos de la refactorización, también es importante profundizar sobre los aspectos más prácticos de la aplicación de esta técnica en un proyecto de desarrollo real.
                En el caso de comenzar un proyecto desde cero, la refactorización continua es una práctica que conlleva un gran número de beneficios y evita de forma natural algunos peligros mencionados. Sin embargo, no siempre se parte de cero, y lo habitual es encontrarnos con código cuyo diseño y/o estructura está lejos de ser los más apropiados. Esta situación puede darse cuando tenemos que ampliar la funcionalidad de código previamente existente o simplemente porque en un proyecto empezado desde cero no se detectó la necesidad de refactorizar a tiempo.. En estos casos nos encontramos con una
                refactorización a posteriori y deben tomarse medidas específicas para que afecte lo menos posible al ritmo normal de desarrollo.
                Pasos para implantar la refactorización en un equipo
                Implantar la refactorización debe realizarse de una manera progresiva. Nuestra experiencia en la implantación de esta técnica nos dice que es necesario seguir los siguientes pasos:
                * Escribir pruebas unitarias y funcionales. Refactorizar sin pruebas unitarias y funcionales resulta demasiado costoso y de mucho riesgo.
                * Usar herramientas especializadas (más información en [PerezFerrerColado60]). Las refactorizaciones en muchas ocasiones obligan a pequeñas modificaciones muy simples en muchas clases de nuestro código. Con herramientas especializadas estas refactorizaciones se realizan automáticamente y sin riesgo.
                * Dar formación sobre patrones de refactorización y de diseño. Una buena base teórica sobre las refactorizaciones más comunes permite al programador detectar “Bad Smells” de los que no es consciente y que harán que su código se degrade progresivamente.
                * Refactorizar los principales fallos de diseño. Nuestra recomendación es comenzar realizando refactorizaciones concretas que corrijan los principales fallos de diseño, como puede ser código duplicado, clases largas, etc que son refactorizaciones sencillas de realizar y de las que se obtiene un gran beneficio.
                * Comenzar a refactorizar el código tras añadir cada nueva funcionalidad en grupo. Una vez corregido los errores del código existente, la mejor manera de aplicar Refactorirzacion suele ser al añadir una nueva funcionalidad, de manera que se desarrolle de la manera mas eficiente. Realizar discusiones en grupos sobre la conveniencia de realizar alguna refactorización suele ser muy productivo.
                * Implantar refactorización contínua al completo. Esta es la última fase y es cuando cada desarrollador incorpora la Refactorizacion como una tarea mas dentro del su proceso de desarrollo de Software.
                Refactorización continua
                Refactorizar de forma continuada es una práctica que consiste en mantener el diseño siempre correcto, refactorizando siempre que sea posible, después de añadir cada nueva funcionalidad. Esta no es una práctica nueva pero esta adquiriendo mayor relevancia de mano de las metodologías ágiles..
                Dado que una refactorización supone un cambio en la estructura del código sin cambiar la funcionalidad, cuanto mayor sea el cambio en la estructura más difícil será garantizar que no ha cambiado la funcionalidad. Dicho de otra forma, cuanto mayor sea la refactorización, mayor es el número de elementos implicados y mayor es el riesgo de que el sistema deje de funcionar. El tiempo necesario para llevarla a cabo también aumenta y por tanto el coste se multiplica.
                Cuando un diseño no es óptimo y necesita ser refactorizado, cada nueva funcionalidad contribuye a empeorar el diseño un poco más. Por ello cuanto más tiempo esperamos mayor es la refactorización necesaria.
                Esta es la mayor de las justificaciones de una refactorización contínua, que trata de evitar las refactorizaciones grandes haciendo refactorizaciones pequeñas muy a menudo.
                En concreto se refactoriza de forma inmediata a la inclusión de una nueva funcionalidad. Esta práctica tiene dos ventajas principalmente:
                •El código afectado es el mismo que el que se modificó para añadir la funcionalidad y por tanto se reduce o evita completamente los inconvenientes para otros desarrolladores.
                •El tiempo que se debe dedicar es reducido dado que en ese momento se tiene un conocimiento preciso del código que se verá afectado.
                Las claves para poder aplicar refactorización contínua son:
                •Concienciación de todo el equipo de desarrollo.
                •Habilidad o conocimientos necesarios para identificar qué refactorizaciones son necesarias.
                •Compartir con todo el equipo de desarrollo la visión de una arquitectura global que guíe las refactorizaciones en una misma dirección.
                Lo más habitual es que este tipo de refactorizaciones surjan nada más implementar una funcionalidad y dejar los tests pasando satisfactoriamente. Estos mismos tests nos garantizarán que el código sigue funcionando después de la refactorización.
                Sin embargo no siempre resulta evidente para todos los desarrolladores la necesidad de una refactorización. En estos casos el uso de herramientas como javastyle y jcsc pueden emplearse como ayuda para identificar bad smells además de unificar criterios entre todo el equipo. En cualquier caso es importante que estas herramientas no sustituyan nunca al sentido común y se usen únicamente como ayuda.
                La refactorización continua también es una muy buena estrategia para evitar la proliferación de ventanas rotas. Este término, presentado por Dave Thomas y Andrew Hunt en The Pragmatic Programmer, representa todo aquel aspecto del código que un desarrollador percibe como negativo. Según Thomas y Hunt un desarrollador no tiene muchos escrúpulos en dejar una ventana rota más, en un código en el que él percibe o es consciente de muchas otras ventanas rotas. Sin embargo cuando el código no tiene ninguna, ningún desarrollador quiere ser el primero en romper una ventana.
                El principal riesgo de la refactorización continua consiste en adoptar posturas excesivamente exigentes o criterios excesivamente personales respecto a la calidad del código. Cuando esto ocurre se acaba dedicando más tiempo a refactorizar que a desarrollar. La propia presión para añadir nuevas funcionalidades a la mayor velocidad posible que impone el mercado es suficiente en ocasiones para prevenir esta situación.
                Si se mantiene bajo unos criterios razonables y se realiza de forma continuada la refactorización debe tender a ocupar una parte pequeña en relación al tiempo dedicado a las nuevas funcionalidades.
                El desarrollo dirigido por pruebas incluye de forma natural la refactorización continua, es lo que se conoce como TDD (Test Driven Development), práctica en la que se se hace la prueba, se añade la funcionalidad necesaria para satisfacerla, se generaliza la prueba y se refactoriza. La consecuencia inmediata de la aplicación de está práctica suele ser la evolución de la arquitectura inicial, en beneficio de un código bien estructurado, de una arquitectura orientada en todo momento a la funcionalidad implementada y por tanto, clara y sencilla, características necesarias para que un código sea mantinible y extensible.
                Dificultades de la refactorización en la práctica
                Si para afirmar que sabemos refactorizar basta con conocer los principales patrones y seguir una serie de sencillos pasos, para refactorizar con éxito es necesario tener en cuenta otros muchos factores propios de los proyectos de desarrollo reales.
                En un proyecto real hay que ser consciente de que no refactorizar a tiempo un diseño degradado puede tener consecuencias muy negativas, pero a la vez debe tener en cuenta que el tiempo dedicado a refactorizar no suele ser considerado como un avance del proyecto, por los usuarios, clientes o gestores del mismo. Otros factores clave para una correcta aplicación de la refactorización son la forma en que afecta a otros miembros del equipo, el hecho de que un desarrollador realice refactorizaciones sobre código en el que todos están trabajando, el que una refactorización provoque que el código esté mucho tiempo sin funcionar o el efecto negativo de no refactorizar, en el ánimo de los desarrolladores por la sensación de no estar haciendo un trabajo de calidad.
                Pero antes de tratar estos temas y cómo base para entender sus verdaderas implicaciones comenzaremos por tratar un factor raramente considerado en los libros o artículos sobre refactorización: el factor humano. Es decir, cómo afecta al desarrollador o equipo de desarrollo el refactorizar poco o demasiado, el hacerlo pronto o tarde, etc.
                El factor humano
                Una realidad a veces olvidada es que cualquier desarrollador prefiere hacer código de calidad. También es cierto que esta realidad a veces queda oculta debido a condiciones de presión excesiva o menosprecio del trabajo técnico.
                En este sentido refactorizar es una forma de mejorar la calidad y por tanto es una forma de hacer que el desarrollador esté más orgulloso de su trabajo. Puede decirse que la refactorización bien realizada, rápida y segura, genera satisfacción.
                Pero también hay un peligro en este hecho: el exceso de celo para conseguir un resultado de calidad puede llegar a suponer un número excesivo de refactorizaciones dando vueltas sobre diseños similares una y otra vez.
                Una posible solución consiste en mantener la refactorización bajo control, definir claramente los objetivos antes de comenzar a refactorizar y estimar su duración. Si se excede el tiempo planificado es necesario un replanteamiento. Quizá haya ocurrido que la refactorización era más complicada de lo esperado pero también es posible que se esté refactorizando más de lo necesario.
                Además de la evidente pérdida de tiempo, refactorizar demasiado suele llevar a dos efectos negativos. El primero es que la modificacion del codigo incremente la complejidad de nuestro diseño, que es justo el efecto contrario del que intentabamos lograr al refactorizar. Por otro, es habitual fomentar la sensación bien del desarrollador o bien de todo el equipo de que no se está avanzando. Una sensación que conduce a repercusiones anímicas negativas.
                Trabajo en equipo
                Un equipo de desarrollo debe ser uno. Todos los miembros del equipo deben conocer la arquitectura en cada momento, el estado actual, los problemas que tenemos y el objetivo que se busca. Una reunión diaria es una práctica que facilita la comunicación entre
                todos los miembros del equipo, y supone una práctica que proporciona un momento a todos los miembros del grupo para plantear sus ideas, dudas e inquietudes respecto al proyecto.
                Cuando un desarrollador refactoriza, afecta al resto del equipo. En este sentido, las refactorizaciones internas a una clase son menos problemáticas, pero con las arquitecturales es necesario tener mucho cuidado. Este tipo de refactorizaciones supone cambios en un gran número de ficheros y por tanto es probable que se afecte a archivos que están siendo modificados por otros desarrolladores. La comunicación y coordinación entre los afectados es fundamental para que esto no se convierta en un problema. Por tanto es necesario que en la reunión diaria del equipo (u otro mecanismo de comunicación equivalente) se planteen las refactorizaciones de este tipo. De esta forma se sabrá en todo momento quienes son los afectados y cual es el objetivo de la refactorización. De esta forma se promueve la colaboración de todos en la refactorización arquitectural que se va a llevar a cabo. A veces, el comentar con el resto del equipo una refactorización que parece muy necesaria, puede hacernos ver que lo que parecía una muy buena idea, no lo es tanto por otros factores que ven otros compañeros y de los que no eramos conscientes. Esto evita también refactorizaciones en sentidos contrarios propias de equipos en los que falta comunicación.
                Las refactorizaciones en sentidos contrarios suelen estar motivadas por tener cada miembro del equipo una idea diferente del diseño hacia el que debe dirigirse ese código. Este problema de comunicación puede tener repercusiones bastante negativas en el diseño resultante. Una vez ocurre debe volverse a una situación en la que todo el equipo comparta una misma visión de la arquitectura y diseño del sistema. Para ello la mejor manera es parar el desarrollo y reunir al todo equipo para alcanzar un acuerdo común. En estas reuniones es muy útil el uso de pizarras y diagramas UML si son conocidos por el equipo. Incluso los miembros del equipo que no se habían visto involucrados en la situación aprenderán de la experiencia para evitar que vuelvan a darse en el futuro.
                Una vez extraidas conclusiones, es posible que sea necesario realizar una refactorización más para devolver el código y el diseño a parámetros acordes con nuestros requisitos de calidad.
                Refactorización a posteriori
                En este apartado nos referimos con el término refactorización a posteriori a todas aquellos cambios estructurales que se realizan un tiempo después de la implementación de la funcionalidad existente. Existen varios motivos para encontrarse con esta situación. Algunos de ellos son:
                •Un equipo comienza a trabajar con código desarrollado por un equipo anterior que o bien no tiene buena calidad o bien no está preparado para que se incorporen nuevas funcionalidades.
                •Se ha aplicado refactorización continua pero la calidad del código o el diseño se ha degradado porque no se identificó a tiempo la necesidad de una refactorización o bien se equivocó la refactorización necesaria.
                Este tipo de refactorizaciones tiene un riesgo mucho mayor que los que nos encontramos con refactorización continua.
                Otra característica de estas refactorizaciones es que afectan no sólo al desarrollador o desarrolladores que van a aplicar la refactorización, sino a todos aquellos que trabajan con la parte del código afectada: por un lado no pueden trabajar con el código o deben
                hacerlo con mayor precaución durante un tiempo; y por otro cuando vuelvan a trabajar con él se encontrarán con un diseño desconocido.
                La mejor estrategia a seguir en estos casos es tratar de dividir la refactorización en el mayor número de pequeños pasos posible. Tras cada paso el código debe seguir funcionando sin fallos.
                Antes de abordar cada uno de estos pasos se debe comunicar a todas las personas afectadas por los cambios que se van a realizar. De esta forma se evita que cuando vuelvan a trabajar con ese código se encuentren con un panorama completamente desconocido. En ocasiones también es recomendable dar explicaciones después de la refactorización para explicar el diseño final. Si este tipo de explicaciones son necesarias con demasiada asiduidad, la solución no es no realizarlas sino identificar los motivos de tanta refactorización a posteriori. Probablemente sea necesario un mayor grado de refactorización continua.
                La refactorización por pasos tiene la ventaja de afectar menos al ritmo normal de desarrollo y permite ser intercalada con la implementación de nuevas funcionalidades. El principal inconveniente es que extiende en el tiempo la existencia de código imperfecto. Por ello siempre que sea posible debe completarse la refactorización en el menor tiempo posible.
                Cuando una refactorización es difícil técnicamente y lleva un tiempo considerable se corre el riesgo de entrar en lo que podríamos denominar la espiral refactorizadora. Esta situación ocurre cuando una refactorización se complica y conduce a nuevas refactorizaciones hasta que llega un momento en el que no es posible o es muy difícil estimar cuanto tiempo queda para terminar de refactorizar.
                Toda situación durante un desarrollo de software en la que no puede estimarse la finalización debe evitarse. En este caso la situación se ve agravada porque se frena la adición de nueva funcionalidad en el código que está siendo refactorizado durante un tiempo indeterminado.
                Hay varias medidas que pueden adoptarse para evitar caer en la espeial refactorizadora. La primera de ellas es declarar el objetivo de una refactorización antes de empezar. Si durante la misma aparece nuevo código susceptible de ser refactorizado debe dejarse sin modificar, por ahora sólo apuntaremos esta necesidad para no olvidarnos de hacer la refactorización más adelante. Cuando se considere más oportuno.
                Otra práctica muy útil consiste en introducir el código en el sistema de control de versiones antes de emprender cualquier refactorización que podamos considerar grande.
                Si cuando se está llevando a cabo una refactorización se dispersan los objetivos iniciales, se llevan muchas horas o incluso días sin tener un código que funciona o resulta imposible estimar cuanto queda para terminar es necesario asumir que la refactorización no va por buen camino. Para volver a la normalidad se debe devolver el código a un estado en el funcionara correctamente. Esto resulta muy fácil si se siguió el consejo anterior de comenzar con código guardado en el sistema de control de versiones. No deben tenerse reparos en tirar las modificaciones empezadas, estaremos cambiando un poco de tiempo de trabajo por recuperar una situación estable que nos permite seguir avanzando sin incertidumbres. Lo más importante será lo aprendido de la refactorización fallida, que nos será muy valioso para volver a intentarlo.
                La mejor forma de enfocar correctamente una refactorización y evitar caer en espirales
                de refactorizaciones, es conocer muy bien los patrones de diseño. Los patrones de diseño no son sino soluciones que funcionan para problemas comunes que todos encontramos cuando estamos desarrollando. Conocer perfectamente los patrones de diseño nos permite identificar y ver con claridad el objetivo de la refactorización, mejora la comunicación entre los miembros del grupo al compartir una base de conocimiento muy importante y además nos hace más eficientes en la resolución no sólo de problemas conocidos, sino también de problemas desconocidos al haber adquirido una forma mejor arquitecturada de enfocar los problemas.
                Refactorización y pruebas unitarias
                La existencia de pruebas automatizadas facilita enormemente las refactorizaciones. Los principales motivos son:
                •El riesgo de la refactorización disminuye, dado que es posible comprobar cuando esta concluye que todo sigue funcionando satisfactoriamente.
                •El desarrollador que realiza la refactorización puede comprobar que no ha estropeado la funcionalidad implementada por otros. Evita efectos colaterales.
                •El tiempo necesario para comprobar que todo sigue funcionando es menor lo que permite avanzar en pasos más pequeños si se desea.
                •Refactorizar sin tests es una actividad de alto riesgo y no debe hacerse salvo para las refactorizaciones más sencillas y realizadas con herramientas especializadas. En ocasiones incluso algunas refactorizaciones muy pequeñas pueden provocar la aparición de bugs muy difíciles de identificar a posteriori.
                Sin embargo las pruebas también pueden llegar a ser un estorbo a la refactorización si se emplean de forma inadecuada.
                En este sentido es muy importante resaltar la importancia de que las pruebas tanto funcionales como unitarias comprueben que el código bajo prueba funcione correctamente independientemente de cómo esté implementado. Cuanto mayor sea el acoplamiento de la prueba con la implementación, mayores serán los cambios que habrá que realizar en esta cuando se lleve a cabo.
                Pero incluso aunque no exista este acoplamiento las refactorizaciones grandes, que afectan al diseño del código significativamente, obligarán a realizar cambios en las pruebas. Esto provoca una situación incómoda, las pruebas que deben ser una ayuda se convierten en este caso en un estorbo a la refactorización. La mejor solución es prevenir aplicando refactorizaciones continuas y más pequeñas. Pero una vez nos encontramos con esta situación, es recomendable cambiar las pruebas antes o a la vez que el código. De esta forma las pruebas colaborarán guiando la refactorización.
                En este sentido también resulta útil pensar antes de refactorizar en cómo van a afectar a los tests y cómo podemos usarlos en nuestro beneficio en vez de dejar que se conviertan en un problema.

                Conclusiones
                El problema de abordar una refactorización es que supone un esfuerzo que no se ve compensado por ninguna nueva funcionalidad y por ese motivo muchas veces es complicado de justificar. Sin embargo si introducimos la refactorización como parte del desarrollo conseguiremos que nuestro código sea de calidad y la experiencia dice que
                este tiempo que vamos empleando a lo largo del desarrollo será recuperado con creces según el proyecto avance.
                Se puede afirmar que refactorizar nos facilita enormemente el mantenimiento de un código siempre correctamente estructurado. El hecho de que el código esté bien estructurado es una ventaja por su mantenibilidad y extensibilidad. Sin embargo, esta técnica que parece tan ventajosa, requiere de su aplicación continua, el empleo de pruebas que proporcionen seguridad a su aplicación un buen conocimiento de los patrones de diseño que van a permitir al desarrollador saber hacia donde camina y por último importantes dosis de comunicación con todos los miembros del equipo.

                Referencias
                [Fowler] Martin Fowler: Refactoring: Improving the Design of Existing Code. Addison-Wesley Co.[1999] http://www.refactoring.com
                [Wiki] Páginas Wiki sobre Refactorización

                http://c2.com/ppr/wiki/WikiPagesAboutRefactoring

                [IntelliJ] Uno de los mejores Entorno de Desarrollo Java para Refactorizar

                http://www.intellij.com

                [Refactoring] Página de Refactorización en Español.

                http://refactoring.blog-city.com

                [PerezFerrerColado58] Jesús Pérez, Jorge Ferrer, César Colado. “Introducción a la refactorización”, Mundo Linux nº 58. Revistas Profesionales.
                [PerezFerrerColado60] Jesús Pérez, Jorge Ferrer, César Colado. “Refactorización en la práctica”, Mundo Linux nº 60 (pendiente de publicación). Revistas Profesionales.

                Berly v/s Aero

                Para los Fans de lo GNU/Linux, observen esto,

                Como dice el video, para que quieres windows si tienes GNU/Linux??, lo quieres??, aqui esta, bajalo, pero comprueba tu version de linux

                Proyecto Berly

                Cambiar colores del MenuStrip y otros controles de windows

                Hola, o hi en inglish, mostrare un ejemplo de como cambiar el color de los menuStrip en VB.NET, ustedes haran la conversion a otros lenguajes, recuerden que esto se puede hacer en cualquier lenguaje aderido a la CLS (Common Languege Specification), no se si lo escribi bien, pero es la Especificacion comun del lenguaje, esto hace que todos los lenguajes de .net tengan soporte para las mismas librerias y “casi”, se logren los mismos resultados con cualesquiera lenguaje que se trabaje, siempre y cuando esten bajo la plataforma .NET.

                Bien a lo que nos convoca:

                1) Paso 1; Deberemos crear una clase de nombre (lo que quieran es su programa) Colores, como? asi:

                Public Class Colores

                End Class

                2) Paso 2; Ahora heredaremos la clase que trae .NET llamada “ProfessionalColorTable”, asi;

                Public Class Colores

                Inherits ProfessionalColorTable

                End Class

                3) Paso 3; Sobreescribiremos las propiedades que contiene la clase (o como dice .NET “Overrides”) y que establecen el color o los colores de los controles, se preguntaran y que a controles les puedes cambiar el color??, mas adelante, ahora sigamos;

                Public Class Colores

                Inherits ProfessionalColorTable

                Public Overrides ReadOnly Property ButtonCheckedGradientBegin() As System.Drawing.Color

                Get
                Return Color.AliceBlue

                End Get

                End Property

                End Class

                Como veran la propiedad ButtonCheckedGradientBegin es sobrescrivida, y es establecida a un color pasado en return, al momento de escribir property, veran como la super duper ayuda de MICROSOFT VISUAL STUDIO .NET 2005, o la version que tengan, les mostrara la lista completa que las propiedades que pueden modificar a su gusto, pero?? y cual corresponde a que parte del control??, esto talvez les ayudara:

                Nombre Descripción
                Propiedad pública ButtonCheckedGradientBegin Obtiene el color de inicio del degradado utilizado cuando se activa el botón.
                Propiedad pública ButtonCheckedGradientEnd Obtiene el color final del degradado utilizado cuando se activa el botón.
                Propiedad pública ButtonCheckedGradientMiddle Obtiene el color medio del degradado utilizado cuando se activa el botón.
                Propiedad pública ButtonCheckedHighlight Obtiene el color sólido utilizado cuando se activa el botón.
                Propiedad pública ButtonCheckedHighlightBorder Obtiene el color de borde que se va a utilizar con ButtonCheckedHighlight.
                Propiedad pública ButtonPressedBorder Obtiene el color de borde que se va a utilizar con los colores ButtonPressedGradientBegin, ButtonPressedGradientMiddle y ButtonPressedGradientEnd.
                Propiedad pública ButtonPressedGradientBegin Obtiene el color de inicio del degradado utilizado cuando se presiona el botón.
                Propiedad pública ButtonPressedGradientEnd Obtiene el color final del degradado utilizado cuando se presiona el botón.
                Propiedad pública ButtonPressedGradientMiddle Obtiene el color medio del degradado utilizado cuando se presiona el botón.
                Propiedad pública ButtonPressedHighlight Obtiene el color sólido utilizado cuando se presiona el botón.
                Propiedad pública ButtonPressedHighlightBorder Obtiene el color de borde que se va a utilizar con ButtonPressedHighlight.
                Propiedad pública ButtonSelectedBorder Obtiene el color de borde que se va a utilizar con los colores ButtonSelectedGradientBegin, ButtonSelectedGradientMiddle y ButtonSelectedGradientEnd.
                Propiedad pública ButtonSelectedGradientBegin Obtiene el color de inicio del degradado utilizado cuando se selecciona el botón.
                Propiedad pública ButtonSelectedGradientEnd Obtiene el color final del degradado utilizado cuando se selecciona el botón.
                Propiedad pública ButtonSelectedGradientMiddle Obtiene el color medio del degradado utilizado cuando se selecciona el botón.
                Propiedad pública ButtonSelectedHighlight Obtiene el color sólido utilizado cuando se selecciona el botón.
                Propiedad pública ButtonSelectedHighlightBorder Obtiene el color de borde que se va a utilizar con ButtonSelectedHighlight.
                Propiedad pública CheckBackground Obtiene el color sólido que se utilizará cuando se active el botón y se utilicen los degradados.
                Propiedad pública CheckPressedBackground Obtiene el color sólido que se utilizará cuando se active y seleccione el botón y se utilicen los degradados.
                Propiedad pública CheckSelectedBackground Obtiene el color sólido que se utilizará cuando se active y seleccione el botón y se utilicen los degradados.
                Propiedad pública GripDark Obtiene el color que se va a utilizar para los efectos de sombra del control (controlador de movimiento).
                Propiedad pública GripLight Obtiene el color que se va a utilizar para los efectos de resaltado del control (controlador de movimiento).
                Propiedad pública ImageMarginGradientBegin Obtiene el color de inicio del degradado utilizado en el margen de la imagen de un ToolStripDropDownMenu.
                Propiedad pública ImageMarginGradientEnd Obtiene el color final del degradado utilizado en el margen de la imagen de un ToolStripDropDownMenu.
                Propiedad pública ImageMarginGradientMiddle Obtiene el color medio del degradado utilizado en el margen de la imagen de un ToolStripDropDownMenu.
                Propiedad pública ImageMarginRevealedGradientBegin Obtiene el color de inicio del degradado utilizado en el margen de la imagen de un ToolStripDropDownMenu cuando se revela un elemento.
                Propiedad pública ImageMarginRevealedGradientEnd Obtiene el color final del degradado utilizado en el margen de la imagen de un ToolStripDropDownMenu cuando se revela un elemento.
                Propiedad pública ImageMarginRevealedGradientMiddle Obtiene el color medio del degradado utilizado en el margen de la imagen de un ToolStripDropDownMenu cuando se revela un elemento.
                Propiedad pública MenuBorder Obtiene el color que es el color de borde que se va a utilizar en MenuStrip.
                Propiedad pública MenuItemBorder Obtiene el color de borde que se va a utilizar con ToolStripMenuItem.
                Propiedad pública MenuItemPressedGradientBegin Obtiene el color de inicio del degradado utilizado cuando se presiona un ToolStripMenuItem de nivel superior.
                Propiedad pública MenuItemPressedGradientEnd Obtiene el color final del degradado utilizado cuando se presiona un ToolStripMenuItem de nivel superior.
                Propiedad pública MenuItemPressedGradientMiddle Obtiene el color medio del degradado utilizado cuando se presiona un ToolStripMenuItem de nivel superior.
                Propiedad pública MenuItemSelected Obtiene el color sólido que se va a utilizar cuando se seleccione un ToolStripMenuItem distinto del ToolStripMenuItem de nivel superior.
                Propiedad pública MenuItemSelectedGradientBegin Obtiene el color de inicio del degradado utilizado cuando se selecciona el ToolStripMenuItem.
                Propiedad pública MenuItemSelectedGradientEnd Obtiene el color final del degradado utilizado cuando se selecciona el ToolStripMenuItem.
                Propiedad pública MenuStripGradientBegin Obtiene el color de inicio del degradado utilizado en el control MenuStrip.
                Propiedad pública MenuStripGradientEnd Obtiene el color final del degradado utilizado en el control MenuStrip.
                Propiedad pública OverflowButtonGradientBegin Obtiene el color de inicio del degradado utilizado en el control ToolStripOverflowButton.
                Propiedad pública OverflowButtonGradientEnd Obtiene el color final del degradado utilizado en el control ToolStripOverflowButton.
                Propiedad pública OverflowButtonGradientMiddle Obtiene el color medio del degradado utilizado en el control ToolStripOverflowButton.
                Propiedad pública RaftingContainerGradientBegin Obtiene el color de inicio del degradado utilizado en el control ToolStripContainer.
                Propiedad pública RaftingContainerGradientEnd Obtiene el color final del degradado utilizado en el control ToolStripContainer.
                Propiedad pública SeparatorDark Obtiene el color que se va a utilizar para los efectos de sombra en el ToolStripSeparator.
                Propiedad pública SeparatorLight Obtiene el color que se va a utilizar para los efectos de resaltado en el ToolStripSeparator.
                Propiedad pública StatusStripGradientBegin Obtiene el color de inicio del degradado utilizado en el control StatusStrip.
                Propiedad pública StatusStripGradientEnd Obtiene el color final del degradado utilizado en el control StatusStrip.
                Propiedad pública ToolStripBorder Obtiene el color de borde que se va a utilizar en el borde inferior de ToolStrip.
                Propiedad pública ToolStripContentPanelGradientBegin Obtiene el color de inicio del degradado utilizado en el control ToolStripContentPanel.
                Propiedad pública ToolStripContentPanelGradientEnd Obtiene el color final del degradado utilizado en el control ToolStripContentPanel.
                Propiedad pública ToolStripDropDownBackground Obtiene el color de fondo sólido del ToolStripDropDown.
                Propiedad pública ToolStripGradientBegin Obtiene el color de inicio del degradado utilizado en el fondo de ToolStrip.
                Propiedad pública ToolStripGradientEnd Obtiene el color final del degradado utilizado en el fondo de ToolStrip.
                Propiedad pública ToolStripGradientMiddle Obtiene el color medio del degradado utilizado en el fondo de ToolStrip.
                Propiedad pública ToolStripPanelGradientBegin Obtiene el color de inicio del degradado utilizado en el control ToolStripPanel.
                Propiedad pública ToolStripPanelGradientEnd Obtiene el color final del degradado utilizado en el control ToolStripPanel.
                Propiedad pública UseSystemColors Obtiene o establece un valor que indica si utilizar SystemColors en lugar de colores que coincidan con el estilo visual actual.

                por otro lado, y como les dije esta clase de framework .net, le proporciona los colores utilizados para los elementos de visualización de Microsoft Office, y como usar la clase que hemos hecho??, bien miren:

                4) Paso y (y ultimo): para usar la clase con las definiciones de colores que esten dispuestos a cambar (los colores que no definan, se usara los que esten establecidos por defecto en la case “ProfessionalColorTable”, no piensen que quedan en color blanco), para usarlo, usaremos un ejemplo bien practico, en el evento load de un formulario y supuniendo que tenemos un menu llamado “main_menu”, miren, asi;

                Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
                main_menu.Renderer = New ToolStripProfessionalRenderer(New Colores)
                End Sub

                Como veran se crea una nueva instancia de la clase Colores la quien heredo de la clase “ProfessionalColorTable”, instancia la cual fue pasada como parametro al constructor de la case “ToolStripProfessionalRenderer” y el resultado de esto se paso a la propiedad renderer del objeto main_menu, el cual es una propiedad de tipo ToolStripRenderer, la cual es clase base abstracta de las anteriores, como ven calza todo, :) , espero que les sirva, pues cuando quise hacer esto, no enconstraba informacion de esto, pero como buen chileno, busco en todas partes menos donde se debe si tiene el MSDN Visual Studio .NET miren esto

                ms-help://MS.VSCC.v80/MS.MSDN.v80/MS.VisualStudio.v80.es/dv_fxmclictl/html/94e7d7bd-a752-441c-b5b3-7acf98881163.htm

                Ejecuntenlo desde menu->ejecutar y hasta la proxima, chao ;)

                Mi Primer Post

                Hola a todos, aqui comienza mi vida en internet, mi primer post, espero que le sea de ayuda, la informacion que aqui, tratare de dar a conocer al mundo, gracias a todos por sus visitas, y no olviden de dejar sus comentarios..

                Seguir

                Get every new post delivered to your Inbox.