Curso de Filosofía elemental (29)

XXXVI

Las sociedades laborales

 

177. LA SOCIEDAD GREMIAL O SINDICAL COMO MEDIO DE FORMACIÓN, PROTECCIÓN Y DEFENSA EN CADA ACTIVIDAD PROFESIONAL

   Los hombres no se asocian sólo por razón de vecindad o localización, sino también por el trabajo o prefesión que ejercen, causa de relaciones estables y de intereses comunes. Surge así la asociación laboral, que recibe diferentes nombres según los lugares y épocas: gremio, sindicato, colegio profesional, etc. Finalidades de estas asociaciones son: la defensa colectiva y corporativa frente a las presiones exteriores o interferencias de otros grupos, el establecimiento de las condiciones de trabajo y la mutua ayuda de sus miembros.
   A lo largo de la Edad Media fueron surgiendo en la sociedad europea estas agrupaciones gremiales, que llegaron a tener gran fuerza y arraigo. El gremio, sobre defender corporativamente a la clase, regulaba las condiciones de trabajo y el acceso al mismo, el aprendizaje y la titulación profesional, los precios de las mercancías, y eran al mismo tiempo cofradías religiosas y organismos de previsión bajo la forma de mutualismo.

178. EL PROBLEMA SOCIAL

   A principios del siglo XIX, y como consecuencia de la Revolución francesa y de las ideas individualistas y del libre cambio, se suprimieron los gremios históricos y se declaró la libertad de contratación o de trabajo.
   Se suponía entonces que los gremios eran una rémora o dificultad para el progreso industrial y económico. Consecuencia de su supresión fue el privar a la clase más débil de su única fuerza posible, que es la asociación, y el que, de este modo, se extendiese en los nuevos ambientes industriales un pauperismo y una explotación del hombre en un grado desconocido en la época anterior. Surgía una nueva clase –el proletariado–, que se veía indefensa y a merced del poderoso en su necesidad de trabajo. No puede ser libre ni contratar en igualdad de condiciones quien, por carecer de reservas, no está en situación de esperar ni de imponer condiciones; así, la libertad de contratación del trabajo fue solamente teórica, pues, de hecho, el débil, privado de sus medios corporativos de defensa, quedaba a merced del poderoso, y sólo aprovecharía para la formación de la gran industria anónima con mano de obra asalariada.
   Aquí radica propiamene el problema social, típico de nuestra época. Por otra parte, con la supresión de señorios y la desamortización de bienes eclesiáasticos y comunales que fueron puestos en venta, apareció una nueva clase poderosa: la burguesía capitaista, que, amparada en esa supuesta igualdad y libertad de contratos de trabajo, no se reconocería con deberes de ningún género hacia quienes trabajaban en sus propiedades o empresas. Al mismo tiempo, con la descinvulación del capital respecto de familias y corporaciones fue haciéndose progresivamente anónima la propiedad y transformándose en la gran empresa moderna.
   En esta evolución, que se inició con la Revolución francesa, tuvieron mucha parte las ideas de la Ilustración y la Enciclopedia (siglo XVIII), especialmente las de J. JACOBO ROUSSEAU, autor de El Contrato Social. Según este autor, el hombre, de naturaleza racial, es bueno, y es la sociedad, con sus instituciones, poderes y costumbres irracionales, la que lo malea, obligándole a fingir y disimular sentimientos e inclinaciones. El postulaba una sociedad antigua (los gremios entre ellas), en la que el hombre, desvinculado y libre, contrataría en libertad con sus conciudadanos, sin que el poder estatal( emanado de la Voluntad General) velase más que por el derecho de todos, con lo cual recobraría progresivamente su inocencia primitiva.
   A lo largo del siglo XIX y del XX las condiciones económicas de los asalariados fueron aliviándose con el aumento de la producción industrial, que elevó los niveles de vida, y con la intervención del Estado en la reglamentación del trabajo, pero subsistieron el desarraigo y el descontento de una clase, la más numerosa, que no contaba ya con propiedad alguna, ni aun comunal, ni con un medio corporativo de defensa, ni siquiera con una relación personal con el patrono, convertido ahora en sociedades anónimas. La lucha de clases surge de esta situación social y espiritual.
   Durante esta época fueron renaciendo las asociaciones laborales –prohibidas por la ley liberal– bajo la sombra de los partidos políticos –asociaciones ideológicas, éstas si, autorizadas legalmente–, y así se amparó la formación de cooperativas, principalmente agrícolas, para la defensa de los campesinos modestos. Pero muchas de estas asociaciones, de carácter más político que profesional, se orientaban más a la lucha de clases y a la conquista del poder que a fines corporativos.
   En la actualidad, los gremios o sindicatos laborales han renacido en muchos países, pero no en forma libre y corporativa, sino bajo la tutela del Estado. En unos casos, este resurgir se orienta hacia una progresiva libertad de asociación corporativa, y en otros se convierte en instrumentos de dirigismo para los Estados socialistas y totalitarios.

