venerdì , 6 Dicembre 2019
iten
Home » Programmazione » C# visto da un pythonista/1

C# visto da un pythonista/1

Era da un po' di tempo che mi solleticava l'idea di lanciarmi nello studio di un nuovo linguaggio di programmazione, visto che sono ormai 8 anni e più che programmo esclusivamente in Python. E avendo abbandonato di sana pianta i linguaggi a tipizzazione statica, l'idea era proprio quella di ritornare a studiarne uno.
Complice il talk di Federico di Gregorio al PyCon Due, la mia attenzione è caduta su C#, il linguaggio di elezione per la piattaforma .NET di Microsoft. La scelta è stata effettuata per i seguenti motivi:

  1. Non è Java e in quanto tale non è molto diffuso
  2. Non è un linguaggio di 'basso livello' in cui ci si deve preoccupare di gestire l'allocazione di variabili, puntatori, ecc
  3. È sicuramente un linguaggio di ispirazione più moderna
  4. Non si è necessariamente vincolati alla Microsoft

Quest'ultimo punto è quello che mi ha spinto ad avventurarmi nell'impresa: nonostante C# sia un linguaggio di casa Microsoft, è stato standardizzato (fino alla versione 2.0, ma è in corso di standardizzazione la 3.0) dalla ECMA, e grazie al progetto Mono è possibile sviluppare e distribuire applicazioni C# (attenzione, ho detto C# e non .NET) su qualunque piattaforma (Linux, Win, Mac, UNIX). In realtà conoscevo già il progetto Mono, e in passato aveva provato a darci uno sguardo. Fino a 2 anni fa poteva considerarsi poco più di un progetto sperimentale a vita (una sorta di Wine), ma di recente grazie al contributo di Novell si può considerare una piattaforma Open completa ed affidabile, che sta in tempi brevi supportando anche le versioni 3.0 e 3.5 di .NET.
Quindi, secondo me, l'abbinata C# + Mono può essere considerata una valida alternativa per lo sviluppo di applicazioni multipiattaforma.

