Saludos a todos.
Espero que os encontréis bien y que todo lo que se va conociendo en cuanto a novedades os parezca al menos motivador y atractivo. Y que, en definitiva, os guste. A mi sí me parecen muy interesantes y atractivas muchas de esas novedades.
🙂
Tras unas semanas en las que he estado algo cargado de trabajo, he encontrado este hueco para retomar el blog y compartir con vosotros algunas ideas en las que sigo trabajando:
Sigo intentando ponerme al día en el tema de las novedades porque es lo que toca ahora, dado que se ha hecho la presentación del producto y parece natural dedicarle tiempo a esas mejoras y cambios que existen. Y en ese decantarse por algo, quizás mi esfuerzo lo estoy dedicando mas a ponerme al día en lo que hace referencia a datasnap, intentando recabar la máxima información que sea útil de cara a compartirla con vosotros.
En ese sentido, y sin dar una confirmación de fechas ni compromiso, compartiré con vosotros que me planteé la posibilidad de organizar de aquí en adelante, lo que yo llamaría pequeños talleres de trabajo, complementando excepcionalmente algunas de las entradas que pudieran requerirlos. Era algo sobre lo que ya había valorado tiempo atrás e incluso lo había dejado entrever en alguna entrada, pero que nunca se pudo llevar adelante por falta de medios adecuados que ahora, afortunadamente, sí podría disponer.
Vendrían a ser como grupos reducidos de un máximo de 10 personas y me permitiría de forma puntual, tener un contacto más directo y personal con muchos compañeros que me han requerido consultas de forma privada a través del correo. Estos talleres girarían alrededor de alguna entrada del blog y serían eminentemente prácticos y con un despliegue de contenido muy básico, orientado a la persona que se inicia y siguiendo la linea de apoyo que siempre ha estado desde el comienzo en el blog. El título lo dice claramente: Delphi Básico. Intento que esa idea sea nuestra guía.
🙂
No. No es una idea novedosa ya que siempre ha estado en la mente de todos los que participamos, de una forma o de otra: el apoyo a los compañeros que empiezan, pero me parece importante la idea de tener ese recurso disponible dentro de lista de posibles vías de comunicación, que vamos consolidando año a año en el blog. Mi aspiración es la de ser al menos buen comunicador.
Eso sí, quisiera que quedara claro que no son cursos ni seminarios on line de la forma en que podemos entender tradicionalmente, sino algo mas interactivo y funcional, requiriendo de las personas que participen un pequeño esfuerzo, dado que la idea es que sean «participativos». Y comentaba esto relacionándolo con datasnap porque quizás el primer taller que en mi opinión debería abordarse, sería el que haría referencia a las entradas de la serie «Un día con los mayores», de forma que se pudiera ver a la luz de las novedades existen actualmente. Estoy en ello ahora mismo. 😀 Haciendo cambios y pruebas y preparando el material y los recursos que nos puedan hacer falta.
De acuerdo a la experiencia de ese primer taller o sucesivos, si se ve que es algo realmente útil y no es una pérdida de tiempo, se intentarían repetir y extender a otras entradas o contenido que juzgáramos de interés. Sobre los detalles os iré informando desde el grupo de facebook, Delphi Solidario, dado que va a ser uno de los requisitos para poder participar. Mas adelante, cuando el proyecto de DelpHispano esté avanzado ya estudiaremos cómo integramos estas iniciativas en él.
Esta semana hice un alto en el tema de datasnap a raíz de leer una de las entradas, magnificas como siempre, de Rodrigo Ruz, donde se comentaban algunos cambios en el lenguaje, en el sentido de que habían sido ampliado el ámbito de los helpers extendiéndolo a los tipos simples. La resaltaba hace algunos días en el muro del grupo de facebook y una cosa y otra me llevó a recabar información sobre estos puntos y mas concretamente sobre la RTL.
Por tanto, creo que podemos empezar a ver las novedades haciendo una pequeña parada en la rtl y comentando algunos de los cambios que trae la nueva versión.
Cito textualmente, lo que podemos leer en la docwiki de Embarcadero en la página de novedades de XE3 en lo que conscierne a las novedades en la RTL
What’s New in Delphi and C++Builder XE3
- Global Variables Deprecated; Use FormatSettings Instead: Twenty global variables in System.SysUtils, which have been deprecated for several years, have now been removed from the product. Examples: CurrencyDecimals, LongTimeFormat. For more information, see Global Variables.
- Refactoring: Some platform-neutral FireMonkey types and math functions have been moved from FMX to the RTL (in the System unit):
- From FMX.Types: Vector
- New: tagVECTOR, tagVECTOR3D
- From FMX.Types3D: TMatrix3D, Point3D, TQuaternion3D, TVector3D, Vector3D
- Methods in FMX.Types3D that work with TVector3D have been refactored. For example, FMX.Types3D.Vector3DAdd is now System.Types.TVector3D.AddVector3D.
- Some newly documented methods:
- InflateRect, IntersectRectF, MultiplyRect
- NormalizeRect, NormalizeRectF, UnionRectF
- RectCenter
- ScalePoint
- Three new helper types (TSingleHelper,TDoubleHelper, and TExtendedHelper) replace the following record types: TSingleRec, TDoubleRec, TExtendedRec, extensively.
A primera vista, creo que vale la pena detenernos en el primer párrafo, donde se nos anuncia algo que ya conocíamos y que directamente hemos compartido desde las entradas del blog. Recordad por ejemplo la entrada Taller práctico – EditMask-y-TFormatSettings. En dicha entrada, exponíamos el uso de la estructura TFormatSettings en el contexto de nuestro trabajo con el componente EditMask y las máscaras de formato numéricas, con decimales y separadores de millares, así como con fechas. Decíamos entonces que el hecho de haberse marcado como deprecated el uso de las variables globales, hacía recomendable incorporar la estructura TFormatSettings en su lugar e ir previniendo la sustitución progresiva de las que teníamos en desarrollos mas antiguos. Ahora, como veis, definitivamente se han eliminado del producto y aunque eso pueda visualizarse de forma inmediata como un simple error de compilación (en los primeros pasos de una migración), reflexionando sobre ese punto quizá nos haga pensar que determinadas rutinas que funcionaban correctamente en nuestro código al estar definida cualquiera de las variables globales, ahora puedan fallar y generar errores sencillos pero no predecibles de forma inmediata. Pienso que es algo que deberíamos tener en cuenta y por eso lo resalto.
En segundo lugar, vemos en los dos siguientes puntos de la cita, como nuestra RTL se va enriqueciendo con nuevas funciones, fruto de ese proceso imparable y progresivo de adaptación y convivencia de la tradicional VCL con la reciente plataforma de firemonkey (FMX). Eso era algo predecible y natural (y además deseado por múltiples razones): algunas funciones y tipos definidos inicialmente en el ámbito de la segunda, con esa característica valiosa como es el que fueran neutros a la plataforma, se están acomodando en zonas comunes y así podemos ver enriquecida nuestra rtl por ejemplo como el caso que nos ocupa con funciones relacionadas con vectores y matrices. Asimismo, y en ese sentido, se os refieren también algunas nuevas funciones que se han documentado, por lo que, puede ser interesante seguir algunos de estos enlaces y analizar si cubre alguna necesidad pendiente o puede resolver problemas que tuviéramos tiempo atrás.
Vamos a tomar como ejemplo los cambios que hacen referencia a TVector:
Inicialmente, la RTL de XE2 ya tenia declaradas las estructuras de los registros TVector y TMatriz. Las podíamos encontrar en la unidad System.Types (..EmbarcaderoRAD Studio9.0sourcertlsysSystem.Types.pas) del directorio de instalación de XE2.
Así se nos mostraban definidos en la interfaz:
TVectorArray = array [0..2] of Single; TVector = record case Integer of 0: (V: TVectorArray;); 1: (X: Single; Y: Single; W: Single;); end; TMatrixArray = array [0..2] of TVector; TMatrix = record case Integer of 0: (M: TMatrixArray;); 1: (m11, m12, m13: Single; m21, m22, m23: Single; m31, m32, m33: Single); end;
Se declaraban como meras estructuras desprovistas de funcionalidad.
Por otro lado, teníamos que irnos a la unidad FMX.Types (..EmbarcaderoRAD Studio9.0sourcefmxFMX.Types.pas) del análogo directorio para encontrar las funciones que extendían el uso de éstas, ceñidas en ese momento al ámbito de firemonkey.
function Vector(const X, Y: Single; const W: Single = 1.0): TVector; overload; function Vector(const P: TPointF; const W: Single = 1.0): TVector; overload; function VectorTransform(const V: TVector; const M: TMatrix): TVector; function VectorAdd(const v1: TVector; const v2: TVector): TVector; function VectorSubtract(const v1: TVector; const v2: TVector): TVector; function VectorNorm(const V: TVector): Single; function VectorNormalize(const V: TVector): TVector; function VectorScale(const V: TVector; factor: Single): TVector; function VectorLength(const V: TVector): Single; function VectorDotProduct(const v1, v2: TVector): Single; function VectorAngleCosine(const v1, v2: TVector): Single; function VectorCrossProductZ(const v1, v2: TVector): Single; function VectorReflect(const V, N: TVector): TVector; function VectorAngle(const V, N: TVector): Single;
Ahora, en XE3, disponemos tambien de las estructuras (2D y 3D)
tagVECTOR = record case Integer of 0: (V: TVectorArray;); 1: (X: Single; Y: Single; W: Single;); end;
donde TVectorArray se define como
type TVectorArray = array [0..2] of Single;
tagVECTOR3D = record case Integer of 0: (V: TVector3DType;); 1: (X: Single; Y: Single; Z: Single; W: Single;); end;
Sin embargo, como explicábamos, en XE3 la estructura de registro TVector asume e implementa estas funciones que le eras propias en teoria (desde un punto de vista OO).
TVector = record private // sum of squares coords function Norm: Single; // 1 div square root, when square root is not null function RSqrt(const AValue: Single): Single; function Add(const AVector: TVector): TVector; function Subtract(const AVector: TVector): TVector; public constructor Create(const AX, AY, AW : Single); overload; constructor Create(const APoint : TPointF); overload; function Normalize: TVector; // proportional change vector's length function Scale(const AFactor: Single): TVector; // calc vector's length function Length: Single; // multiplying the vector's coordinates function DotProduct(const AVector: TVector): Single; function Reflect(const AVector: TVector): TVector; // get TPointF value function ToPointF: TPointF; class operator Add(const AFirstVector, ASecondVector : TVector) : TVector ; class operator Subtract(const AFirstVector, ASecondVector : TVector) : TVector ; class operator Equal(const AFirstVector, ASecondVector : TVector) : Boolean; class operator NotEqual(const AFirstVector, ASecondVector : TVector): Boolean; class operator Implicit(AVector : TVector) : TPointF; class operator Explicit(AVector : TVector) : TPointF; class operator Implicit(APointF : TPointF) : TVector; class operator Explicit(APointF : TPointF) : TVector; class operator Implicit(ASizeF : TSizeF) : TVector; class operator Explicit(ASizeF : TSizeF) : TVector; case Integer of 0: (V: TVectorArray;); 1: (X: Single; Y: Single; W: Single;); end;
Estos comentarios pueden ser extendidos al resto de cambios que citábamos y nos pueden ayudar a comprender en que sentido se pueden valorar estas mejoras ya que lejos del inconveniente de la recolocación del código, suponen en un futuro un diseño coherente y mas racional. No me cabe duda que es mas fácil encontrar una referencia a un método de un vector en la misma entidad que no en otro módulo independiente, lo cual incrementara nuestra productividad, al menos si ese espíritu y esfuerzo se mantiene en el tiempo. ¿Simbiosis…? No se la palabra exacta pero la vcl aporta experiencia y madurez mientras firemonkey aporta hetereogeneidad y juventud. Puede existir un intercambio beneficioso entre ambas plataformas a largo plazo.
Finalmente, si os parece, podemos comentar el último de los puntos, que toca de refilón un tema que especialmente me gusta: los cambios en el lenguaje. Hacemos referencia al reemplazo de los tipos TSingleRec, TDoubleRec y TExtendedRec, que ya han sido marcados con el atributo deprecated recomendando el uso de las nuevas estructuras que los sustituyen TSingleHelper, TDoubleHelper y TExtendedHelper.
Dicho así podría pasarnos inadvertido el tema, pero la observación de las mismas nos descubre en su definición de tipo como helpers de registro de un tipo nativo, algo que no era posible en las versiones anteriores por definición, valga la redundancia. Y eso abre nuevas vías en aras de la productividad. Haced una lectura de la entrada de Rodrigo, Exploring Delphi XE3 – Record Helpers for simple types – System.SysUtils.TStringHelper.
En su entrada, Rodrigo, resalta y habla de esta novedad y se recrea en el helper que extiende el tipo String (TStringHelper), que se incorpora en la unidad System.SysUtils de XE3. Ahora vamos a disponer de todas estas funciones adicionales ligadas al tipo de cadena, como si fuera un objeto.
En ese sentido, hay voces de expertos como Marco Cantú, que lo han recibido con buenos ojos, a tenor de sus comentarios en su web, en Delphi XE3: Record Helpers for Intrinsic Types. Os aconsejo su lectura, pues da detalles para conocer como ha evolucionado esta nueva «característica» del lenguaje.
Y si no os desanima verlo y escucharlo en inglés, hay un vídeo de Alister Christie, donde se puede ver en la práctica:
El enlace a la documentación oficial de embarcadero lo podeis encontrar en: http://docwiki.embarcadero.com/RADStudio/en/Class_and_Record_Helpers
Los enlaces que hemos compartido son muy claros y la verdad es que no es necesario extendernos mucho mas. No obstante, entiendo que algunos compañeros que se inician pueden tener confusión en cuanto al ámbito de aplicación del helper y de la «restricción» de acceso al mas cercano.
Si es así podemos poner dos ejemplos. Uno para una clase helper (que era una funcionalidad que ya existía para clases) y otro para un registro helper a un tipo nativo. De esa forma nos permite comparar ambos ya que el código es similar. Al final, entenderéis porque esta distinción.
THelperPersona = class helper for TPersona
Vamos a abrir nuestro entorno de desarrollo y seleccionamos un nuevo proyecto de tipo consola. Dicho proyecto lo vamos a guardar como TestClassHelper. Aunque podemos definir la clase en el mismo proyecto vamos a añadir una nueva unidad de código que guardaremos como UPersona.pas y en ella definimos una clase muy sencilla (TPersona) donde podemos fijar la edad.
Este es el código que he escrito para la misma.
unit UPersona; interface type TPersona = class private FEdad: Integer; procedure SetEdad(const Value: Integer); published property Edad: Integer read FEdad write SetEdad; end; implementation { TPersona } procedure TPersona.SetEdad(const Value: Integer); begin FEdad := Value; end; end.
Ahora podemos regresar al proyecto y nos aseguramos que en el uses del mismo, existe una referencia a la unidad anterior. Esto se debería hacer automáticamente por el entorno justo al momento en el que es añadida.
Os muestro el código que he escrito y tras ejecutarlo comentamos.
program TestClassHelper; {$APPTYPE CONSOLE} {$R *.res} uses System.SysUtils, System.Classes, UPersona in 'UPersona.pas'; type THelperPersona = class helper for TPersona function EdadToStr: String; end; TOtroHelperPersona = class helper (THelperPersona) for TPersona procedure CelebraCumpleaños; end; var Persona: TPersona; procedure TOtroHelperPersona.CelebraCumpleaños; begin Edad:= Edad + 1; end; { TMiPersona } function THelperPersona.EdadToStr: String; begin Result:= IntToStr(Self.Edad) end; begin Persona:= TPersona.Create; Persona.Edad:= 47; try try { TODO -oUser -cConsole Main : Insert code here } WriteLn('Mi edad actual es '+ Persona.EdadToStr); Persona.CelebraCumpleaños; WriteLn('Al celebrar mi cumpleaños tendré '+ Persona.EdadToStr); //---------------------------------------------------------------- WriteLn('Pulsa cualquier tecla para salir...'); ReadLn; except on E: Exception do Writeln(E.ClassName, ': ', E.Message); end; finally Persona.Free; end; end.
Lo ejecutamos y comprobamos que todo es correcto.
Para ver lo que intento resaltar teneis que daros cuenta que ambos métodos tanto el que convierte en texto la edad como el que celebra el cumpleaños son visibles y accesibles desde nuestro codigo. Y no es porque residan juntos en el mismo modulo y se haya despertado cierta amistad entre ambos tipos. Si eliminais de la definicion de
TOtroHelperPersona = class helper (THelperPersona) for TPersona
precisamente el ancestro, dejando exclusivamente
TOtroHelperPersona = class helper for TPersona
emergerá un error en tiempo de compilación, dado que el método Persona.EdadToStr no es alcanzable y nuestro compilador muestra este problema (Undeclare identifier). En realidad lo que esta pasando es que sin la definición del ancestro, se sobrescribe el primero de ellos y tan solo permite acceder al citado. Y como éste no tiene definido este método, tenemos la oportunidad de comprender claramente que papel juega este ancestro en la relajación de la restricción de ámbito y de como este afectará a que podamos acceder o no a una clase helper existente.
Vamos al segundo de los ejemplos.
THelperInteger = record helper for Integer
Aquí vamos a hacer algo similar para los tipos nativos.
Creamos un nuevo proyecto de tipo consola y lo guardamos con el nombre TestRecordHelper.
Vamos a escribir unas lineas de código definiendo dos tipos record helper para el tipo nativo integer.
Os copio a continuación lo que he escrito, que es similar al ejemplo anterior. Dos tipos y el posterior define como ancestro el previo. Siguiendo el mismo razonamiento anterior debería también funcionar.
program TestRecordHelper; {$APPTYPE CONSOLE} {$R *.res} uses System.SysUtils; type THelperInteger = record helper for Integer function IsOdd: Boolean; function Value: String; end; TOtroHelperInteger = record helper (THelperInteger) for Integer function Value: String; end; var i: Integer; { TMyInteger } function THelperInteger.IsOdd: Boolean; begin Result:= Odd(self); end; function THelperInteger.Value: String; begin Result:= IntToStr(self); end; { TMyOtherInteger } function TOtroHelperInteger.Value: String; type TNumero = 0..9; const numeros: array [TNumero] of String = ('Cero', 'Uno', 'Dos', 'Tres', 'Cuatro', 'Cinco', 'Seis', 'Siete', 'Ocho', 'Nueve'); begin if (self < 10) and (self >= 0) then Result:= numeros[self] else Result:= IntToStr(self); end; begin Randomize; try { TODO -oUser -cConsole Main : Insert code here } i:= Random(100); WriteLn('La variable i vale ' + i.Value); if i.IsOdd then WriteLn(#13'Es impar...') else WriteLn(#13'Es par...'); WriteLn('Pulse cualquier tecla para salir'); ReadLn; except on E: Exception do Writeln(E.ClassName, ': ', E.Message); end; end.
Al compilar descubrimos múltiples errores.
El primero de ellos es que la definición del ancestro, en los record helpers no funciona correctamente. He encontrado en ese sentido la incidencia registrada en QC de no hace muchos días
http://qc.embarcadero.com/wc/qcmain.aspx?d=107781
Lo cual, en principio lo tenemos que tener en cuenta ya que mientras no se solvente esto, si queremos utlizar los helpers que trae el sistema y que hemos resaltado (TStringHelper, TSingleHelper, etc..) no va a poder ser simultaneamente a los nuestros, rigiendo las reglas de alcance del ámbito.
Para que funcione el codigo eliminad la referencia al ancestro e intercambiar el orden tal que así.
... TOtroHelperInteger = record helper for Integer function Value: String; end; THelperInteger = record helper for Integer function IsOdd: Boolean; function Value: String; end; ...
Compilará sin problemas y mostrara una ventana como la de la imagen inferior.
Resumiendo…
He puesto los dos ejemplos para que os dierais cuenta que actualmente no es posible utilizar el ancestro en los registros helper. Al menos la incidencia está abierta aunque no existe comentario alguno sobre la misma. Por eso he puesto el primer ejemplo que nos mostrara como funcionaba y de forma, en teoría, debería permitirnos para extender los registros.
Existe un debate abierto sobre estos temas bastante interesante.
Espero y deseo que todos estos comentarios os hayan servido de algo.
Nos vemos en una próxima entrada.
Hola Salvador.
Finalmente he podido leer la entrada completa, que tenía impresa para poder revisarla con detalle.
Como siempre, me parece muy aclaradora y clara. La explicación sobre los «Helpers» y sus posibilidades muy útil.
Un saludo.
Me gustaMe gusta
Gracias Germán por el comentario.
Me alegra que te guste y que te parezca útil. Es lo que siempre intentamos con mayor o menor acierto.
😉
Me gustaMe gusta