Club Delphi  
    Paypal   FTP   CCD     Buscar   Trucos   Trabajo   Foros

Retroceder   Foros Club Delphi > Principal > Varios
Registrarse FAQ Miembros Calendario Guía de estilo Buscar Temas de Hoy Marcar Foros Como Leídos

Coloboración Paypal con ClubDelphi

 
 
Herramientas Buscar en Tema Desplegado
  #8  
Antiguo 29-01-2017
Avatar de AgustinOrtu
[AgustinOrtu] AgustinOrtu is offline
Miembro Premium
NULL
 
Registrado: ago 2013
Ubicación: Argentina
Posts: 1.858
Poder: 17
AgustinOrtu Es un diamante en brutoAgustinOrtu Es un diamante en brutoAgustinOrtu Es un diamante en brutoAgustinOrtu Es un diamante en bruto
Hola Mario,

Cita:
Es por eso que los nulos se consideran el "error del billon de dolares". La unica forma (sana) es ELIMINAR POR COMPLETO los nullos.

Eso no sirve en Delphi, ya tiene demasiada historia.

Igual, el resto de cosas son mas de lenguajes funcionales, y una vez que un lenguaje se estabiliza en un paradigma, intentar alterarlo solo hace todo mas confuso.
Estoy de acuerdo con el error de billon de dolares. El tema es que para ciertos casos puede ser util. Muchos diran que se puede resolver usando clases (el null object), es decir crear una clase en donde este definido el comportamiento "nulo" (muchas veces es "no hacer nada"). Esto me parece muy bien y de hecho lo he usado muchisimo. Pero en ciertos casos resulta engorroso de escribir, leer, entender: (esto tambien toma ideas de primitive obsession)

Código Delphi [-]
procedure DoQuery;
var
  DateForm, DateTo: TDateTimeClass;
begin
  DateFrom := TDateTimeClass.CreateOrDefault(Edit1.Text);
  DateTo := TDateTimeClass.CreateOrDefault(Edit2.Text);
  Query.SQL.Text := 'SELECT * FROM Orders WHERE Date BETWEEN :From AND: :To ';
  Query.ParamByName('From').AsDateTime := DateFrom.Value; 
  Query.ParamByName('To').AsDateTime := DateTo.Value; 
end;

TDateTimeClass parseara el string y creara la instancia de TDateTime, y la hace accesible a partir de la propiedad .Value. Una instancia de TDateTimeClass garantiza que el valor de TDateTime es correcto dentro de las reglas de negocio.

Esto lleva muchos problemas como determinar cual es el valor mas adecuado para representar el "nulo". Ademas, la idea del nulo es asignar un valor para que "de manera conveniente" cuando se use Value me de un valor que me sirva como "nulo". En caso de fechas podria devolver la minima fecha representable. Entonces en el SQL me quedaria un between 31/12/1899 and 31/12/1899, lo que no me devuelve el resultado esperado. Entonces tengo que meter otro constructor especificamente para las "FechaHasta" que asigne como nulo por ej la fecha 31/12/2200 23:59:59:999.

Ya no son una sino dos clases para una "pavada". Ademas tengo que usar try-finally cuando antes no era necesario, para liberar memoria. Si usara interfaces es cierto, me ahorro el try-finally, pero la complejidad sigue aumentando y ademas eso es cada vez mas y mas overhead. Y por ultimo el .Value "contamina la API", ya que en Delphi no podemos sobrecargar los operadores en clases (ok, se puede usando features que "no existen")

El Nullable<T> como "first type citizen" soluciona este problema ya que no contamina la API y ademas como es un record y con sus operadores sobrecargados, lo hace tan limpio que no se nota. Simplemente te ayuda a prevenir errores tontos y dificiles de encontrar (bueno quiza con un Unit Test lo detectes en segundos) porque te olvidaste de asignar un valor primitivo, o justo de lo contrario, de "limparlo"

Aca tambien tenemos mas influencias de programacion funcional en Delphi (por ejemplo: "Monads" o el "Either")

Yo creo que la era en la que OOP debe ser la respuesta a todo ha llegado a su fin. Si un lenguaje queda "atado" o estabilizado a un paradigma no podria considerarlo un lenguaje "moderno". Por suerte no es el caso de Delphi. En Delphi podes programar estructurado, procedimental, orientado a objetos, orientado a aspectos, y "emular" o tomar ideas de funcional

