PDA

Ver la Versión Completa : Sobrecarga de operadores de clase


AgustinOrtu
29-11-2016, 04:57:45
Pues eso, aunque se supone que Delphi no soporta sobrecargar los operadores de clase como si sucede con los tipos records o registros, existe cierta sintaxis que lo hace posible, apartentemente no documentada ni oficial

En fin, no se como lo habra descubierto el que lo hizo, o de donde lo saco. La cuestion es que ha publicado dos fragmentos de codigo en GitHub, aca (https://gist.github.com/lynatan/673e574faa8343fa01d7a91e75065c54) y aca (https://gist.github.com/lynatan/886ed984d230ac1b42dd89ed42ab2214)

Sorprendentemente este codigo compila, funciona y no hay fugas de memoria, en Delphi 2010 y Delphi 10.1 Berlin, asi que supongo que "la compatibilidad se mantiene" en todas las versiones del medio.


program Project1;

{$APPTYPE CONSOLE}

uses
SysUtils,
Classes;

type
TStringListHelper = class helper for TStringList
public
class function &&op_In(const Value: string; Target: TStringList): Boolean; static;
class function &&op_LogicalOr(A, B: TStringList): TStringList; static;
end;

class function TStringListHelper.&&op_In(const Value: string; Target: TStringList): Boolean;
begin
Result := Target.IndexOf(Value) <> -1;
end;

class function TStringListHelper.&&op_LogicalOr(A, B: TStringList): TStringList;
begin
if A <> nil then
Result := A
else
Result := B;
end;

procedure Test;
var
StringListA, StringListB, StringListC: TStringList;
begin
StringListA := nil;
StringListB := TStringList.Create;
try
StringListB.Add('Hola mundo');
StringListC := StringListA or StringListB; // -> StringListC apunta al mismo objeto que StringListB
Writeln('StringListB.Text: ' + StringListB.Text); // imprime hola mundo
Writeln('StringListC.Text: ' + StringListC.Text); // imprime hola mundo
Writeln(BoolToStr(StringListB = StringListC, True)); // imprime True
Writeln(BoolToStr('Hola mundo' in StringListB, True)); // imprime True
Writeln(BoolToStr('blabla' in StringListB, True)); // imprime False
finally
StringListB.Free;
end;
end;

begin
ReportMemoryLeaksOnShutdown := True;
try
Test;
Readln;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.



Me parece algo realmente poderoso y peligroso al mismo tiempo, no he podido ordenar mis ideas por el momento

Realmente algo como esto si que es util para escribir codigo mas elegante y sencillo. Muchas veces se emplea el uso de records como envolturas de clases o tipos primitivos ya que ellos son los unicos con los que se pueden sobrecargar los operadores; pero esto lo lleva a otro nivel

De hecho es posible hasta con genericos como se puede ver en el codigo del autor

jhonny
29-11-2016, 06:13:16
Sí que sí, bastante interesante, me he preguntado desde hace tiempo ¿Cuál será la razón para que la sobrecarga de operadores no esté soportada de manera nativa en las clases?, pues como bien indicas esto es poderoso aunque a la vez peligroso.

¿Cómo habrá sabido este personaje que tenía que anteponer &&op_ al nombre de cada operador? :D

Bastante interesante, el truco y lo que habrá detrás de dicha conclusión.

P.D: Sólo anotar, que me parece muy curiosa la observación que hace un usuario diciendo que no funciona en Records :D

Al González
29-11-2016, 07:30:50
Interesante, Agustín. Al estar depurando, con la ventana CPU, me parece haber visto nombres similares a esos. Quizá de ahí el descubridor obtuvo los nombres "reales" que les da el compilador a esos métodos.

Sólo los compiladores con ARC (https://en.wikipedia.org/wiki/Automatic_Reference_Counting) (Delphi para dispositivos móviles) admiten la sobrecarga "formal" de operadores en clases, ya que en ellos puede uno despreocuparse por las instancias "intermedias" que se crean a mitad de algunas operaciones, dado que todos los objetos usan un contador de referencias, tal como lo hacen los String en cualquiera de los compiladores de Delphi.

Creo que la explicación al truco podría estar en que sólo el revisor sintáctico del compilador impide la declaración en clases, pero el resto de la compilación se realiza asumiendo que ya se superaron las validaciones sintácticas de plataforma y procede sin restricciones (asumiendo también el nombre transformado del método).

Resulta tentador empezar a aprovechar esta puerta trasera en los compiladores de escritorio, pero ya aprendí la lección (https://www.clubdelphi.com/foros/showthread.php?p=511047#post511047), y es mejor prescindir de características del compilador no oficiales. Además hay que tener cuidado no de aplicar ningún operador que deje un objeto "suelto" por ahí. :p

¡Qué hallazgo! :)

Neftali [Germán.Estévez]
29-11-2016, 08:18:55
...
Writeln(BoolToStr('Hola mundo' in StringListB, True)); // imprime True
Writeln(BoolToStr('blabla' in StringListB, True)); // imprime False
...



Este me ha gustado mucho.
^\||/^\||/^\||/^\||/^\||/

AgustinOrtu
29-11-2016, 11:03:29
Marco Cantú ha confirmado (https://plus.google.com/+AgustinOrtu/posts/G7mJxL3S9EL) que esto es una puerta trasera, que es una característica no intencionalmente puesta a nuestro alcance, tal y como comenta nuestro amigo Al González

Los operadores que crean objetos intermedios seguro que ocasionan fugas de memoria.

Aún así me parece un juguetito muy interesante y que tiene mucha tela por donde cortar.

Esta es la lista completa de operadores:


class operator class function
Implicit &&op_Implicit
Explicit &&op_Explicit
Negative &&op_UnaryNegation
Positive &&op_UnaryPlus
Inc &&op_Increment
Dec &&op_Decrement
LogicalNot &&op_LogicalNot
Trunc &&op_Trunc
Round &&op_Round
In &&op_In
Equal &&op_Equality
NotEqual &&op_Inequality
GreaterThan &&op_GreaterThan
GreaterThanOrEqual &&op_GreaterThanOrEqual
LessThan &&op_LessThan
LessThanOrEqual &&op_LessThanOrEqual
Add &&op_Addition
Subtract &&op_Subtraction
Multiply &&op_Multiply
Divide &&op_Division
IntDivide &&op_IntDivide
Modulus &&op_Modulus
LeftShift &&op_LeftShift
RightShift &&op_RightShift
LogicalAnd &&op_LogicalAnd
LogicalOr &&op_LogicalOr
LogicalXor &&op_ExclusiveOr
BitwiseAnd &&op_BitwiseAnd
BitwiseOr &&op_BitwiseOr
BitwiseXor &&op_BitwiseXOR
Include &&op_Include
Exclude &&op_Exclude


A mi también me ha gustado mucho el operador in. Y el del or para usar un objeto u otro en caso de nil también me parece que tiene mucho juego

En fin, es cuestión de creatividad. Pero ya hemos sido advertidos: es posible que solucionen este defecto

escafandra
29-11-2016, 18:04:00
Para mi siempre ha sido algo natural la sobrecarga de operadores que uso bastante, ahorrándome código y proporcionando limpieza e intuición al código. Estoy hablando de C++
Siempre lo he echado en falta en delphi.

Saludos.

AgustinOrtu
29-11-2016, 18:43:47
En C++ o los lenguajes con recoleccion de basura es seguro tener operadores de clase.

En Delphi que no es necesariamente el caso, puede haber muchas fugas de memoria, y creo que ese es el motivo por el cual no tenemos a nuestra disposicion sobrecarga de operadores. Al menos no para el compilador para Windows; los que implementan ARC, como señalo Al, si que permiten la sobrecarga

Los record son un caso especial porque Delphi se encarga de manejar la memoria por nosotros y entonces no hay problema

A mi tambien siempre me hacian mucha falta en Delphi.. seria muy bueno poder tener esta sintaxis de manera oficial, por lo menos en interfaces que si implementan el mecanismo de contador de referencias

Otro detalle es que no necesariamente hay que usar un ayudante de clase como esta mas arriba. Es posible utilizar una subclase para lograr el mismo efecto; o tambien el viejo truco de la clase interpuesta


TStringListEx = class(TStringList)
public
class function &&op_In(const Value: string; Target: TStringListEx): Boolean; static;
class function &&op_LogicalOr(A, B: TStringListEx): TStringListEx; static;
end;



TStringList = class(Classes.TStringList)
public
class function &&op_In(const Value: string; Target: TStringList): Boolean; static;
class function &&op_LogicalOr(A, B: TStringList): TStringList; static;
end;

Al González
29-11-2016, 19:12:12
Hablando de estas cosas, yo sueño con el día en que pueda escribir en Delphi "expresiones de asignación" como
...

If ... Then
If (A := B) > 10 Then // <---
...

...
en lugar de
...

If ... Then
If TghSys.SetInt (A, B) > 10 Then // <---
...

...
que suelo escribir en lugar de
...

If .. Then
Begin // <---
A := B; // <---

If A > 10 Then // <---
...
End; // <---

...

Un abrazo esperanzador. :)

Al González.

roman
29-11-2016, 19:22:02
En C++ o los lenguajes con recoleccion de basura es seguro tener operadores de clase.]

¿C++ tiene recolector de basura? :eek:

Y si no, ¿por qué en C++ sí es seguro y en Delphi no?

LineComment Saludos

roman
29-11-2016, 19:24:35
yo sueño con el día en que pueda escribir en Delphi "expresiones de asignación" como
...

If ... Then
If (A := B) > 10 Then // <---
...

...


¡Anda! Ahora sí me sorprendiste. ¿Tú soñando con una característica típica del lenguaje C? :rolleyes:

LineComment Saludos

AgustinOrtu
29-11-2016, 19:29:25
Ay ay me equivoque :)

