Italian community of Lazarus and Free Pascal

Programmazione => Generale => Topic aperto da: nomorelogic - Gennaio 10, 2022, 09:45:51 am

Titolo: esperimenti con TNullable
Inserito da: nomorelogic - Gennaio 10, 2022, 09:45:51 am
ciao a tutti

relativamente all'articolo sui TNullable sul blog https://blog.lazaruspascal.it/2021/12/27/tnullable/ (https://blog.lazaruspascal.it/2021/12/27/tnullable/)
sto facendo degli esperimenti

se qualcuno è interessato sto cercando di capire come determinare il tipo di specializzazione a partire da un puntatore non tipizzato
qualcosa di simile all'operatore is delle classi ma applicato agli advanced records
penso che l'unico modo sarà quello di accedere alle info RTTI ma spero in qualcosa di meglio

ho postato sul forum ufficiale
https://forum.lazarus.freepascal.org/index.php?topic=57814.msg430213#msg430213 (https://forum.lazarus.freepascal.org/index.php?topic=57814.msg430213#msg430213)
Titolo: Re:esperimenti con TNullable
Inserito da: DragoRosso - Gennaio 10, 2022, 10:07:33 am
Perchè usi il new e il dispose ?

Non puoi semplicemente lasciare che il compilatore lavori per te usando il record (classe) e non il puntatore.

Se ti serve un puntatore lo referenzi con la variabile ....

In questo modo non ti serve ne costruirlo ne distruggerlo ...

Ciao
Titolo: Re:esperimenti con TNullable
Inserito da: nomorelogic - Gennaio 10, 2022, 10:15:50 am
Sto cercando di realizzare una specie di classe per gestire una "memory space".
In pratica ho una hash list dove vado a memorizzare specializzazioni di TNullable di stringhe, interi, double, ecc...
(Questo mi serve perché non so quali valori arriveranno ed in quale sequenza)

Poi arriva il momento della Dispose, in quel momento la Dispose ha bisogno del puntatore tipizzato.

Edit:
in fondo è come se in una hash list memorizzassi a caso interi, stringhe e double.
Qualcuno farebbe una hash per tipo, io ho bisogno di una unica hash.
Titolo: Re:esperimenti con TNullable
Inserito da: DragoRosso - Gennaio 10, 2022, 10:42:12 am
Quindi, in pratica, a differenza del codice che hai postato, tu userai la variabile nullint (e le altre tipizzate) per creare il contenitore, assegni il contenitore ad una lista e continui così.

Di fatto l'unica referenza di tutti i contenitori (TNullable) che avrai creato sarà nella lista, non contando nullint che verrà "sovrascritta" ogni volta.....

Hai due problemi di referenza, non solo quando fai la dispose ma anche quando estrai dalla lista l'oggetto devi conoscere la tipizzazione (TNullString, TNullInteger), se no come fai a sapere cosa ci hai messo dentro ?

Secondo me, potrebbe essere interessante inserire in TNullable (ovviamente riscrivendolo) una proprietà con la sua tipizzazione. In questo modo non usi le RTTI.

Non vedo altro modo per fare ciò che chiedi ...

Ciao.
Titolo: Re:esperimenti con TNullable
Inserito da: nomorelogic - Gennaio 10, 2022, 11:28:24 am
in effetti è così, quando estraggo il valore, so già di che tipo si tratta :)
è come quando hai un integer: sai che è un integer!

per questo avrò una proprietà per ogni tipo, ad esempio:
Codice: [Seleziona]
     property Integers[AName : String] : TMaizAssignable_Integer Read GetIntegers Write SetIntegers;
     property Strings[AName : String] : TMaizAssignable_String read GetStrings write SetStrings;
in questo modo posso cercare nella hash list (che è unica per tutti i tipi di valori) e poi assegnare il tipo giusto nel Getter

è una alternativa a creare un tipo record statico, ad esempio, per ogni tabella di un database
io ho diverse tabelle che superano i 100 campi...
avrei potuto generare i tipi record ma ho bisogno di adattarmi a qualunque tipo di tabella senza conoscerne la definizione
così perdo un po' in prestazioni (ma poco) ma ci guadagno in dinamismo


Edit:
TMaizAssignable è una mia versione di TNullable
Titolo: Re:esperimenti con TNullable
Inserito da: DragoRosso - Gennaio 10, 2022, 12:02:12 pm
in effetti è così, quando estraggo il valore, so già di che tipo si tratta :)
è come quando hai un integer: sai che è un integer!