Ma veniamo al C#. Beh, devo dire che ritornare dopo anni a pensare 'statico' è stato uno shock, molto ma molto più forte del processo inverso, ossia abituarsi alla tipizzazione dinamica venendo da quella statica. La tipizzazione dinamica porta a pensare in termini di funzionalità e di interfacce di oggetto (cosa ancor più vera in Python che ha potenti funzionalità di introspezione), a concentrarsi sulla struttura del programma e non sulla coerenza di tipi e variabili e a generare codice molto 'prolisso' e dispersivo. Insomma, il duck typing spinge il programmatore a scrollarsi dalle frustrazioni tipiche che si hanno quando si programma con linguaggi più tradizionali. E a dirla tutta, ho la sensazione che la tipizzazione statica sia la preistoria dell'informatica (e dell'ingegneria del software stessa), e che forse Microsoft poteva osare un po' di più nel proporre un linguaggio nuovo. Ma ammetto che in genere questo tema divide non poco i programmatori.
Mettendo per un attimo da parte la tipizzazione, cosa c'è in C# che può accumunarlo al Python, o magari renderlo ancora più distante? Secondo Federico, addirittura a tratti C# è un Python con le parentesi graffe. Sarà così?
Queste sono una serie di conclusioni a cui sono giunto dopo i primi 10 capitoli di questo libro della O'Reilly:

  1. quasi 100 keyword: troppe. Punto.
  2. le parentesi graffe se le potevano risparmiare: non se ne può più! Sono 35 anni che ci ritroviamo queste maledette {} d'avanti, e veramente è troppo. Innanzitutto, il codice sorgente diventa estremamente lungo e farraginoso, quando con il ':' di Python si riesce a rendere tutto molto più compatto e pulito

    public void myFunc(int param) {
         if (param > 1) {
              Console.WriteLine(i);
         }
    }

    quando, invece, in Python basta scrivere:

    def myFunc(param):
        if param > 1:
            print param

    Poi, a me sta venendo il tunnel carpale per digitare la combinazione Altgr+Shift+[
     

  3. datemi la print: si è vero, anche Python 3.0 avrà la sua brava funzione print() al posto di una keyword, ma print() è infinitamente più breve di Console.WriteLine()
  4. ok agli shared references: anche C# implementa il modello degli shared references come Python. In parole povere, una variabile C# è una terna del tipo (nome, tipo, riferimento), e i tipi primitivi generano oggetti immutabili. Quindi l'operazione di assegnamento implica far condividere a due nomi lo stesso riferimento ad un oggetto. L'unica differenza con Python è che il passaggio di parametri può avvenire anche nella modalità 'ref', dove ad essere passata come parametro non è il solo riferimento ma tutta la varabile (poi, voglio capire perché tutte le guide e i manuali che ho letto hanno un'abilità tale a complicare l'argomento che difficilmente si riesce a comprendere che il concetto che c'è sotto è molto semplice).
  5. non bastavano protected, private e public: C# supporta i tipici modificatori di accesso del filone C++ (in realtà manca friend), ma ne introduce di nuovi come internal (accessibile dalle classi dello stesso assembly - una sort di friend con scope a visibilità di modulo), ma anche override per indicare che una sottoclasse sta effettuando l'overriding di un metodo. Se non si è capito, faccio parte di quelli che sono convinti che impiegare 10 giorni per decidere se una metodo o un attributo devono essere private, internal o public è solo una perdita di tempo e un offesa all'intelligenza dei programmatori.
  6. polimorfismo ed ereditarietà controllata: C#, sulla falsa riga di C++, permette di controllare il polimorfismo con l'uso del modificatore virtual, ma anche con override per indicare espressamente che un metodo effettua l'overriding di un altro. Anche l'ereditarietà può essere controllata e anche impedita per mezzo della keyword sealed (l'equivalente del final Java). Tutti i tipi primitivi la usano, e quindi non si può derivare, ad esempio, dal tipo String, cosa che al contrario in Python è possibile da un bel po' di tempo (versione 2.2), ma è anche vero che le stringhe C# supportano in maniera nativa Unicode, cosa che in Python non avverrà prima della versione 3 con l'unificazione di oggetti str e unicode. 1-1 e palla al centro.
  7. interessante l'implementazione del costrutto di interfaccia: C# implementa il costrutto di interfaccia. Ora, non c'è molto da meravigliarsi dato che siamo in presenza di un linguaggio moderno a tipizzazione statica, però lo fa in maniera molto interessante. Infatti, è possibile estendere interfacce (creare interfacce che ereditano da altre interfacce), è possibile definire polimorfismo a livello di interfaccia, è possibile definire vincoli sui tipi dei 'generics' in base ad un'interfaccia prestabilita (con la keyword where), esporre selettivamente i metodi di un interfaccia.
  8. generics, servono: mancavano nelle prime versioni di C#, ci sono dal 2.0. In un linguaggio a tipizzazione statica, se se ne abbraccia il credo, servono. Interessante la possibilità di poter definire vincoli sull'interfaccia del tipo generico T.
  9. direttamente da Python, i generatori: pardon, gli enumeratori. Esattamente lo stesso concetto presente in Python (stessa keyword yield), ma basati su un'interfaccia specifica IEnumerable<T>. Inutile dire che sono utilissimi.
  10. dizionari con keyword arbitrarie: il tipo Dictionary<K,V> (che non è primitivo come in Python) consente di specificare tipi arbitrari per le chiavi, e non solo oggetti immutabili come in Python, a patto che tale oggetto non cambi nel flusso di esecuzione.
  11. possibilità di specificare funzioni di conversioni tra tipi: questa è una funzionalità che reputo sublime. A differenza di quanto si legge in giro, C# è un linguaggio fortemente tipato e quindi non c'è il concetto di cast presente nel C (che è debolmente tipato). Quello che viene chiamato erroneamente cast, è la possibilità del C# di specificare le funzioni di conversione dei tipi, sia implicite sia esplicite. Ad esempio:
     

    public static implicit operator MyType(int i) {
        //Qui il codice di conversione da intero
    }
    public static explicit operator int(MyType t) {
        //Qui il codice di conversione verso intero
    }
    ...
    public static void Main(string[] args) {
        MyType t = 10; //Usa la conversione implicita
        int a = (int)t; //Usa la conversione esplicita
    }

 

Queste sono le prime osservazioni che riesco a fare su C#. Man mano che andrò avanti nello studio del linguaggio e del framework Mono posterò nuovi articoli. Nel frattempo, come sempre, sono apprezzati commenti ed osservazioni.


Check Also

Breakpoint condizionali durante il debug su STM32

È un esigenza che si verifica molto di frequente quando si lavora con l'hardware, soprattutto …

5 comments

  1. Michele Simionato

    Se volevi imparare un linguaggio a tipizzazione statica facevi
    meglio a studiarne uno con type inference, tipo SML o Haskell; C# pare proprio una pizza per un Pythonista.

  2. C# pare proprio una pizza per un Pythonista

    Beh si, è vero che è una pizza (è incredibile quanto siano prolissi e dispersivi i programmi scritti con C#), però è anche vero che se tu vuoi un linguaggio da adoperare in un ambiente di produzione, quelli che mi hai citato dubito che vadano bene. E poi ammetto che non riesco a digerire i linguaggi funzionali, è più forte di me.

  3. Io uso quasi sempre Delphi, ma capisco che purtroppo, soprattutto  per problemi commerciali, è un linguaggio condannato ad un'inesorabile declino. Per questo sto valutando la possibilità di iniziare ad utilizzare un nuovo linguaggio; C# è uno di quelli che stò valutando.Ho trovato interessanti le tue osservazioni che mi hanno mostrato come C# abbia ereditato molte caratteristiche da Delphi. E non è un caso, visto che lo sviluppo, sia di .NET che di C#, sono diretti da  Anders Hejlsberg che, in Borland, ha sviluppato sia turboPascal che Delphi.Anch'io provo un certo senso di orticaria alla vista di quel marasma di {}, però di qualche cosa bisogna pur morire!! D'altronde non si può nemmeno dire che i Begin/end del Pascal siano particolarmente gustosi!Nel C# apprezzo l'approccio "fai quel che vuoi ma voglio essere sicuro che lo fai volendolo fare" che è alla base di casting esplicito, virtual/override dichiarato....che èalla base anche di Delphi. E lo apprezzo perchè con Delphi ho sperimentato che previene un sacco di possibili errori subdoli.Questa similitudine sintattica e funzionale con Delphi mi renderebbe più semplice la migrazione, anche se al momento non so valutare se C# è destinato a diventare un linguaggio duraturo e di punta;  non vorrei scoprire di aver impiegato energie per passare da un linguaggio di nicchia ad un'altro linguaggio di nicchia. Cosa ne pensi del futuro di .NET e di C#?

  4. Scommettere sulla longevità di una piattaforma e di un linguaggio è sempre un azzardo non di poco conto, soprattutto nel medio/lungo periodo. Non ne parliamo poi se di mezzo c'è la Microsoft, che ci ha abituato molto spesso a cambiamenti anche pesanti dall'oggi al domani. Mi ricordo ancora bene il filone del COM/COM+ sviluppatosi sul finire degli anni '90 e gli inizi del 2000: sembrava la soluzione definitiva e soprattutto destinata a durare anni. Poi d'improvviso arrivò .NET, spazzando via tutto (con qualche dubbio di troppo, IMHO).
    Quanto .NET sia una piattaforma valida, questo secondo me è un punto molto delicato. C'ho giocato per un pochino, e ho realizzato dei piccoli programmi che ci servivano per garantire della compatibilità verso applicazioni Windows. Io credo che sia una piattaforma valida ed interessante ma incompleta. Incompleta perché un arsenale di quella portata relegata esclusivamente al settore Windows è troppo limitante. Si c'è Mono, però alla fine se si cerca la stabilità di sviluppo e la piena compatibilità, allora perché non scegliere Java? Per qualche feature in meno del linguaggio? Dubito che questo possa essere solo una valida ragione per adottare C#/.NET. D'altro canto, oggi come oggi i framework si assomigliano un po' tutti, e secondo me un linguaggio si sceglie rispetto ad un altro solo su scelte radicali: ad esempio, scelgo Python o C#, ma dire scelgo C# o VisualBasic.NET o anche Java mi sa di ideologico.
    Vivrà nel lungo periodo C#? Secondo l'indice TIOBE C# non è al momento un linguaggio "killer", e da quel che si vede in giro (soprattutto in Italia) è così. Oggi il mercato dei linguaggi è abbastanza polarizzato sulle applicazioni finali: web o resto del mondo. E la maggiore vivacità sta proprio nel settore web, dove è vero che la fa da padrone PHP, però altri linguaggi rosicchiano fette di mercato, e soprattutto questo è il segmento dove è più facile oggi introdurre innovazione.
    Questo per dire che secondo me nel medio/lungo periodo le cose resteranno così come sono, e quindi è un investimento che si può fare. D'altro canto io sono sempre stato convinto che sono meglio le nicchie: si lavora più tranquilli e ci si riesce a ricavare le proprie autonomie. Quindi, dovendo limitare l'ambito a Windows, la mia risposta è sicuramente sì.

  5. Ho iniziato a lavorare, negli anni 80, in una azienda dove si sviluppava con microprocessori ad 8 bit intel e si poteva scegliere (ma scegliere è una parola grossa, diciamo che molto dipendeva dalle esigenze e dalle idiosincresie dei clienti) fra assembler e PLM. Il PLM, linguaggio abbastanza simile al Pascal, prevedeva cose gustose come la ricorsività, ma non la nidificazione dei contesti, l'assembler era l'assbler, punto e basta. Si lavorava su sistemi di sviluppo che usavano 3 floppy disk simultaneamente per compilare, uno con i sorgenti e gli oggetti, uno con il sistema operativo ed uno con il sistema operativo. I listati andavano direttamente sulla stampante e, di solito, la compilazione inziava il venerdì sera e terminava ad un'ora imprecisata della domenica, se tutto andava bene si poteva lavorare per una settimana con le stampe "fresche", altrimenti, se finiva la carta, si esauriva il nastro inchiostrato, si inceppava la stampante o simili, si continuava con quelle della settimana prima.

    Durante la settimana si debuggava inserendo, byte per byte, le toppe in zone libere della memoria e mettendo dei jmp per passare dalla parte sbagliata alla toppa e rientrare alla fine. Tutte queste modifiche si memorizzavano sul disco del sistema di sviluppo e venivano caricate automaticamente assieme all'applicativo fino alla successiva compilazione.

    Se poi si aveva la fortuna di lavorare ad un progetto su mini (PDP 11 o microvax) si poteva, udite udite, scrivere in FORTRAN, un vero lusso, specie perché c'era a disposizione il debugger simbolico e poi, diciamocelo, quando bastano poche decine di minuti a compilare è quasi una bazza.

    Col tempo sono arrivati altri linguaggi ed hardware più potenti, ci siamo fatti tutta la famiglia 86, senza saltare un processore, e gli RTK sono diventati sempre più simili a sistemi operativi. Abbiamo scoperto il pascal, l'ADA, l'OCCAM, il parallel C, il C++ e poi... e poi basta. Noi, quelli che a scuola studiavano F4004 prima dello Z80, quelli che avevano un mini con 64 RAM e due dischi rigidi da 5 MBYTE l'uno condiviso fra tutti gli studenti, quelli che usavano l'editor di linea e lo trovavano un grande vantaggio rispetto alle schede perforate ed ai nastrini, abbiamo smesso di desiderare linguaggi diversi. A prescindere dal fatto che, volendo trovare un difetto nel C++ l'unico che mi viene in mente è, quando si eredita per vie traverse dalla stessa classe, come cavarci i piedi e che, comunque, si tratta di un caso abbastanza estremo, non credo proprio che l'usare astrazioni sempre più complesse sia giustificato.

    La morte di Pascal è stata decretata, anche se pochi se ne rendono conto, non dai limiti del linguaggio, che all'epoca ci pareva elegantissimo, ma dalla mancanza di un hardware adeguato alle caratteristiche del linguaggio stesso e dal fatto che, di conseguenza, il passaggio fra l'alto livello e l'eseguibile non era così immediato con gli annessi svantaggi sia in termini di rapidità di esecuzione che in termini di controllo del codice generato. Oggi i processori sono così potenti che anche usando linguaggi molto lontani dall'hardware difficilmente ci si trova in difficoltà, però di fatto si scrive codice inefficiente e, specialmente, i programmatori di oggi spesso non hanno ben chiaro cosa succede quando scrivono le cose in un determinato modo e non in un altro, come questo viene tradotto in istruzioni da eseguire e memoria da utilizzare. Già il C++, se usato a fondo, richiede un'ottima preparazione di base per mantenere il controllo sul codice generato e dubito che chi non ha mai programmato mescolando assmbler, C e C++ sia veramente in grado di gestirlo nei dettagli.

    Credo fermamente che tutto questo proliferare di linguaggi interpretati e semiinterpretati, di framework che dovrebbero rappresentare l'astrazione di ciò che, di fatto, non si può astrarre e così via siano semplicemente un modo per allontanarsi dalla mera realtà delle cose e cioè che, dopo la programmazione funzionale e la programmazione ad oggetti, nessuno ha ancora veramente inventato niente di nuovo e, specialmente, che si dovrebbe concentrarsi di più sui problemi e meno sugli strumenti usati per risolverli.

    Per il resto, siccome sono un brontolone, anzi, per essere un informatico direi pure un vecchio brontolone, non prendetevela troppo a male per le mie parole, forse sono solo io che non capisco i tempi moderni.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Questo sito usa Akismet per ridurre lo spam. Scopri come i tuoi dati vengono elaborati.