El que recolecta basura era el C#

Casimiro Notevi
29-11-2016, 19:29:28
¡Anda! Ahora sí me sorprendiste. ¿Tú soñando con una característica típica del lenguaje C? :rolleyes:Me lo has quitado de la boca... :)

Al González
29-11-2016, 19:49:29
¡Anda! Ahora sí me sorprendiste. ¿Tú soñando con una característica típica del lenguaje C? :rolleyes:
El lenguaje C tiene algunas cosas muy buenas, Román. ;)

roman
29-11-2016, 20:08:07
El lenguaje C tiene algunas cosas muy buenas, Román. ;)

Eso lo sé, pero pensé que tú no :D

Pero ya en serio, me causa curiosidad el uso que deseas, quizá por no tener contexto. En lo particular, esa característica (que la asignación sea un operador) casi sólo la uso para ciclos en los que obtengo y uso un dato:


/*
DameRegistro devuelve el siguiente registro o FALSE si no hay más registros
*/
WHILE registro = DameOtro() DO
BEGIN
MuestraRegistro(registro)
END


En otros casos, puede devenir en una lectura confusa.

LineComment Saludos

roman
29-11-2016, 20:13:51
Ay ay me equivoque :)

El que recolecta basura era el C#

Ok. Pero entonces, ¿qué hace que C++ si pueda tener el operator overloading y delphi no?