Bhè, si è no ..... quando tu scrivi:

Codice: [Seleziona]
WriteLn('one = ', PNullString(HashList.Find('one'))^.Value);

istruisci al compilatore a fare una cosa ben precisa ... deferenzi in maniera rigida una "risposta" il cui risultato non è noto al compilatore (il riferimento di "HashList.Find('one'))^.Value" non può esserti noto a priori in generale).

Lo sarebbe se tu avessi un qualcosa del tipo: "HashList.Find('one'))^.ValueString" o "HashList.Find('one'))^.ValueInteger" ad esempio.

Inoltre, per l'uso delle RTTI dovresti comunque implementare nel codice del TNullable un qualcosa.

In generale, vorrei ricordare che ereditarietà, interfacce e altro con i record non si possono usare.

Ciao

EDIT: giusto per essere più chiaro, il tuo chiamamolo cast PNullString(...) non è dinamico, ma statico. Il compilatore sia aspetta di trovare quella referenza. Quindi in generale và bene avere il gettere e il setter, ma non è sufficiente. Se devi leggere un tipo di valore generico, non puoi aspettarti un tipo di valore noto, a meno che non istruisci il "getter" a darti quel tipo preciso di valore (ad esempio come fanno gli HELPER .toString .toInteger .... etc) ....   
Titolo: Re:esperimenti con TNullable
Inserito da: nomorelogic - Gennaio 10, 2022, 12:37:14 pm
istruisci al compilatore a fare una cosa ben precisa ...

infatti: so già che cosa mi torna indietro e va bene così


il codice sotto
Codice: [Seleziona]
var i: integer
begin
  i := 5;
  writeln('intero I =', i);
end;

voglio scriverlo così
Codice: [Seleziona]
var ms: TMemoryspace;
begin
  ms:=TMemoryspace.Create;
  ms.Integers['i'] := 5;
  writeln('intero I =',ms.Integers['i']);
  ms.Free;
end;

solo che in più ho gestiti anche i valori null
Titolo: Re:esperimenti con TNullable
Inserito da: DragoRosso - Gennaio 10, 2022, 02:27:46 pm
Lol .... ma se sai cosa ti torna, allora sai anche come "distruggerlo" .....

 :o
Titolo: Re:esperimenti con TNullable
Inserito da: nomorelogic - Gennaio 10, 2022, 03:50:41 pm
fai conto che devo memorizzare tutte le colonne di un record di una query: scorro la proprietà FieldDefs del ClientDataset

però da quello che mi rispondono sul forum ufficiale, dovrò farlo comunque di salvarmi il tipo...
Titolo: Re:esperimenti con TNullable
Inserito da: DragoRosso - Gennaio 10, 2022, 11:01:41 pm
@nomorelogic,
ho visto la modifica che hai proposto nel codice presentato nel forum ufficiale.

E ho visto che hai usato FREEMEM su un "oggetto" creato con NEW.

Guarda che l'uso di FREEMEM al posto del DISPOSE su oggetti creati con NEW è assolutamente sconsigliata. E' storica questa cosa, e non penso sia mai stata sanata la differenza, differenza che non conosco nei dettagli (dai sorgenti non è così scontato riuscire a seguire tutti i rami).

E anche se le prove che fai magari ti riportano un esito positivo, non è assolutamente detto che ciò possa perdurare in tutte le condizioni.

Un'altra cosa che mi "prude" nell'uso dei puntatori così diretti, in particolare con record complessi, è il pericolo del "ref on copy" e del "dup on copy", ossia che a runtime generalmente si crea semplicemente un riferimento allo stesso puntatore quando si copia "qualcosa", ma può anche effettuarsi un duplicato dell'oggetto, con un puntatore, allocazioni ed altro diverse dall'originale nel caso si modifichi una delle copie. Ma questa è una mia paturnia.

Ciao
Titolo: Re:esperimenti con TNullable
Inserito da: nomorelogic - Gennaio 10, 2022, 11:51:06 pm
Riguardo l'uso di FreeMem al posto di Dispose non l'ho proposto io. Lo ha proposto engkin ed io ho provato per vedere che succedeva  perché neanche io pensavo fosse possibile ed ho voluto vedere.
Però di basare il codice su questa forzatura non mi piace, infatti seguirò il consiglio di PascalDragon che è quello di memorizzare il tipo per poi fare una Rispose mirata.