179. EL DERECHO DE PROPIEDAD. EL SOCIALISMO

   El problema social, creado tanto por la supresión de gremios y la libre contratación del trabajo como por el fenómeno general de la industrialización, ha sido la gran cuestión de nuestra época, causa de innumerables conflictos laborales y de un mal estar permanente en la sociedad.
   Una escuela económica moderna —el socialismo— pretende resolver el problema social mediante la supresión de la propiedad privada, al menos en los bienes de producción (campos, industrias, etc.). La forma más conocida y extrema de socialismo es el marxismo, movimiento fundado por el alemán CARLOS MARX (1818-83) y extendido como régimen político y económico a numerosos países a partir de la revolución rusa de 1917.
   Según el socialismo, la propiedad privada, que era en otro tiempo vincular y familiar (unida estrechamente al trabajo), se convirtió después en capitalista y anónima (ajena al trabajo y explotadora del mismo), concentrándose progresivamente en grandes empresas o trusts. Al término de esta evolución el número de grandes capitalistas (poseedores de todas las fuentes de riqueza) será muy reducido, e inmenso el de los asalariados. Será el momento (según Marx) en que el Estado socialice todos los bienes de producción del país administrándolos por sí mismo en nombre de la comunidad.
   La solución socialista no tiene en cuenta que el derecho de propiedad es un derecho radicado en la naturaleza humana, la cual, lo mismo que exige que el hombre viva en sociedad –especialmente en familia– para su recto y normal desarrollo, exige también las condiciones necesarias para su existencia y libertad, entre ellas la propiedad privada. No puede el individuo ni la familia ser verdaderamente independiente y libre si no posee ni puede poseer bienes de reserva, y si depende de un único patrono o poder económico, aunque sea éste el Estado.
   La verdadera solución al problema social no ha de buscarse en la supresión de la propiedad privada –que entrañaría la violación de un derecho natural y la caída en males mayores de los que se trataría de evitar–, sino más bien en la extensión de la pequeña propiedad y en su vinculación al patrimonio familiar, transmisible de padres a hijos. El renacimiento de gremios o sindicatos a que no hemos referido puede ser un camino de solución del problema social en la industria, así como el sistema de cooperativas agrícolas (reunión de pequeños agricultores para el cultivo en común) puede serlo del problema agrario.

 

XXXVII

Sociedad religiosa

180. DIARQUÍA IGLESIA-ESTADO. SU FUNDAMENTO EN LOS FINES DEL SER HUMANO Y EN LOS MODOS DE ALCANZARLOS

   Todas las sociedades históricas –incluso las primitivas o salvajes– nos muestran en su seno una dualidad de poderes o jurisdicciones más o menos diferenciados: el poder civil y religioso. El rey o príncipe, de una parte, y el supremo sacerdote o poder religioso, de otra. Esto mismo sucede en nuestra civilización cristiana con la diarquía o dualidad armónica de poderes: Iglesia-Estado, o poder religioso y poder temporal.
   Esta dualidad de sociedades y de poderes se justifica por el ser y el destino del hombre. Si el hombre pudiese alcanzar su fin supremo por las solas fuerzas naturales, bastaría con un poder temporal que guiara y encauzase a una sociedad también temporal; es decir, la autoridad y la sociedad civiles. Pero el hombre necesita para salvarse –es decir, para alcanzar el fin a que está llamado– de la gracia, que se nos comunica a través de los sacramentos, que son pura y libre donación divina. La administración de estos sacramentos exige una comunidad espiritual dotada de auxilios y fines sobrenaturales, que es la Iglesia fundada por el mismo Cristo.
   Una y otra sociedad –Iglesia y sociedad civil o Estado– se llaman perfectas o resolutivas, a diferencia de otras formas de asociación que llamamos imperfectas (total o parcialmente subordinadas). La familia, el municipio, el gremio, por ejemplo, aunque tengan sus propios fines y autonomía, no cuentan con medios suficientes para realizar esos fines sin el concurso de un poder y de una sociedad más alta (el Estado) capaz de armonizarlos dentro del cuerpo social y de defenderlos respecto del exterior. Son sociedades naturales, inviolables en sus fines, pero imperfectas, insuficientes, llamadas por su misma naturaleza a integrarse en una sociedad más amplia que las complete y defienda sin destruirlas. El estado y la Iglesia, en cambio, se bastan por sí mismos dentro de sus fines: no necesitan integrarse en una sociedad más amplia ni ser suplidos y defendidos por ella. Son independientes y resolutivos en su esfera.

181. NATURALEZA Y MISIÓN DE LA IGLESIA

   BELLARMINO define a la Iglesia como una sociedad de personas que profesan la misma fe cristiana y participan de los mismos sacramentos bajo la dirección de sus legítimas autoridades, y principalmentee del Romano Pontífice. Para ser miembro de la Iglesia es necesario y suficiente estar bautizado, juntamente con el vínculo de la unidad de la fe y de la comunión católica.
   De acuerdo con lo que hemos dicho, la Iglesia es una sociedad perfecta, puesto que posee un fin propio, último e independiente (la santificación de los hombres para conducirles a su destino sobrenatural) y una autoridad adecuada de institución divina y diferente de la autoridad civil. Por ello mismo la Iglesia no puede recibir normas del poder civil ni depender de él, y tiene potestad para dictar a sus miembros las normas necesarias para el cumplimiento de su fin sobrenatural.
   El cristiano, en relación con la Iglesia de la que es miembro, ha de atenerse a estas verdades dogmáticas:

a) El mismo Jesucristo instituyó la Iglesia como comunidad de fieles, otorgándole el depósito de la fe y la administración de los sacramentos.

b) La instituyó en forma jerárquica al conferir a los apóstoles –y a los obispos como sus sucesores– el triple poder de magisterio para enseñar el contenido de la fe, de imperio para crear y mantener una disciplina, y de santificación en la administración de la gracia a través de los sacramentos.