LineComment Saludos

jhonny
01-12-2016, 00:21:09
Un parentesis pequeño, sólo para mostrar el recolector de basuras de C#

https://s17.postimg.org/lmf0zmjn3/Unknown.jpg

P.D: Perdón, no pude evitarlo. :D

mamcx
01-12-2016, 17:30:49
Ok. Pero entonces, ¿qué hace que C++ si pueda tener el operator overloading y delphi no?

LineComment Saludos

QUe C++ es el frankestein de los lenguajes y le inyectan toda caracteristica bajo el sol. Prácticamente puedes programar como si estuvieras en otro lenguaje abusando de C++ como te de la gana.


Delphi, por el contrario, fue derivado de alguien que *diseño* el lenguaje:

http://pascal-central.com/ppl/#Origins


Niklaus Wirth completed development of the original Pascal programming language in 1970. He based it upon the block structured style of the Algol programming language. There were two original goals for Pascal.

According to the Pascal Standard (ISO 7185), these goals were to a) make available a language suitable for teaching programming as a systematic discipline based on fundamental concepts clearly and naturally reflected by the language, and b) to define a language whose implementations could be both reliable and efficient on then-available computers.


Delphi/Pascal tiene un compilador veloz *PRECISAMENTE* por ser tan anti-C++ como es posible.