Riguardo agli effetti collaterali nell'uso dei puntatori, spero di scrivere codice non buggato, del resto i puntatori esistono e in questo caso ho bisogno di velocità... 😊
Titolo: Re:esperimenti con TNullable
Inserito da: DragoRosso - Gennaio 11, 2022, 12:07:59 am
.....
Però di basare il codice su questa forzatura non mi piace, infatti seguirò il consiglio di PascalDragon che è quello di memorizzare il tipo per poi fare una Rispose mirata.
.....

....
Secondo me, potrebbe essere interessante inserire in TNullable (ovviamente riscrivendolo) una proprietà con la sua tipizzazione. In questo modo non usi le RTTI.
....

E certo che la "pesantezza delle affermazioni" di un membro INTERNAZIONALE si fà sentire ...... sigh

 ;D ;D ;D ;D ;D ;D ;D ;D ;D ;D ;D ;D ;D ;D ;D
Titolo: Re:esperimenti con TNullable
Inserito da: DragoRosso - Gennaio 11, 2022, 02:17:45 am
Stò costruendo un TNullable che associa anche il NULL, consentendo l'assegnazione diretta nei due sensi ....

@nomorelogic, sò già che non ti piacerà .... ma consente l'assegnazione mantenendo comunque la logica di un NULLABLE.

Ciao
Titolo: Re:esperimenti con TNullable
Inserito da: nomorelogic - Gennaio 11, 2022, 09:05:19 am
.....
Però di basare il codice su questa forzatura non mi piace, infatti seguirò il consiglio di PascalDragon che è quello di memorizzare il tipo per poi fare una Rispose mirata.
.....

....
Secondo me, potrebbe essere interessante inserire in TNullable (ovviamente riscrivendolo) una proprietà con la sua tipizzazione. In questo modo non usi le RTTI.
....

E certo che la "pesantezza delle affermazioni" di un membro INTERNAZIONALE si fà sentire ...... sigh

 ;D ;D ;D ;D ;D ;D ;D ;D ;D ;D ;D ;D ;D ;D ;D