Funcional realmente hace el codigo mas facil de entender, de leer, de mantener y de extender porque separa el "que se hace" del "como se hace". Un ejemplo pavo, dadas estas declaraciones:

Código PHP:
uses
  System
.SysUtils,
  
Spring,
  
Spring.Collections;

type
  Customer 
record
  
private
    
FNamestring;
    
FAgeInteger;
  public
    class function 
Create(const AgeInteger; const Namestring): Customer; static;
    
property AgeInteger read FAge;
    
property Namestring read FName;
  
end;

function 
CustomersSpring.Collections.IEnumerable<Customer>;
var
  
IInteger;
  List: 
IList<Customerabsolute Result;
begin
  
List := TCollections.CreateList<Customer>;
  for 
:= 0 to Random(100) do
  
begin
    
if Odd(Ithen
      
List.Add(Customer.Create(I'Cliente # ' I.ToString))
    else
      List.
Add(Customer.Create(I'Customer # ' I.ToString))
  
end;
end
La forma "tradicional" imperativa de mostrar en pantalla los clientes con edad entre 9 y 23 podria ser:

Código Delphi [-]
procedure Main;
var
  Each: Customer;
begin
  Writeln('Clientes con Edad entre 9 y 23');

  for Each in Customers do
  begin
    if (Each.Age > 9) and (Each.Age < 23) then
      Writeln(Format('%s - Edad: %d', [Each.Name, Each.Age]));
  end;
end;

Si ahora tambien tengo que incluir en el resultado aquellos que tienen Edad 4, tengo que modificar el condicional, haciendolo cada vez mas complejo:

Código Delphi [-]
procedure Main;
var
  Each: Customer;
begin
  Writeln('Clientes con Edad entre 9 y 23, o bien Edad = 4');

  for Each in Customers do
  begin
    if ((Each.Age > 9) and (Each.Age < 23)) or (Age = 4) then
      Writeln(Format('%s - Edad: %d', [Each.Name, Each.Age]));
  end;
end;

Y si ahora necesito tambien solo los que el nombre es "ingles" ("Customer en lugar de "Cliente")

Código Delphi [-]
procedure Main;
var
  Each: Customer;
begin
  Writeln('Clientes con Edad entre 9 y 23, o bien Edad = 4, y nombre ingles');

  for Each in Customers do
  begin
    if ((Each.Age > 9) and (Each.Age < 23)) or (Age = 4) and (Each.Name.StartsWith('Customer')) then
      Writeln(Format('%s - Edad: %d', [Each.Name, Each.Age]));
  end;
end;

Y esta todo mezclado: la data con las transformaciones filtros y operaciones esta todo entremezclado entre si. Y por mas que refactorize agregando condiciones, no cambia tanto porque tendria muchas condiciones.

Por suerte la interface IEnumerable<T> define un metodo Where que recibe un predicado<T>. Un predicado<T> es metodo anonimo que retorna Boolean y recibe como argumento un "T". Basicamente es esto:

Código Delphi [-]
function(const Arg: T): Boolean;
begin
  Result := evaluar Arg
end;

Podemos escribir entonces..

Código Delphi [-]
procedure Main;
begin
  Writeln('Clientes con Edad entre 9 y 23');

  Customers
    .Where(
      function(const c: Customer): Boolean
      begin
        Result := (c.Age > 9) and (c.Age < 23);
      end)

    .ForEach(
      procedure(const c: Customer)
      begin
        Writeln(Format('%s - Edad: %d', [c.Name, c.Age]));
      end);
end;

Ja. No cambio mucho, no? Muchos me diran, ok cambiaste el for in, escribiste un monton de codigo mas y es lo mismo: las condiciones sigen juntas. Bueno, bien se pueden concatenar las invocaciones a Where si quisiera. Pero el problema sigue igual: datos y operaciones mezcladas. Pero que tal esto:..

Código PHP:
function OlderThan(const AgeInteger): TPredicate<Customer>;
begin
  Result 
:= function(const cCustomer): Boolean
            begin
              Result 
:= c.Age Age;
            
end
end
;

function 
YoungerThan(const AgeInteger): TPredicate<Customer>;
begin
  Result 
:= function(const cCustomer): Boolean
            begin
              Result 
:= c.Age Age;
            
end
end
;

function 
Aged(const AgeInteger): TPredicate<Customer>;
begin
  Result 
:= function(const cCustomer): Boolean
            begin
              Result 
:= c.Age Age;
            
end
end
;

function 
NameStartsWith(const Textstring): TPredicate<Customer>;
begin
  Result 
:= function(const cCustomer): Boolean
            begin
              Result 
:= c.Name.StartsWith(TextTrue);
            
end
end
;

function 
NameIsEnglishTPredicate<Customer>;
begin
  Result 
:= NameStartsWith('Customer');
end;

procedure PrettyPrint(const cCustomer);
begin
  Writeln
(Format('%s - Edad: %d', [c.Namec.Age]));
end
Procedimientos o funciones "sencillas", separadas, reusables, faciles de entender, "parametrizables" si es necesario, y se pueden "componer", asi:

Código Delphi [-]
procedure Main;
begin
  Writeln('Clientes con Edad entre 9 y 23');

  Customers
    .Where(OlderThan(9))
    .Where(YoungerThan(23))
    .ForEach(PrettyPrint);
end;

Quiero filtrar tambien solo los que tienen nombre "en ingles"? No hay problema:

Código Delphi [-]
procedure Main;
begin
  Writeln('Clientes con Edad entre 9 y 23');

  Customers
    .Where(OlderThan(9))
    .Where(YoungerThan(23))
    .Where(NameStartsWith('Customer')) // ambas alternativas validas de hecho, NameIsEnglish
    .Where(NameIsEnglish()) // esta implementada usando NameStartsWith invocandola como NameStartsWith('Customer')
    .ForEach(PrettyPrint);
end;

Y si quiero meter todo dentro de un solo predicado, se pueden usar patrones: Specification Pattern

Código PHP:
uses
  
...,
  
Spring.DesignPatterns;

...

function 
CustomerFilterTSpecification<Customer>;
var
  
sTSpecification<Customer>;
begin
  s 
:= OlderThan(9);
  
Result := (and YoungerThan(23) or Aged(4)) and NameIsEnglish;
end;

procedure Main;
begin
  Writeln
('Clientes con Edad entre 9 y 23 o Edad = 4, y nombre en ingles');

  
Customers
    
.Where(CustomerFilter())
    .ForEach(
PrettyPrint);
end
En Spring otra vez se "abusa" de la sobrecarga de operadores para poder usar la sintaxis "and", "or", "not". Aun asi, el lenguaje limita un poco y por eso es necesaria la variable "s" para iniciar "la cadena" en la funcion CustomerFilter. Como TSpecification es un record que tiene sobrecargado el operador Implicit para convertirlo directamente a un TPredicate, la llamada al Where es posible. Esto tambien se puede hacer:

Código PHP:
procedure Main;
var
  
sTSpecification<Customer>;
begin
  Writeln
('Clientes con Edad entre 9 y 23 o Edad = 4, y nombre en español');

  
:= TSpecification<Customer>(OlderThan(9));
  
Customers
    
.Where(and YoungerThan(23) or Aged(4))
    .
Where(not TSpecification<Customer>(NameIsEnglish()))
    .ForEach(
PrettyPrint); 
De nuevo, la sintaxis demasiado "verbose" de Pascal, y el tipificado fuerte me obliga a "mucha ceremonia" pero creo que la idea se entiende

Última edición por AgustinOrtu fecha: 29-01-2017 a las 23:29:46.
Responder Con Cita
 


Herramientas Buscar en Tema
Buscar en Tema:

Búsqueda Avanzada
Desplegado

Normas de Publicación
no Puedes crear nuevos temas
no Puedes responder a temas
no Puedes adjuntar archivos
no Puedes editar tus mensajes

El código vB está habilitado
Las caritas están habilitado
Código [IMG] está habilitado
Código HTML está deshabilitado
Saltar a Foro


La franja horaria es GMT +2. Ahora son las 13:41:16.


Powered by vBulletin® Version 3.6.8
Copyright ©2000 - 2026, Jelsoft Enterprises Ltd.
Traducción al castellano por el equipo de moderadores del Club Delphi
Copyright 1996-2007 Club Delphi