----

Ahora, lograr poder sobrecargar operadores no es en si algo tan problematico... excepto que en los lenguajes imperativos existe una división entre expresiones, clases, tipos, statements (bloques), etc. Si Delphi fuera un lenguaje unificado a todo es una expresión esto sería un cambio trivial.

Tambien *supongo* que no seria trivial el darle una disponibilidad universal y que efectos tendria con el chequeo e intersección de tipos y si quedaria oscurecido el código por su uso. Esto no lo he pensando asi que es pura especulación.
---

En ultimas hay cambios a los lenguajes que los alteran de forma fundamental. Intentar agregarles cosas puede terminar siendo poco "idiomático" o convertirse en una característica de nicho poco usada pero que igual tiene su coste en mantenimiento e implementación.


P.D: Esta es una caracteristica que no es tan problematica si esta bien implementada, y NO SE PERMITE redefinir o crear nuevos operadores al vuelo (un problema que termina creando diversos codigos con el mismo operador pero diferente semantica!). Si esta limitado a unos pocos es muy util!

escafandra
01-12-2016, 19:21:38
¿Qué tendrá C/C++ que es tan envidiado y odiado?

Saludos.

Casimiro Notevi
01-12-2016, 19:24:47
¿Qué tendrá C/C++ que es tan envidiado y odiado?
Saludos.
C me gusta, C++ no me gusta :)

escafandra
01-12-2016, 19:28:24
C me gusta, C++ no me gusta :)

Ya pero características propias de C++ son tan envidiadas cómo el propio C. No será tan malo, diho yo. :) A mi me gustan ambos y delphi también.

Saludos

mamcx
01-12-2016, 21:31:24
C++ es "odiado" mas que todo por su complejidad, lentitud de compilacion y y dificultades para entder el codigo (ya que al poder abusarse, se abusa de forma diferentes por diferentes personas!).

Por ahi esta el dicho de que "no existe" el lenguaje C++, solo existe dialectos particulares aplicados de forma divergente por diversas entidades.

Al González
01-12-2016, 22:31:54
En lo particular, esa característica (que la asignación sea un operador) casi sólo la uso para ciclos en los que obtengo y uso un dato:

/*
DameRegistro devuelve el siguiente registro o FALSE si no hay más registros
*/
WHILE registro = DameOtro() DO
BEGIN
MuestraRegistro(registro)
END

Buen uso. :)

Hoy estuve trabajando con cierto método que quizá sirva de ejemplo:
Function TuaByNodeIdDic &lt;T&gt;.TryGetValue (Const AKey :TuaDNodeId;
Out AValue :T) :Boolean;
Var
LTried :Boolean;
Begin
LTried := System.False;
Result := System.False;

If AKey.IndexableByID Then
Begin
Result := TryGetValue (AKey.BinEncodedID, AValue); // Sobrecarga (no recursivo)
LTried := System.True;
End;