giusto per fugare i dubbi...  :)
me ne sono accorto che era lo stesso consiglio ed infatti ho dato la stessa risposta (nel senso che per l'uso dei valori non ne avevo bisogno) con un bel copia/incolla/traduci da questo thread :D

onestamente pensavo che almeno con rtti si potesse fare qualcosa, anche se non sapevo ancora come; e lo avrei preferito
nel senso che memorizzare il tipo avrei voluto evitarlo per tenere il codice il più snello possibile

poi se uno come PascalDragon ti dice che sei in un vicolo cieco... tocca trovare una strada diversa
anche perchè, la FreeMem al posto della Dispose... la pensiamo allo stesso modo e non voglio farla (anche se devo ammettere che mi ha fatto venire un'idea, ma non è questo il momento)




Titolo: Re:esperimenti con TNullable
Inserito da: nomorelogic - Gennaio 11, 2022, 09:07:15 am
Stò costruendo un TNullable che associa anche il NULL, consentendo l'assegnazione diretta nei due sensi ....

@nomorelogic, sò già che non ti piacerà .... ma consente l'assegnazione mantenendo comunque la logica di un NULLABLE.

suppongo che abbia a che fare con i variant quindi :D

cmq, lo ammetto, sono curioso  ;)
Titolo: Re:esperimenti con TNullable
Inserito da: DragoRosso - Gennaio 11, 2022, 11:10:22 pm
.... ho fallito con Lazarus ...  :-[ :-\ :'(

negli operators overload (class operator), c'è distinzione tra i parametri ma non tra i "risultati". In questo modo non è possibile definire l'overload dello stesso operatore, a meno di non definire più metodi distinti.

Ciò che volevo fare con il TNullable, in maniera semplice ed efficace non sembrerebbe possibile, almeno per lo mie skill con Lazarus.

D'altronde non è un caso se il NULL è gestito con un variant. I "Generic" hanno necessità di essere ancora sviluppati.

Ad esempio, il seguente codice non viene compilato, perchè per FPC le due implementazioni sono duplicate:
Codice: [Seleziona]
class operator TNullable.:= (aValue: TMyType): T;
begin
    ........
end;

class operator TNullable.:= (aValue: TMyType): Variant;
begin
   .........
end;


Parcheggiato per adesso il progetto, proverò con FPC 3.4 ....

Ciao
Titolo: Re:esperimenti con TNullable
Inserito da: nomorelogic - Gennaio 12, 2022, 08:58:37 am
l'idea era buona, peccato non si possa fare

poi bisognava vedere come si comportava una variabile di questo tipo in una espressione di calcolo o concatenazione tra stringhe
però l'idea era buona sul serio e probabilmente il cast implicito al variant avrebbe funzionato a dovere

mi viene in mente  un workaround non proprio politically-correct :D
invece di usare i variant si potrebbero definire delle costanti con valori predefiniti che rappresentano il null
ad esempio:
Codice: [Seleziona]
const 
   NULLSTRING = '{NULL}';
   NULLINT = high(integer);
   NULLDATE = 0;

per cui si potrebbe scrivere:
Codice: [Seleziona]
var
   s: TNullableString;
begin

  s := NULLSTRING;

end;



l'assegnazione di NULLSTRING dovrebbe richiamare internamente il .Clear
poi, in una espressione o concatenazione, grazie agli operator overload, si potrebbe generare una eccezione

non è il massimo come cosa da implementare, ma si tratterebbe di poter definire delle costanti che scatenano l'assegnazione del NULL
 ::)

Titolo: Re:esperimenti con TNullable
Inserito da: nomorelogic - Gennaio 12, 2022, 09:16:41 am
comunque, ora che ci penso, si può sempre fare qualcosa del genere

Codice: [Seleziona]
var
  NullString,  // questa variabile è destinata a rimanere sempre a null (non va mai assegnata)
     s: TNullableString;
begin

  // per dare un valore
  s := 'ciao mondo';

  // per impostare a null
  s := NullString;
end;
Titolo: Re:esperimenti con TNullable
Inserito da: DragoRosso - Gennaio 12, 2022, 10:45:17 am
La funzione, teorica, del Nullable sarebbe quella di contenere due "stati" essenziali: uno è il tipo "specializzato" e l'altro lo stato di NULL o non assegnato.

Indipendentemente dalla rappresentazione interna, il problema concettuale è la sua rappresentazione esterna (assegnazione LEFT <-> RIGHT)

In questo momento ciò che può essere NULL, è rappresentato da una variant. Ed è su questo che ci si deve confrontare.

Sino a quando il compilatore non supporterà integralmente i GENERIC, e in particolare i record managed, non potranno esserci scorciatoie allo stato di fatto. Anche l'RTTI (definito sperimentale) andrebbe implementato.

Se io assegno a un NullableInteger.Value un Intero o un NULL (lasciando perdere per un attimo la conversione da altri formati come double) il "modo" di assegnazione dovrebbe essere sempre quello:
Codice: [Seleziona]
//Esempio, lasciando perdere il metodo di assegnazione (implicito o esplicito)
NullableInteger.Value := 1;
NullableInteger.Value := NULL;

ciò perchè non è noto in generale se il dato ricevuto sarà un intero o un NULL (ad esempio supponiamo che invece di una costante riceviamo l'input da un campo di un database).
Idem quando il valore viene "emesso".

L'alternativa c'è ovviamente, ma è assolutamente "grezza", inefficiente e priva di significato: basta avere due metodi di ingresso e due metodi di "uscita" e uso uno o l'altro in funzione di quello che è il valore effettivo come nell'esempio qui sotto
Codice: [Seleziona]
 //è solo esemplificativo !!!!
if Valore = NULL then
  NullableInteger.ValueNull := Valore
else
  NullableInteger.Value := Integer(Valore);

Con FPC 3.3.1 le cose non cambiano. Devo provare FPC 3.4.

Tanto per dare l'idea, questo è quello che volevo fare (stralcio), e che ho fatto da altre parti ....
Codice: [Seleziona]
    
    class operator Implicit(const Value: TNullable<T>): T;
    class operator Implicit(const Value: TNullable<T>): Variant;
    class operator Implicit(const Value: Pointer): TNullable<T>;
    class operator Implicit(const Value: T): TNullable<T>;
    class operator Implicit(const Value: Variant): TNullable<T>;

        // set values
        nullstr:=new(PNullString);
        nullstr^ := 'value 1';
        HashList.Add('one', nullstr);

        nullint:=new(PNullInteger);
        nullint^:=2;
        HashList.Add('two', nullint);

        nullint:=new(PNullInteger);
        nullint^:= NULL;
        HashList.Add('three', nullint);

RICORDO A TUTTI CHE LAZARUS si pone come linguaggio universale su tutte le piattaforme, e quindi per affrontare tali problematiche occorre una visione globale che molto spesso noi utenti non abbiamo. Gli sviluppatori invece hanno una sensibilità verso questi aspetti e quindi non propongono sicuramente una soluzione senza vagliare pro / contro a livello globale (codice esistente, piattaforme, impatto prestazionale)

Ciao ciao
Titolo: Re:esperimenti con TNullable
Inserito da: nomorelogic - Gennaio 12, 2022, 11:18:40 am
Lazarus però vuole anche essere compatibile con Delphi.
Se in Delphi quello che hai postato viene compilato (e funziona), si può segnalare la cosa come richiesta di compatibilità
Titolo: Re:esperimenti con TNullable
Inserito da: nomorelogic - Gennaio 12, 2022, 11:30:54 am
L'alternativa c'è ovviamente, ma è assolutamente "grezza", inefficiente e priva di significato: basta avere due metodi di ingresso e due metodi di "uscita" e uso uno o l'altro in funzione di quello che è il valore effettivo come nell'esempio qui sotto
Codice: [Seleziona]
 //è solo esemplificativo !!!!
if Valore = NULL then
  NullableInteger.ValueNull := Valore
else
  NullableInteger.Value := Integer(Valore);


infatti questo non avrebbe senso, ma i campi dei database hanno .AsVariant quindi questo si potrebbe già fare

Codice: [Seleziona]
var 
  s: TNullableString;
begin
   ...
   s.AsVariant := Table1.FieldByName('campo1').AsVariant;
   ...
end;


Edit:
però torniamo sempre li, il fatto è che
Table1.FieldByName('campo1').AsInteger non varrà mai null
dovremmo invece avere
Table1.FieldByName('campo1').AsNullableInteger


Edit2:
penso che potrei provare un class Helper per TIntegerField...
appena posso provo

Titolo: Re:esperimenti con TNullable
Inserito da: nomorelogic - Gennaio 12, 2022, 11:33:22 am
Con FPC 3.3.1 le cose non cambiano. Devo provare FPC 3.4.

La fpc 3.3.x è la trunk che un giorno verrà rilasciata come 3.4 quindi se non funziona sulla 3.3.x non c'è speranza per la 3.4
Se implementano la cosa nella trunk invece sappiamo che arriverà nella 3.4
Titolo: Re:esperimenti con TNullable
Inserito da: DragoRosso - Gennaio 12, 2022, 12:13:02 pm
In Delphi quello che ho abbozzato funziona, ci sono ancora alcuni dettagli da definire (l'avevo abbozzato per portarlo su Lazarus).

Anche in Delphi il compilatore non è propriamente compatibile al 100% con i Generic, tanto è vero che i Nullable non sono supportati ufficialmente e secondo una nota di Embarcadero ci stanno lavorando.

Richiedere il porting sicuramente sarebbe da fare, fermo restando però che l'aspetto principale che è l'overloading completo (parametri dei metodi e valore di ritorno) Lazarus non c'è l'ha.

Pensa che ho già implementato l'ingresso di un dato variant e la sua conversione nel formato <T>, compreso il NULL, grazie alle RTTI.

Di fatto è un casting, ma comunque un casting a codice non a compilazione (ovviamente, essendo il dato di ingresso non specificato).

La compatibilità che chiederemmo secondo me è troppo precoce, e forse è già stata richiesta.

P.S.: ho aggiornato il tuo codice con qualcosa di più moderno, c'è pure l'enumerazione  ;D

Codice: [Seleziona]
var nullint: PNullInteger;
    nullstr: PNullString;
    i: integer;
    //HashList: TFPHashList;
    HashList: specialize TDictionary<string, pointer>;
    HashEl: specialize TPair<string, pointer>;
begin
  WriteLn('Start');
  //HashList:=TFPHashList.Create;
  HashList := specialize TDictionary<string, pointer>.Create;
  try
    // set values
    nullstr:=new(PNullString);
    nullstr^ := 'value 1';
    HashList.Add('one', nullstr);

    nullint:=new(PNullInteger);
    nullint^:=2;
    HashList.Add('two', nullint);

    nullint:=new(PNullInteger);
    nullint^:= 5;
    HashList.Add('three', nullint);

    // write
    //WriteLn('one = ', PNullString(HashList.Find('one'))^.Value);
    //WriteLn('two = ', PNullInteger(HashList.Find('two'))^ .Value);
    //WriteLn('three = ', PNullInteger(HashList.Find('three'))^ .Value);

    WriteLn('one = ', PNullString(HashList.Items['one'])^.Value);
    WriteLn('two = ', PNullInteger(HashList.Items['two'])^ .Value);
    WriteLn('three = ', PNullInteger(HashList.Items['three'])^.Value);

    // free values
    //for i := 0 to HashList.Count-1 do begin
    i := 0;
    for HashEl in HashList do begin
        try
          WriteLn('Dispose ' + i.ToString);
          // Dispose( PNullString(HashList[i]) );
          //FreeMem(HashList[i]);
          FreeMem(HashEl.Value);
        except
          on e: exception do begin
             WriteLn('Error during sispose (' + i.ToString + '):' + e.Message);
          end;
        end;
        inc(i);
    end;

  finally
    // free resources
    FreeAndNil(HashList);
    WriteLn('Stop');
  end;
end;             
Titolo: Re:esperimenti con TNullable
Inserito da: DragoRosso - Gennaio 12, 2022, 12:18:30 pm
però torniamo sempre li, il fatto è che
Table1.FieldByName('campo1').AsInteger non varrà mai null
dovremmo invece avere
Table1.FieldByName('campo1').AsNullableInteger

Secondo me, lo cose devono restare come sono: perchè in assegnazione devi "castare" il dato .... lascialo nativo....
Codice: [Seleziona]
Table1.FieldByName('campo1').Value := NullInt; <- Può essere valorizzato tranquillamente come NULL o Integer

EDIT: è da qui che ci deve essere il supporto dei Generic agli overload completi e del compilatore.

Ciao
Titolo: Re:esperimenti con TNullable
Inserito da: DragoRosso - Gennaio 12, 2022, 02:56:01 pm
Esempio:

Codice: [Seleziona]
        
        nullstr:=new(PNullString);
        nullstr^ := 'value 1';
        HashList.Add('one', nullstr);

        nullint:=new(PNullInteger);
        nullint^:=2;
        HashList.Add('two', nullint);

        nullint:=new(PNullInteger);
        nullint^:= NULL;
        HashList.Add('three', nullint);

        // write
        WriteLn('one = ', TNullString(HashList.Items['one']).Value);
        WriteLn('two = ', PNullInteger(HashList.Items['two'])^ .Value);
        WriteLn('three = ', TNullInteger(HashList.Items['three']).Value);  <- Qui il risultato dà 0 perchè WriteLn lo trasforma in 0

        testvariant := NullInt^;

        WriteLn('three is NULL ? -> ', testvariant = NULL);   <- qui viene testato invece che il valore NULL sia effettivamente "passato"

        nullint^:= 18;

        WriteLn('Ora invece è -> ', TNullInteger(NullInt).Value);
Titolo: Re:esperimenti con TNullable
Inserito da: nomorelogic - Gennaio 12, 2022, 03:11:45 pm
quindi lo hai finito con i variant in Delphi?
il risultato è buono :)


cosa esce in output con?
Codice: [Seleziona]
WriteLn('three = ', TNullInteger(HashList.Items['three']).Value = NULL);
Titolo: Re:esperimenti con TNullable
Inserito da: DragoRosso - Gennaio 12, 2022, 04:56:22 pm
quindi lo hai finito con i variant in Delphi?
il risultato è buono :)

cosa esce in output con?
Codice: [Seleziona]
WriteLn('three = ', TNullInteger(HashList.Items['three']).Value = NULL);

No, non l'ho terminato. Infatti ..... ritorna FALSE

WriteLn con quel confronto richiede implicitamente un puntatore, puntatore che non è ovviamente NULL .... Stessa cosa che accade quando viene richiesto il valore dello stesso record più sopra (che è NULL ma WRITELN riporta 0). Devo implementare la class operator per i puntatori.

Il record come l'ho impostato adesso, funziona correttamente se usi la forma IMPLICITA (quindi senza il .Value) con le Variant o la forma esplicita / implicita però nello stesso formato <T>. L'implementazione di WRITELN probabilmente richiede un puntatore e il compilatore genera qualcosa che non controllo.

WriteLn non accetta la forma implicita del record (perchè manca la definizione di puntatore immagino).

Vedo di implementare anche i puntatori e fare qualche test per validare il tutto. Poi potremmo anche passare l'unità agli sviluppatori FPC e che se la sbroglino loro poi.

A più tardi.

Ciao
Titolo: Re:esperimenti con TNullable
Inserito da: DragoRosso - Gennaio 12, 2022, 06:29:01 pm
Aggiornamento:

eh eh, non avevo pensato al compilatore .... alle volte è contorto ....

Nella forma:
Codice: [Seleziona]
TNullableInt(.....).Value = .......

Il maledetto converte l'espressione di destra nello stesso tipo di quella di sinistra ....  :o :o :o

EDIT: e la conversione avviene come RECORD MANAGED dello stesso tipo <T> tramite il metodo CREATE e tutte le funnzioni annesse.
         Quindi bisogna fare attenzione a quello che si fà, perchè viene usato direttamente dal compilatore....

Ora siamo a posto. La situazione comunque non è perfettamente omogenea.

Questo perchè non ci sono limiti alla fantasia e alla perversione, come insegna @nomorelogic (  :P ).

Se devo usare i Nullable, DEVO ESSERE CERTO che chi riceve i dati (ad esempio una variabile, una funzione un ...... GESTISCA CORRETTAMENTE I NULL !!!! (ovviamente ammesso che usi i NULL).

In pratica se ciò che esce dal Nullable non è gestibile dal ricevente .... verrà generato un errore runtime.

Questo è il caso di Writeln che dei NULL proprio non ne vuole sapere.

Per fare ciò sono ricorso ad uno stratagemma ... usare una chiamata differenziata così:

Codice: [Seleziona]
WriteLn('three = ', PNullInteger(HashList.Items['three'])^.GetValueOrDefault);

Il Nullable riporterà o il valore corretto, o nel caso di NULL il valore di default del tipo.

Ora si puà gestire correttamente il metodo implicito. Con i risultati attesi.

Codice: [Seleziona]
WriteLn('three is NULL ? -> ', PNullInteger(HashList.Items['three'])^ = NULL);

In pratica,  la proprietà "Value" del NULLABLE non si dovrebbe mai usare (dovrei implementare le EXPLICIT, ma di fatto non ha molto senso non potendo comunque generare con il NULL un valore diverso da ..... NULL  ;D )

Le Explicit avrebbero senso per consentire una conversione gestita invece che lasciare fare al compilatore.

Ma va al di fuori delle funzionalità del NULLABLE.
 
Unico problema ... funziona in Delphi ... fà uso di RTTI ...

Ciao
Titolo: Re:esperimenti con TNullable
Inserito da: DragoRosso - Gennaio 12, 2022, 07:25:51 pm
quindi lo hai finito con i variant in Delphi?

Nel Nullable il variant viene usato solo ed esclusivamente per accettare (leggere) il valore NULL (che non viene memorizzato nel record ovviamente), con relative funzionalità del compilatore, e per generare il NULL di ritorno.

Nel codice del nullable ho implementato la capacità di acquisire un valore variant anche diverso dal NULL e convertirlo se possibile nel tipo <T> (altrimenti genera un errore runtime). Così pure in uscita se ce nè la necessità,  il tutto dovrebbe essere "automatico" e sotto giudizio del compilatore grazie agli operatori IMPLICITI.

Ciao
Titolo: Re:esperimenti con TNullable
Inserito da: nomorelogic - Gennaio 12, 2022, 08:12:30 pm
bel lavoro, complimenti

ci hai pensato se dare in pasto il sorgente delphi a quelli di fpc?
secondo me ne vale la pena :)

nomorelogic


PS:
poi un giorno mi spieghi la storia della "perversione"  :o

Titolo: Re:esperimenti con TNullable
Inserito da: DragoRosso - Gennaio 12, 2022, 09:06:52 pm
PS:
poi un giorno mi spieghi la storia della "perversione"  :o

Uno che la mattina si sveglia e vuole lavorare con una tecnologia mai vista prima, mai implementata da nessuno, compatibile con nessuno ..... se non è perversione questa  ;D

ci hai pensato se dare in pasto il sorgente delphi a quelli di fpc?
secondo me ne vale la pena :)