c) Instituyó esa jerarquía en forma monárquica al establecer el Pontificado en la Primacía de Pedro.

182. INDEPENDENCIA DE LA IGLESIA RESPECTO AL PODER CIVIL

   Si la naturaleza y misión de la Iglesia son sobrenaturales, y es sociedad perfecta (resolutiva dentro de su fin), habrá de ser independiente respecto del poder civil. Por ello, el Codigo de Derecho Canónico establece que la Santa Sede es independiente de todo poder temporal (canon 218, $$ 1,2). Por ello, la Iglesia ha condenado (Concilio Vaticano I, sesión IV, canon 3.º) el llamado Regium exequator o pase regio, que es <<el derecho que quiere arrogarse la potestad civil de someter a su juicio las leyes de la Santa Sede, para darles o no el permiso de promulgación en un territorio>>.
   Aún más opuesta a la independencia de la Iglesia respecto al poder civil han sido los intentos de formar Iglesias nacionales (anglicanismo, galicanismo), total o parcialmente desligadas de Roma, y dirigidas o fuertemente influidas por los respectivos Estados. También se oponen a esa independencia las pretensiones totalitarias del Estado moderno, que se erige en fuente única de poder sobre la nación en todos sus aspectos y otorga a la Iglesia la consideración de una mera sociedad de derecho privado (semejante a una sociedad cultural o recreativa).

183.  RELACIONES DE LA IGLESIA Y EL ESTADO

   El hecho de que la Iglesia sea católica o universal, es decir, se halle extendida por todo el mundo, y que sus miembros o fieles sean al propio tiempo súbditos de las distintas nacionalidades, plantea el problema de las mutuas relaciones de dos potestades –Iglesia y Estado–, cuyo territorio y miembros no sólo se entremezclan, sino que a menudo coinciden estrictamente.
   En una sociedad idealmente cristiana, tal problema no existiría, puesto que su autoridad o príncipe sería asimismo súbdito de la Iglesia, y gobernaría con independencia en las materias de su competencia, pero con sumisión a la moral religiosa en el ejercicio del poder y en cuanto pudiera rozar aspectos de fe y costumbres. El poder del Príncipe en la antigua sociedad cristiana no se extendía a más que armonizar los diversos estamentos, corporaciones e instituciones que integraban la sociedad, uno de cuyos brazos era la Iglesia. No teniendo las pretensiones del Estado moderno sobre la organización de todas las funciones de la vida social (incluidas la enseñanza y la beneficencia, que tan ligadas se encuentran a las tareas apostólicas), antes bien, limitándose a ayudar y suplir esas funciones, la interferencia entre una y otra potestad, al menos en el plano teórico y prescindiendo de abusos concretos, era casi imposible. En vez de relación de poder a poder, existía complementación, esto es, autonomía de poderes dentro de sus fines y sumisión del poder civil al religioso en el seno de la Iglesia universal.
   El problema teórico de las relaciones de ambas potestades surge en el Estado moderno, debido, a algunos casos, a la heterogeneidad religiosa en el seno de las naciones y, en todo caso, al monopolio del poder y organización que el Estado se arroga dentro de la nación. La solución moderna a este problema suele hallarse en un concordato o pacto de poder a poder entre la Iglesia y el Estado, pacto en el cual se determinan sus relaciones recíprocas.
   Suelen diferir los concordatos según se trate de Estados que se declaren oficialmente católicos o de Estados laicos o no católicos:

A) En el primer caso, cuando la religión oficial del Estado es la católica, se establece una discriminación de funciones. Aun cuando teóricamente los actos humanos miran simultáneamente al orden natural y al sobrenatural, sin embargo, unos son más directamente sobrenaturales y otros tienen un fin más directamente temporal:
a) en las materias directamente sobrenaturales (administración de sacramentos, culto, predicación, etc.), la Iglesia ejerce una autoridad exclusiva. b) En las materias que se llaman mixtas –que miran por igual a lo temporal y a lo espiritual (enseñanza, beneficencia, costumbres, etc.), el Estado no puede legislar prescindiendo de la Iglesia, esto es, sin un previo acuerdo con ella.

B) En el segundo caso, cuando el Estado es laico o no católico, la Iglesia puede exigir no sólo que los católicos sean respetados como tales, sino que puedan desarrollar su vida y educación religiosa, incluidos los deberes de apostolado.

Anuncio publicitario

Fundamentos de programación con Módula-2 (18)

Tema 8
Metodología de Desarrollo de Programas (III)

 

Este tema completa el anterior, ampliando la metodología de desarrollo por refinamientos sucesivos con la posibilidad de usar subprogramas como técnica de abstracción.

A nivel metodológico, las funciones y procedimientos introducen la posibilidad de descomposición de un problema en subproblemas realmente independientes. De todas maneras se mantiene la unidad del programa, que sigue apareciendo como un único módulo principal en Modula-2.

8.1 Operaciones abstractas

Los subprogramas constituyen un primer paso hacia la metodología de programación basada en abstracciones. Los subprogramas permiten definir operaciones abstractas. El siguiente paso será la definición de tipos abstractos de datos, que se introducirán brevemente más adelante, en el Tema 14.

Una abstracción es una visión simplificada de una cierta entidad, de la que sólo consideramos sus elementos esenciales, prescindiendo en lo posible de los detalles. Las entidades que podemos abstraer para materializarlas como subprogramas son, en general, operaciones. Con la palabra operación englobamos tanto la idea de acción como la de función.