//...
End;
Con "asignaciones expresivas" (por llamarles de algún modo) esa primera parte se reduciría un poco:
Function TuaByNodeIdDic &lt;T&gt;.TryGetValue (Const AKey :TuaDNodeId;
Out AValue :T) :Boolean;
Var
LTried :Boolean;
Begin
If Result := LTried := AKey.IndexableByID Then
Result := TryGetValue (AKey.BinEncodedID, AValue); // Sobrecarga (no recursivo)

//...
End;

Saludos.

escafandra
02-12-2016, 00:24:17
C++ es "odiado" mas que todo por su complejidad, lentitud de compilacion y y dificultades para entder el codigo (ya que al poder abusarse, se abusa de forma diferentes por diferentes personas!).

Por ahi esta el dicho de que "no existe" el lenguaje C++, solo existe dialectos particulares aplicados de forma divergente por diversas entidades.

La complejidad es relativa al conocimiento del lenguaje, como en cualquiera. Si llamamos complejidad al uso de punteros, entonces delphi usado de esa forma, también lo es, aporta potencia y capacidad de bajar de nivel. Las dificultades de entender el código, como bien dices dependen del código, y éste de quien lo escriba. Delphi también permite ofuscarlo, aunque en menor grado. La lentitud de compilación al compararlo con delphi es cierta, pero insignificante. Compilar clipper si era lento (minutos eternos) cuando Borland C era un rayo. Lo de los dialectos de C++ dependen de la librería de clases y estas tienden al infinito, delphi puede comportarse de igual forma si salimos de la VCL, que de por sí ya puede considerarse un dialecto si partimos de pascal.

Lazarus adopta formas de C/C++ que de alguna forma son deseadas por los amantes de delphi

http://delphiaccess.com/foros/uploads/monthly_07_2015/post-12294-0-68771700-1436556747.jpg

Al final se reduce a costumbres, más que el lenguaje en si mismo.


Saludos.

mamcx
02-12-2016, 02:17:17
La complejidad es relativa al conocimiento del lenguaje.