Si, però prima devo fare qualche prova per verificare che tutto sia OK, con un pò di casi in modo da presentarli con l'implementazione. E pormi il più possibile il linea con Lazarus.

Cercherò di fare presto.

Ciao
Titolo: Re:esperimenti con TNullable
Inserito da: DragoRosso - Gennaio 12, 2022, 11:37:50 pm
Stò commentando (utile anche per aggiornare il blog) e sono andato a guardarmi GITLAB FPC dove c'è proprio questo argomento (1 anno fà) in oggetto, compresa messa in discussione la compatibilità con Delphi.

La discussione è stata chiusa con l'aggiornamento all'uscita della prima versione accessibile di Delphi con il TNullable implementato (o riproducibile). Un tentativo di riapertura è stato "cassato".

Nella discussione, un utente (ovviamente sviluppatore FPC) riportava esattamente la stessa problematica che ho riscontrato io.

Siccome Emb non supporta ancora ufficialmente TNullable ma è uscita una versione accessibile di Delphi che "dovrebbe" supportare (il condizionale è d'obbligo purtroppo) quanto fatto, forse posso tentare di riaprire il caso.

Ciao

Titolo: Re:esperimenti con TNullable
Inserito da: nomorelogic - Gennaio 13, 2022, 09:20:47 am
Potresti mettere il link della discussione?
Lasciarlo in questo thread può essere utile anche per chi arriverà ad interessarsi di questa cosa in futuro.
Titolo: Re:esperimenti con TNullable
Inserito da: DragoRosso - Gennaio 13, 2022, 09:37:15 am
Questo è il link di discussione su questo argomento (TNullable e compatibilità con Delphi).

https://gitlab.com/freepascal.org/fpc/source/-/issues/37128 (https://gitlab.com/freepascal.org/fpc/source/-/issues/37128)

Ciao
Titolo: Re:esperimenti con TNullable
Inserito da: DragoRosso - Gennaio 15, 2022, 06:59:53 pm
Il TNullable è stato testato e aggiornato con gli aggiustamenti post debug. E' stato aggiornato con alcune peculiarità, ad esempio conoscere il tipo <T> anche se mi ritrovo il record come semplice puntatore.

Test approfonditi sono stati fatti con integer, string, variant, simulando anche eventuali "disguidi" .....

Tutto sommato sembra abbastanza funzionale. Lo si può potenziare aggiornandolo ed espandendolo per un typecast interno più esteso e un supporto a più operatori.
Però sino a quando non verrà supportato nativamente dai compilatori (con le ottimizzazioni del caso), lavorarci troppo sopra non ha molto senso.

I tempi ovviamente di esecuzione soffrono della non ottimizzazione (anche a livello progettuale).
 
In allegato i tempi di esecuzione in un normale programma (non in Threading) senza uso di funzionalità parallele. Per il calcolo del tempo con gli integer si è usata una routine assembler, per dare anche un raffronto di prestazioni reali.

I cicli sono costituiti da 10M di addizioni.

Ciao
Titolo: Re:esperimenti con TNullable
Inserito da: DragoRosso - Gennaio 16, 2022, 12:41:42 pm
Alcune news:

buone notizie x Lazarus, e non proprio buonissime x Delphi.

La matematica per i generic come un TNullable<T> non viene supportata nativamente da Delphi, che lascia spazio alle costruzioni di interfacce per sviluppare gli algoritimi applicativi del caso. Questa è la soluzione più flessibile ma anche la più dispendiosa in termini di energie, oltre al possibile impegno per il mantenimento. Inoltre, non essendo un supporto diretto a livello di compilazione (si usano le interfacce), sofrirebbe sivuramente in termini prestazionali.

Invece FPC supporta la matematica nei generic TNullable<T> (non sò fino a che livello) e quindi risulta più semplice applicarli.

Emb stà "studiando" la situazione.

Esempi:

Codice: [Seleziona]
type
  TNullInteger: TNullable<integer>;

var
  NullInt: TNullInteger;
  NullInt2: TNullInteger;
  A: varinant;

//Assegnazioni: OK
  A := unassigned;
  NullInt := NULL;
  NullInt2 := 10;
  NullInt := NullInt2;
  NullInt := unassigned;
  NullInt2 := A;
  A := 50;
  NullInt := A + 10;

//Le operazioni (nativamente):
  NullInt := NullInt +10;   //DELPHI NO, LAZARUS SI

//Comparazioni
  (NullInt = NullInt2);
  (A = NullInt)
  (NullInt = A)
  (NullInt2 = 10)
  (NullInt2 = NULL)
Titolo: Re:esperimenti con TNullable
Inserito da: DragoRosso - Gennaio 17, 2022, 03:17:06 pm
E' stato fatto il porting per eguagliare le funzionalità tra Delphi e Lazarus.

Lo sviluppo del codice è formalmente terminato. Siamo in fase di TEST (con @nomorelogic) con tutti i tipi più comunemente utilizzati.

Produzione del progetto di Esempio con tutte le funzionalità per documentazione uso e test utente.

Poi lo pubblichiamo qui nel forum (e aggiorniamo il blog).

Ciao