8.1.1 Especificación y realización
Al plantear operaciones abstractas habremos de definir dos posibles visiones. La visión abstracta o simplificada, y la visión detallada, completa. La visión abstracta es la que permite usar dicha operación sin más que conocer qué hace dicha operación. La visión detallada es la que define cómo se hace dicha operación, y permite que el procesador la ejecute. La primera visión representa el punto de vista de quienes han de utilizar la operación. Se dice que esa visión abstracta es la especificación o interfaz de la operación. La visión detallada representa el punto de vista de quien ha de ejecutar dicha acción, y se dice que expresa su realización o implementación. Resumiendo:

ESPECIFICACIÓN:          QUÉ HACE la operación (punto de vista de quien la invoca).

REALIZACIÓN:                CÓMO SE HACE la operación (punto de vista de quien la ejecuta).

En su forma más sencilla la especificación o interfaz consiste simplemente en indicar cuál es el nombre de la operación y cuáles son sus argumentos. En Modula-2 la especificación puede ser simplemente una cabecera de subprograma.

Esa forma simplificada de especificación indica solamente cuál ha de ser la sintasix o forma de uso de la operación. La especificación completa debe establecer también cuál es la semántica o significado de la operación. Para ello podemos añadir un comentario en que se indique qué relación hay entre los argumentos y el resultado de la operación.

La realización, por su parte, debe suministrar toda la información necesaria para poder ejecutar la operación. En Modula-2 la realización o implementación será la definición completa del subprograma, en forma de bloque de código.

Tomemos como ejemplo una función que calcule el máximo de dos números:

Especificación                { PROCEDURE Maximo2( a, b: INTEGER): INTEGER;
                                       (* Maximo2( a,b ) = Máximo de a y b *)

                                     / BEGIN
                                     |    IF a > b THEN
                                     |       RETURN a
Realización                    {    ELSE
                                     |       RETURN b
                                     |    END
                                     \END Maximo2;

Conociendo sólo la especificación podemos invocar la función, aunque no sepamos el detalle de cómo se realiza. Por ejemplo, podemos escribir:

alturaTotal := Maximo2( altura1, altura2 )

Si ahora sustituimos la realización de la función Maximo2 por otra diferente, tal como:

PROCEDURE Maximo2( a, b: INTEGER): INTEGER;
   VAR m: INTEGER;
BEGIN
   m := a;
   IF b > m THEN
      m := b
   END
   RETURN m
END Maximo2;

la especificación de la función sigue siendo la misma. La sentencia anterior que usaba la función sigue siendo siendo correcta:

alturaTotal := Maximo2( altura1, altura2 )

Con ello se pone de manifiesto la idea de que la especificación es una visión abstracta de qué hae la función, con independencia de los detalles de cómo lo hace.

8.1.2 Funciones. Argumentos
En programación la idea de función surge al aplicar el concepto de abstracción a las expresiones aritméticas. Una expresión representa un nuevo valor obtenido por cálculo a partir de ciertos valores ya conocidos que se usan como operandos.

Por ejemplo, el cubo de un número z se puede calcular multiplicando el número por sí mismo, en la forma zxzxz. De esta manera se puede obtener, por ejemplo, el volumen de un cubo escribiendo:

volumen := lado * lado * lado

La expresión lado * lado * lado suministra el valor del cubo del lado al evaluarla. Esta expresión puede verse de manera abstracta como una función, siendo el lado el dato de partida y el cubo el resultado obtenido. La abstracción de dicha expresión tendrá asociado un nombre que podamos identificar con el significado del cálculo, y que, obviamente, podría ser Cubo. Esto nos conduciría a la especificación:

PROCEDURE Cubo( z: REAL ): REAL;
   (* Cubo(z) = z^3 *)

Con esta especificación, el cálculo del volumen se reescribiría como:

volumen := Cubo( lado )

Esta visión abstracta prescinde de los detalles de cómo se calcula el resultado, con tal de que sea correcto, es decir, que se obtenga el cubo del argumento. La realización puede ser tan sencilla como:

PROCEDURE Cubo( z: REAL ): REAL
BEGIN
   RETURN z * z * z
END Cubo;

o tan artificiosa como:

PROCEDURE Cubo( z: REAL ): REAL;
   VAR c: REAL;
           k: INTEGER;
BEGIN
   c := 1.0;
   FOR k := 1 TO 3 DO
      c := c * z
   END;
   RETURN c
END Cubo;

Los operandos que intervienen en el cálculo del valor de la función y que pueden cambiar de una vez a otra se especifican como argumentos de dicha función. En el Tema anterior se han mencionado ya las dos formas disponibles en Modula-2 para el paso de los argumentos al subprograma que realiza el cálculo de la función. Si buscamos que el concepto de función en programación se aproxime al concepto matemático de función, el paso de argumentos debería ser siempre por valor. El concepto matemático de función es una aplicación entre conjuntos, cuyo cómputo se limita a suministrar un resultado, sin modificar el valor de los argumentos.

Aunque algunas veces, por razones de eficiencia, pueda ser aconsejable pasar por referencia argumentos de funciones, seguirá siendo deseable, para mantener la máxima claridad en el programa, que la llamada a la función no modifique el valor de los argumentos.

Desde el punto de vista de claridad del programa, y con independencia de cuál sea el mecanismo de paso de argumentos empleado, la cualidad más deseable al utilizar funciones es conseguir su transparencia referencial. Tal como se mencionó anteriormente, la transparencia referencial significa que la función devolverá siempre el mismo resultado cada vez que se la invoque con los mismos argumentos.

La transparencia referencial se garantiza si la realización de la función no utiliza datos exteriores a ella. Es decir, si no emplea:

  • Variables externas al subprograma, a las que se acceda directamente por su nombre, de acuerdo con las reglas de visibilidad de bloques.
  • Datos procedentes del exterior, obtenidos con sentencias de lectura.
  • Llamadas a otras funciones o procedimientos que no posean transparencia referencial. Las sentencias de lectura son en realidad un caso particular de éste.

Estas restricciones se cumplen en el ejemplo anterior del cálculo del cubo de un número. Las funciones que cumplen la cualidad de transparencia referencial y que no producen efectos laterales o secundarios se denominan funciones puras.

8.1.3 Acciones abstractas. Procedimientos
De manera similar a como las funciones pueden ser consideradas como expresiones abstractas, parametrizadas, los procedimientos pueden ser considerados como acciones abstractas, igualmente parametrizadas. Un procedimiento representa una acción, que se define por separado, y que se invoca por su nombre.

Como acciones abstractas, podemos tener dos visiones de un procedimiento. La primera es la visión abstracta o especificación, formada por la cabecera del procedimiento y una descripción de qué hace dicho procedimiento, y la segunda es la realización, en que se detalla, codificada en el lenguaje de programación elegido, cómo se hace la acción definida como procedimiento.

Como ejemplo, definiremos la acción abstracta de intercambiar los valores de dos variables. La especificación podría ser:

PROCEDURE Intercambiar( VAR a, b: INTEGER );
           (* a’, b’ = b, a *)

Para escribir esta especificación hemos necesitado distinguir los valores de los argumentos (pasados por referencia) en dos momentos diferentes: al comienzo y al final de la ejecución del procedimiento. Los nombres con prima (‘) representan los valores finales. La expresión:

     a’, b’ = b, a

significa que la pareja de valores de los argumentos a y b, por este orden, al terminar la ejecución del subprograma, coincide con la pareja de valores de b y a, por este orden, al comienzo de la ejecución del subprograma.

Conociendo la especificación podemos ya escribir algún fragmento de programa que utilic este procedimiento. Si queremos ordenar dos valores de menos a mayor, podríamos escribir:

IF p > q THEN
   Intercambiar( p, q )
END

Para escribir este fragmento de programa no hemos necesitado saber cuál es la realización del procedimiento de intercambiar. Por supuesto, para tener un programa completo, que se pueda ejecutar, necesitamos escribir una realización válida. Por ejemplo:

PROCEDURE Intercambiar( VAR a, b: INTEGER );
   VAR x: INTEGER;
BEGIN
   x := a;
   a := b;
   b := x
END Intercambiar;

Al definir procedimientos no podemos limitarnos a usar sólo el paso de argumentos por valor. En programación imperativa las acciones consisten habitualmente en modificar los valores de determinadas variables. Por esta razón se considera normal que los procedimientos usen argumentos pasados por referencia.

De todas maneras conviene seguir una cierta disciplina para que los programas resulten claros y fáciles de entender. Para ello podemos recomendar que los procedimientos se escriban siempre como procedimientos puros, entendiendo por ello que no produzcan efectos laterales o secundarios. Con esto se consigue que la acción que realiza un procedimiento se deduzca en forma inmediata de la invocación de dicha acción. Se garantiza que un procedimiento cumple con esta cualidad si su realización no utiliza:

  • Variables externas al subprograma, a las que se accede directamente por su nombre, de acuerdo con las reglas de visibilidad de bloques.
  • Llamadas a otros subprogramas que no sean procedimientos o funciones puras.

Comparando esta lista de restricciones con la que se estableció para las funciones puras, se observa que hemos suprimido la exigencia de que el procedimiento no lea datos del exterior. En general esta lectura puede considerarse como una asignación de valor, que puede quedar suficientemente bien reflejada en la llamada, si los dispositivos o ficheros de entrada se mencionan explícitamente como argumentos.

De todas maneras es difícil establecer una disciplina precisa con recomendaciones sobre la definición y uso de procedimientos. Hay muchas situaciones en las que la claridad del programa aumenta, de hecho, si se usan procedimientos en que se acceda a variables globales. Así es posible evitar que haya que escribir repetidamente argumentos iguales en cada una de las llamadas al procedimiento. En particular, los procedimientos de lectura del módulo InOut omiten pasar como argumento el fichero de datos de entrada, y asumen por defecto una entrada principal de datos, predefinida.

8.2 Desarrollo por refinamiento usando abstracciones

La metodología de programación estructurada puede ampliarse con la posibilidad de definir operaciones abstractas mediante subprogramas. A continuación se describen dos estrategias de desarrollo diferentes, según qué se escriba primero, si la definición de los subprogramas, o el programa principal que los utiliza.

8.2.1 Desarrollo descendente
La estrategia de desarrollo descendente (en inglés, "Top-Down"), es simplemente el desarrollo por refinamientos sucesivos, teniendo en cuenta además la posibilidad de definir operaciones abstractas. En cada etapa de refinamiento de una operación habrá que optar por una de las alternativas siguientes:

  • Considerar la operación como operación terminal, y CODIFICARLA mediante sentencias del lenguaje de programación.
  • Considerar la operación como operación compleja, y DESCOMPONERLA en otras más sencillas.
  • Considerar la operación como operación abstracta, y ESPECIFICARLA, escribiendo más adelante el subprograma que la realiza.

Para decidir si una operación debe refinarse como operación abstracta habrá que analizar las ventajas que se obtengan, frente a codificación directa o descomposición de la operación en forma de un esquema desarrollado en ese punto del programa.

En general resultará ventajoso refinar una operación como operación abstracta, que se define en forma separada, si se consigue alguna de las ventajas siguientes:

  • Evitar mezclar en un determinado fragmento de programa operaciones con un nivel de detalle muy diferente.
  • Evitar escribir repetidamente fragmentos de código que realicen operaciones análogas.

El beneficio obtenido es, como cabría esperar, una mejora en la claridad del programa. Hay que decir que esto implica un costo ligeramente mayor en términos de eficiencia, ya que siempre se ejecuta más rápidamente una operación si se escriben directamente las sentencias que la realizan, que si se invoca un subprograma que contiene dichas sentencias. La llamada al subprograma representa una acción adicional que consume un cierto tiempo de ejecución.

Por el contrario, hay un aumento de eficiencia en ocupación de memoria si se codifica como subprograma una operación que se invoca varias veces en distintos puntos del programa. En este caso el código de la operación aparece sólo una vez, mientras que si se escribiesen cada vez las sentencias equivalentes el código aparecería repetido varias veces.

8.2.2 Ejemplo: Imprimir la figura de un árbol de navidad
Retomamos aquí el ejemplo desarrollado en el Tema 4. El objetivo es imprimir la silueta del árbol, tal como aparece a continuación:

    *
   ***
  *****
   ***
  *****
 *******
  *****
 *******
*********
    *
    *
    *
  *****

Los primeros pasos de refinamiento eran:

            Imprimir árbol —>
                    Imprimir copa
                    Imprimir tronco
                    Imprimir base

           Imprimir copa —>
                    Imprimir primeras ramas
                    Imprimir segundas ramas
                    Imprimir terceras ramas

Podemos observar la existencia de operaciones análogas, correspondientes a la impresión de los distintos fragmentos. Es relativamente sencillo darse cuenta de que cada una de las "ramas" de la copa del árbol es una figura trapezoidal. Por ejemplo, las "segundas ramas" aparecen dibujadas así:

                   ***
                  *****
                 *******

Esta figura geométrica es un trapecio simétrico. Lo mismo puede decirse de las otras "ramas". Puesto que cada vez se imprime una figura diferente, podremos definir esta acción como parametrizada, dando como argumentos la información necesaria para distinguir cada "rama" particular. Por ejemplo, podemos decidir que el único parámetro necesario es la anchura de la base superior, ya que todas las "ramas" tienen 3 líneas de altura, y cada una de estas líneas añade siempre un asterisco más a cada lado.

La especificación de la impresión de una "rama" como procedimiento se podrá redactar en la forma:

PROCEDURE ImprimirRama( ancho: INTEGER );
 (* Imprimir 3 líneas con ancho, ancho+2 y ancho+4 asteriscos *)

En cuanto a la impresión del tronco y la base, también cabe la posibilidad de considerarlas como operaciones análogas, en ambos casos un rectángulo de asteriscos. Los parámetros serían en este caso la anchura y altura del rectángulo. La especificación sería:

PROCEDURE ImprimirRectángulo( ancho, alto: INTEGER );
 (* Imprimir un rectángulo de ancho x alto asteriscos *)

Con esto se podría escribir ya el programa principal, en el que podemos agrupar la impresión de las ramas en un esquema de bucle.

BEGIN
   (*– Imprimir copa –*)
      FOR rama := 1 TO 5 BY 3 DO
         ImprimirRama( rama )
      END;
   (*– Imprimir tronco –*)
      ImprimirRectangulo( 1, 3 );
   (*– Imprimir base –*)
      ImprimirRectangulo( 5, 1 );
END Arbol.

Ahora falta escribir la realización de las operaciones abstractas especificadas anteriormente. Forzando quizá un poco la idea de buscar operaciones análogas, se puede establecer una relación entre la impresión de las "ramas" y las del tronco o la base. En efecto, un rectángulo puede considerarse como un caso particular de un trapecio. Tanto la operación de ImprimiRama como la de ImprimirRectangulo se pueden apoyar en una operación común de ImprimirTrapecio especificada en la forma siguiente:

PROCEDURE ImprimirTrapecio( ancho, alto, avance: INTEGER );
 (* Imprimir un trapecio de asteriscos, con base superior ‘ancho’, altura ‘alto’, y ‘avance’ asteriscos más a cada lado en cada nueva línea *)

Esta operación la desarrollamos mediante refinamientos:

           Imprimir trapecio —>
              FOR k := 1 TO alto DO
                 Imprimir una línea del trapecio
              END
           Imprimir una línea del trapecio —>
              Imprimir los blancos iniciales
              Imprimir los asteriscos

Para mantener la información del número de asteriscos a cada línea usaremos una variable anchura, que tomará inicialmente el ancho de la línea superior, y se irá incrementando después de imprimir cada línea. Los blancos iniciales se calculan cada vez, fijando como parámetro constante la posición del centro de línea.

Al desarrollar este procedimiento se observa una analogía entre la operación de escribir los espacios en blanco y la de escribir los asteriscos. Ambas operaciones se refinan como la operación abstracta de imprimir un mismo carácter un cierto número de veces. Para ello se especifica el procedimiento:

PROCEDURE ImprimirN ( c: CHAR; N: INTEGER );
 (* Imprimir ‘N’ veces seguidas el carácter ‘c’ *)

El programa completo, incluyendo todos los procedimientos, es el siguiente:

(****************************************************************************************************
*
*    Programa: ARBOL
*
*    Descripción:
*      Este programa imprime la silueta de un árbol de Navidad, hecha con asteriscos.
*
****************************************************************************************************)
MODULE Arbol;

(*===================================================================================================
    IMPORTACIÓN Y DECLARACIONES DEL PROGRAMA
===================================================================================================*)
  FROM InOut IMPORT Write, WriteLn;
  CONST centro = 20;            (* centro de cada línea *)
  VAR rama: INTEGER;            (* contador de ramas *)

  PROCEDURE ImprimirN( c: CHAR; N: INTEGER );
  (* Imprimir ‘N’ veces seguidas el carácter ‘c’ *)
    VAR k: INTEGER;
  BEGIN
    FOR k:= 1 TO N DO
      Write( c )
    END
  END ImprimirN;

  PROCEDURE ImprimirTrapecio( ancho, alto, avance: INTEGER );
  (* Imprimir un trapecio de asteriscos, con base superior ‘ancho’,
       altura ‘alto’, y ‘avance’ asteriscos más a cada lado en
       cada nueva línea                                            *)
    VAR k: INTEGER;            (* contador de líneas *)
        anchura: INTEGER;        (* número de asteriscos *)

  BEGIN
    anchura := ancho;
    FOR k := 1 TO alto DO
      ImprimirN( ‘ ‘, centro – anchura DIV 2);
      ImprimirN( ‘*’, anchura );
      WriteLn;
      INC( anchura, avance*2)
    END
  END ImprimirTrapecio;

  PROCEDURE ImprimirRama( ancho: INTEGER );
  (* Imprimir 3 líneas con ancho, ancho+2, y ancho+4 asteriscos *)
  BEGIN
    ImprimirTrapecio( ancho, 3, 1)
  END ImprimirRama;

  PROCEDURE ImprimirRectangulo( ancho, alto: INTEGER );
  (* Imprimir un rectángulo de ancho x alto asteriscos *)
  BEGIN
    ImprimirTrapecio( ancho, alto, 0)
  END ImprimirRectangulo;

BEGIN

(*======================================================================================================
    PARTE EJECUTABLE DEL PROGRAMA
======================================================================================================*) 
  (*– Imprimir copa –*)
    FOR rama := 1 TO 5 BY 2 DO
      ImprimirRama( rama )
    END;
  (*– Imprimir tronco –*)
    ImprimirRectangulo( 1, 3 );
  (*– Imprimir base –*)
    ImprimirRectangulo( 5, 1 );
END Arbol.

Comparando esta redacción del programa con la que se había desarrollado en el Tema 4, se observa que el programa resulta ahora más largo, aunque cada parte separada del programa es más sencilla. En la versión anterior la parte ejecutable del programa principal era más compleja que ahora.

Con esta nueva redacción se obtiene una ventaja adicional, que se ha producido como efecto de la labor de abstracción realizada para especificar los subprogramas. Operaciones que antes se consideraban por separado, ahora se han refundido en una sola operación abstracta y parametrizada. La parametrización tiene la ventaja de que se facilita la modificación posterior del programa.

En efecto, si quisiéramos cambiar el programa para imprimir una figura de árbol algo diferente, en la versión inicial habría sido necesario cambiar casi toda la parte ejecutable del programa, sentencia por sentencia. Ahora la mayor parte del programa está constituida por las definiciones de las operaciones abstractas, que se pueden mantener sin cambios, y sólo hay que rectificar la parte del código del programa principal, que es relativamente corta.

Por ejemplo, podemos modificar el código del programa principal para imprimir un árbol más grande, tal como se indica en el programa ArbolGrande, donde los cambios se han destacado en letra negra.

Con la version inicial del programa habríamos tenido que escribir de nuevo al menos unas 13 líneas del programa. Ahora no ha sido necesaria ninguna línea nueva y tan sólo hemos tenido que retocar 3.

MODULE ArbolGrande;
. . . .
<< Definición de procedimientos >>
. . . .

BEGIN
   (*– Imprimir copa –*)
      FOR rama := 1 TO 9 BY 2 DO
         ImprimirRama( rama )
      END;
   (*– Imprimir tronco –*)
      ImprimirRectangulo( 3, 5 );
   (*– Imprimir base –*)
      ImprimirRectangulo( 9, 2 );
END ArbolGrande;

El resultado de la ejecución del programa ArbolGrande es el siguiente:

      *
     ***
    *****
     ***
    *****
   *******
    *****
   *******
  *********
   *******
  *********
 ***********
  *********
 ***********
*************
     ***
     ***
     ***
     ***
     ***
  *********
  *********

8.2.3 Ejemplo: Imprimir una tabla de números primos
En el ejemplo anterior se buscó de manera insistente la analogía entre operaciones y su especificación como operaciones parametrizadas. En este ejemplo se atenderá fundamentalmente a la limitación en el nivel de detalle.

El objetivo de este programa de ejemplo es imprimir una tabla con los números primos hasta un límite dado, formando varias columnas de números a lo ancho del listado. Si decidimos imprimir los números primos hasta 100, a cuaro columnas de 10 caracteres de ancho cada una, el resultado deberá ser el que aparece a continuación:

 1         2         3         5
 7        11        13        17
19        23        29        31
37        41        43        47
53        59        61        67
71        73        79        83

Los primeros pasos de refinamiento serán:

         Imprimir la tabla de números primos de 1 a n —>
            FOR k := 1 TO N DO
               Imprimir k, si es primo
            END

         Imprimir k, si es primo —>
            IF k es primo THEN
               Imprimir k, tabulando
            END

Ahora decidimos limitar el nivel de detalle, y definir como operaciones abstractas las que faltan por refinar. Sus especificaciones serían:

PROCEDURE EsPrimo( k: INTEGER ): BOOLEAN;
 (* Indica si ‘k’ es un número primo *)

PROCEDURE ImprimirTabulando( k: INTEGER );
 (* Imprimir ‘k’ tabulando a 4 columnas de 10 caracteres *)

A continuación podemos desarrollar la realización de estos subprogramas. La función que determina si un número es primo puede realizarse sencillamente probando todos los divisores posibles. Esta realización es poco eficiente, pero muy sencilla de programar.

PROCEDURE EsPrimo( k: INTEGER ); BOOLEAN;
(* Indica si ‘k’ es un número primo *)
   VAR d: INTEGER;     (* posible divisor *)
BEGIN
   FOR d := 2 TO k-1 DO  (* es igual de válido utilizar (k DIV 2) como cota superior *)
      IF k MOD d = 0 THEN
         RETURN FALSE
      END
   END;
   RETURN TRUE
END EsPrimo;

Para desarrollar la realización del procedimiento de imprimir tabulando hay que analizar algunas cuestiones previas. La especificación se ha establecido pasando como argumento solamente el número que hay que imprimir, reflejando de esta manera la forma natural en que se ha descrito esta acción abstracta. Sin embargo esta información es insuficiente para realizar la acción, ya que es necesario saber qué columna toca imprimir para poder decidir si hay que saltar de línea o no.

En este ejemplo se decide usar una variable global columna para mantener dicha información. La variable contendrá en cada momento el número de la columna en que aparecería escrito el próximo número si previamente no se saltase de línea.

El refinamiento de esta operación será el siguiente:

         Imprimir k, tabulando —>
            Saltar de línea, si es necesario
            Imprimir k y actualizar la columna

         Saltar de línea, si es necesario —>
             IF columna > 4 THEN
                WriteLn;
                columna := 1
             END

El programa completo, incluyendo la definición de todos los subprogramas necesarios, es el siguiente:

(****************************************************************************************************
*
*    Programa: PRIMOS
*
*    Descripción:
*      Este programa imprime una tabla de números primos, tabulando a cuatro columnas.
****************************************************************************************************)
MODULE Primos;

(*===================================================================================================
    IMPORTACIÓN Y DECLARACIONES DE PROGRAMA
===================================================================================================*)
  FROM InOut IMPORT WriteInt, WriteLn;

  CONST N = 100;                (* rango de números *)
  VAR columna: INTEGER;                (* columna a imprimir *)
  VAR K: INTEGER;                (* número a ensayar *)

  PROCEDURE EsPrimo( k: INTEGER ): BOOLEAN;
  (* Indica si ‘k’ es un número primo *)
    VAR d: INTEGER;            (* posible divisor *)
  BEGIN
    FOR d := 2 TO k-1 DO
      IF k MOD d = 0 THEN
        RETURN FALSE
      END
    END;
    RETURN TRUE
  END EsPrimo;

  PROCEDURE ImprimirTabulando( k: INTEGER );
  (* Imprimir ‘k’ tabulando a 4 columnas de 10 caracteres *)
  BEGIN
    IF columna > 4 THEN
      columna := 1;
      WriteLn
    END;
    WriteInt( k, 10 );
    INC( columna )
  END ImprimirTabulando;

BEGIN

(*=====================================================================================================
    PARTE EJECUTABLE DEL PROGRAMA
=====================================================================================================*)
  columna := 1;
  FOR K := 1 TO N DO
    IF EsPrimo( K ) THEN
      ImprimirTabulando( K )
    END
  END;
  WriteLn
END Primos.

8.2.4 Reutilización
La realización de ciertas operaciones como subprogramas independientes facilita lo que se llama reutilización de software. Si la operación identificada como operación abstracta tiene un cierto sentido en sí misma, es muy posible que resulte de utilidad en otros programas, además de en aquél para el cual se ha desarrollado. La escritura de otros programas que utilicen esa misma operación resulta más sencilla, ya que se aprovecha el código de su definición, que ya estaba escrito.

Aplicaremos esta idea a los subprogramas desarrollados para imprimir el árbol de Navidad. Las operaciones abstractas definidas allí permiten imprimir con bloques de asteriscos figuras trapezoidales, o simplemente rectangulares, de dimensiones variables. Cualquier figura que pueda descomponerse en secciones de estas formas se podrá imprimir fácilmente usando los procedimientos ya definidos.

Por ejemplo, si queremos imprimir la figura de una casa de juguete, tal como la siguiente:

     **
     **
  *********
 ***********
*************
  *********
  *********
  *********

sólo tendremos que escribir un fragmento de programa así:

MODULE Casa;
   . . . .
BEGIN
   ImprimirRectangulo( 2, 2 );
   ImprimirRama( 9 );
   ImprimirRectangulo( 9, 3 );
END Casa.

y copiar en la parte de declaraciones las definiciones de los procedimientos ya desarrollados en el programa árbol de Navidad.

A continuación se presentan más ejemplos, que aprovechan subprogramas desarrollados de antemano.