Continuamos con la introducción al lenguaje Pascal Moderno.
Esta entrada es una traducción al español, del texto original escrito por Michalis Kamburelis.
Contenido
Units
Las unidades le permiten agrupar cosas comunes (cualquier cosa que se pueda declarar), para que las usen otras unidades y programas. Son equivalentes a módulos y paquetes en otros idiomas. Tienen una sección de interfaz, donde declara lo que está disponible para otras unidades y programas, y luego la implementación. Guarde la unidad MyUnit como myunit.pas (en minúsculas con la extensión .pas).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
$mode objfpc}{$H+}{$J-} unit MyUnit; interface procedure MyProcedure(const A: Integer); function MyFunction(const S: string): string; implementation procedure MyProcedure(const A: Integer); begin WriteLn('A + 10 is: ', A + 10); end; function MyFunction(const S: string): string; begin Result := S + 'strings are automatically managed'; end; end. |
Los programas finales se guardan como archivos myprogram.lpr (lpr = archivo de programa Lazarus; en Delphi usaría .dpr). Tenga en cuenta que aquí son posibles otras convenciones, p. algunos proyectos solo usan .pas para el archivo de programa principal, algunos usan .pp para unidades o programas. Aconsejo usar .pas para unidades y .lpr para programas FPC/Lazarus.
Un programa puede utilizar una unidad mediante una palabra clave uses:
1 2 3 4 5 6 7 8 9 10 11 |
{$mode objfpc}{$H+}{$J-} program MyProgram; uses MyUnit; begin WriteLn(MyFunction('Note: ')); MyProcedure(5); end. |
Una unidad también puede contener secciones de inicialización y finalización. Este es el código que se ejecuta cuando el programa comienza y finaliza.
1 2 3 4 5 6 7 8 9 10 11 |
{$mode objfpc}{$H+}{$J-} unit initialization_finalization; interface implementation initialization WriteLn('Hello world!'); finalization WriteLn('Goodbye world!'); end. |
Unidades que se llaman unas a otras
Una unidad también puede usar otra unidad. Se puede usar otra unidad en la sección de interfaz, o solo en la sección de implementación. El primero permite definir cosas públicas nuevas (procedimientos, tipos…) además de las cosas de otra unidad. Este último es más limitado (si usa una unidad solo en la sección de implementación, puede usar sus identificadores solo en su implementación).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
{$mode objfpc}{$H+}{$J-} unit AnotherUnit; interface uses Classes; { The "TComponent" type (class) is defined in the Classes unit. That's why we had to use the Classes unit above. } procedure DoSomethingWithComponent(var C: TComponent); implementation uses SysUtils; procedure DoSomethingWithComponent(var C: TComponent); begin { The FreeAndNil procedure is defined in the SysUtils unit. Since we only refer to its name in the implementation, it was OK to use the SysUtils unit in the "implementation" section. } FreeAndNil(C); end; end. |
No está permitido tener dependencias de unidades circulares en la interfaz. Es decir, dos unidades no pueden usarse entre sí en la sección de interfaz. La razón es que para «comprender» la sección de interfaz de una unidad, el compilador primero debe «comprender» todas las unidades que utiliza en la sección de interfaz. El lenguaje Pascal sigue estrictamente esta regla y permite una compilación rápida y una detección completamente automática en el lado del compilador de las unidades que se deben volver a compilar. No hay necesidad de usar complicados archivos Makefile para una tarea simple de compilación en Pascal, y no hay necesidad de volver a compilar todo solo para asegurarse de que todas las dependencias se actualicen correctamente.
Está bien hacer una dependencia circular entre unidades cuando al menos un «uso» está solo en la implementación. Por lo tanto, está bien que la unidad A use la unidad B en la interfaz y luego la unidad B use la unidad A en la implementación.
Calificando identificadores con nombre de unidad
Diferentes unidades pueden definir el mismo identificador. Para mantener el código fácil de leer y buscar, por lo general debe evitarlo, pero no siempre es posible. En tales casos, la última unidad en la cláusula de usos «gana», lo que significa que los identificadores que introduce ocultan los mismos identificadores introducidos por unidades anteriores.
Siempre puede definir explícitamente una unidad de un identificador dado, usándolo como MyUnit.MyIdentifier. Esta es la solución habitual cuando el identificador que desea utilizar desde MyUnit está oculto por otra unidad. Por supuesto, también puede reorganizar el orden de las unidades en su cláusula de usos, aunque esto puede afectar otras declaraciones además de la que está tratando de arreglar.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
{$mode objfpc}{$H+}{$J-} program showcolor; // Both Graphics and GoogleMapsEngine units define TColor type. uses Graphics, GoogleMapsEngine; var { This doesn't work like we want, as TColor ends up being defined by GoogleMapsEngine. } // Color: TColor; { This works Ok. } Color: Graphics.TColor; begin Color := clYellow; WriteLn(Red(Color), ' ', Green(Color), ' ', Blue(Color)); end. |
En el caso de las unidades, recuerda que tienen dos cláusulas de uso: una en la interfaz y otra en la implementación. La regla de que las unidades posteriores ocultan las cosas de las unidades anteriores se aplica aquí de forma coherente, lo que significa que las unidades utilizadas en la sección de implementación pueden ocultar los identificadores de las unidades utilizadas en la sección de interfaz. Sin embargo, recuerde que al leer la sección de la interfaz, solo importan las unidades utilizadas en la interfaz. Esto puede crear una situación confusa, donde el compilador considera diferentes dos declaraciones aparentemente iguales:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
{$mode objfpc}{$H+}{$J-} unit UnitUsingColors; // INCORRECT example interface uses Graphics; procedure ShowColor(const Color: TColor); implementation uses GoogleMapsEngine; procedure ShowColor(const Color: TColor); begin // WriteLn(ColorToString(Color)); end; end. |
La unidad Graphics (de Lazarus LCL) define el tipo de TColor. Pero el compilador no podrá compilar la unidad anterior, alegando que no implementó un procedimiento ShowColor que coincida con la declaración de la interfaz. El problema es que la unidad GoogleMapsEngine también define un tipo TColor. Y se usa solo en la sección de implementación, por lo tanto, sombrea la definición de TColor solo en la implementación. La versión equivalente de la unidad anterior, donde el error es obvio, se ve así:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
{$mode objfpc}{$H+}{$J-} unit UnitUsingColors; // INCORRECT example. // This is what the compiler "sees" when trying to compile previous example interface uses Graphics; procedure ShowColor(const Color: Graphics.TColor); implementation uses GoogleMapsEngine; procedure ShowColor(const Color: GoogleMapsEngine.TColor); begin // WriteLn(ColorToString(Color)); end; end. |
La solución es trivial en este caso, simplemente cambie la implementación para usar explícitamente TColor desde la unidad de gráficos. También podría solucionarlo moviendo el uso de GoogleMapsEngine, a la sección de interfaz y antes que Gráficos, aunque esto podría tener otras consecuencias en casos reales, cuando UnitUsingColors definiría más cosas.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
{$mode objfpc}{$H+}{$J-} unit UnitUsingColors; interface uses Graphics; procedure ShowColor(const Color: TColor); implementation uses GoogleMapsEngine; procedure ShowColor(const Color: Graphics.TColor); begin // WriteLn(ColorToString(Color)); end; end. |
Exponer los identificadores de una unidad de otra
A veces desea tomar un identificador de una unidad y exponerlo en una nueva unidad. El resultado final debería ser que el uso de la nueva unidad hará que el identificador esté disponible en el espacio de nombres.
A veces, esto es necesario para preservar la compatibilidad con versiones anteriores de la unidad. A veces es bueno «ocultar» una unidad interna de esta manera.
Esto se puede hacer redefiniendo el identificador en su nueva unidad.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
{$mode objfpc}{$H+}{$J-} unit MyUnit; interface uses Graphics; type { Expose TColor from Graphics unit as TMyColor. } TMyColor = TColor; { Alternatively, expose it under the same name. Qualify with unit name in this case, otherwise we would refer to ourselves with "TColor = TColor" definition. } TColor = Graphics.TColor; const { This works with constants too. } clYellow = Graphics.clYellow; clBlue = Graphics.clBlue; implementation end. |
Tenga en cuenta que este truco no se puede hacer tan fácilmente con procedimientos, funciones y variables globales. Con procedimientos y funciones, podría exponer un puntero constante a un procedimiento en otra unidad (consulte Devoluciones de llamada (también conocidas como eventos, también conocidas como punteros a funciones, también conocidas como variables de procedimiento)), pero eso parece bastante sucio.
La solución habitual es entonces crear funciones triviales de «envoltura» que debajo simplemente llaman a las funciones desde la unidad interna, pasando los parámetros y devolviendo valores.
Para que esto funcione con variables globales, se pueden usar propiedades globales (a nivel de unidad), consulte Propiedades