Solo cierto, IDEALMENTE. Eso es lo que se llama "complejidad necesaria" (https://en.wikipedia.org/wiki/No_Silver_Bullet).

C++ tiene mucho de lo que se llama "complejidad accidental o inesperada" que no se le puede achacar la culpa al usuario, es culpa del lenguaje, intrínseco a él.

Esto ni siquiera es un tema de opinión o controversial. Es un hecho claro y comprendido. El porque C++ ha tomado esta ruta, es parecido a otros lenguajes como Perl: Anteponer la capacidad de hacer algo de tantas formas posibles vs el estilo de lenguajes como Pascal/Python/Lisp: El tener la forma más simple y clara de lograr el objetivo, a costa de reducir la posibilidad de hacer cosas de forma diferente.

Obvio que eventualmente si se programa dia y noche en C++ todo parecera simple. Pero eso es pura costumbre. Cuando se aprende otros lenguajes se vuelve muy claro en que aspectos son mejores o peores.

Si uno compara C++ con otro lenguaje del mismo nivel, como ADA, Pascal o RUST es muy claro que C++ es mucho mas complejo. Si otros lenguajes tuvieran el minimo de maquinaria faltante para ser considerados "Systems languages", en terminos de pura sintaxis C++ queda borrado.

Desafortunadamente para todos en nuestra industria, C++ le gano a Pascal, y es la causa de millones de dolares en perdidas debido a ello.


----

Como un experimento interesante, busque en google "LANGUAGE X is too complex", usando C++, Delphi, Pascal, Python, Perl, Scala, Javascript.

Efectivamente, es tal como esperabas:

- C++, Scala, Perl, Javascript son considerados muy complicados como tal, como una falla inherente, un mal necesario de usarlos
- Delphi, Python, Pascal se discute la complejidad pero no tanto del lenguaje, sino de los proyectos o librerias externas, de interfazar con otros sistemas y cosas por el estilo. Tal como se espera de una herramienta mejor hecha que permite concentrarse en la tarea.

Una conclusion que es obvia si se tiene experiencia en varios lenguajes.

escafandra
02-12-2016, 12:13:27
Porqué C ganó la batalla es un asunto que no viene al caso.

Como dije, es un asunto que depende mucho de la costumbre. Que C puede resultar algo más complejo que otros, no lo negaré. Para mí no lo es, quizás por costumbre. Pero si puedo decir, en el tema que nos ocupa, que a la hora de escribir código en delphi, echo en falta facilidades de C/C++, las mismas que los compañeros han reclamado en este hilo, como sobrecarga de operadores, expresiones de asignación y otras como el operador incremento/decremento, aritmética de punteros, bucle for estilo C, y tantas cosas. Quiero resaltar que me siento cómodo en delphi y mi intención no es polemizar sin más y mucho menos comparar.

mamcx, explicas porqué y a tu parecer, C/C++ es odiado (complejidad), pero no porqué es envidiado. Quizás dentro de su "complejidad" tenga algo bueno que aporte algo de "simplicidad" que facilite algunas cosas como se demanda en este hilo.


Saludos.

mamcx
02-12-2016, 17:18:39
mamcx, explicas porqué y a tu parecer, C/C++ es odiado (complejidad), pero no porqué es envidiado. Quizás dentro de su "complejidad" tenga algo bueno que aporte algo de "simplicidad" que facilite algunas cosas como se demanda en este hilo.


Porque es envidiado es fácil.

"C++ no te da alto desempeño. Te da el *control sobre el desempeño*".

Hay *muy pocos* lenguajes que son viables como "system languages" y que permiten tunear la estructuras de datos. C++/C es la opción más popular y por ende hay mucho código disponible.

Esta charla ejemplifica muy bien el tema:

https://channel9.msdn.com/Events/CPP/C-PP-Con-2014/Efficiency-with-Algorithms-Performance-with-Data-Structures


---

Hay varias cosas de C++ que son interesantes. Es claro que C es mucho mas simple y en varios casos gente que empezo proyectos con C++ eventualmente se dieron cuenta que hubiera sido mejor usar C, pero igual C++ tiene cosas útiles, entre las cuales destaco:

- RAII : https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization, que permite manejar los recursos sin recurrir a un recolector de basura con retraso.

- Zero Cost Abstraction (https://gautiertalkstechnology.wordpress.com/2015/06/22/zero-cost-abstraction-language-c-future/): C++ permite tunear las cosas para aplicar abstracciones sin incrementar el costo de ellas. Como por ejemplo, convertir una iteración en un loop estándar.

No es el único lenguaje que permite esto, y de hecho, es algo que puede lograrse en muchos lenguajes a mayor o menor grado, pero es un aspecto notable del mismo. Igual se puede hacer de "todo" con "cualquier lenguaje" pero en la práctica es una ilusión: Ciertos lenguajes & su comunidad se prestan más a la hora de actuar de cierta manera.


Siendo Delphi mucho mas cerca de C++ hay muchas cosas que podrían copiarle. No las partes complejas (o mejor dicho, su complicada implementación, como en el caso de los templates). Viendo como se va desarrollando RUST (https://www.rust-lang.org/en-US/) es claro que Delphi en vez de intentar ser una copia inferior de Java/C# puede ser una mejor opcion como lenguaje de sistema con la gracia de ser mucho mas claro y comprensible.

Eso no es imposible, porque Ada, Modula & Oberon son de la familia Pascal y pueden rivalizar con C++ a nivel técnico, sino fuera por su limitada difusión. Lo tremendo es que estos lenguajes incluso son mas simples que Delphi en muchas de las areas donde es complicado hacer cosas en Delphi, como la programacion paralela o asincronica.