Archivio per il mese di luglio 2007



Estendere le classi builtin di IronRuby con nuovi metodi

In precedenza abbiamo visto come con IronRuby sia già possibile estendere a runtime le classi builtin sfruttando lo stesso Ruby e il suo concetto di classi aperte, tuttavia molti metodi che noteremo mancare in queste prime fasi di sviluppo, come MutableString#reverse usato per il nostro esempio ma anche molti altri , fanno parte del set di metodi “standard” delle classi builtin dell’interprete Ruby originale di Matz. L’ideale sarebbe quindi implementarli direttamente in IronRuby, guadagnandone anche in prestazioni, ma la sorpresa è che questo lavoro non è per nulla complicato.

Muoversi all’interno dell’archivio con i sorgenti è semplice, nella directory Docs è presente un readme.htm (tra l’altro realizzato con TiddlyWiki) con alcune indicazioni sulle linee guida per la scrittura del codice (SourceCodeLayout) e un paragrafo che fa al caso nostro: come aggiungere un metodo a una classe builtin (AddAMethod). In Src abbiamo la directory eMicrosoft.Scripting e Ruby, rispettivamente i sorgenti del DLR e di IronRuby. I file su cui andremo a lavorare sono contenuti in Src\Ruby\Builtins dove si trovano le implementazioni di tutte le classi builtin di IronRuby.

A questo punto apriamo il file Src\Ruby\Builtins\MutableString.cs, scriviamo il nostro metodo all’interno della classe MutableString e vediamo di commentarlo:

1 [RubyMethodAttribute("reverse", RubyMethodAttributes.PublicInstance)] 2 public static MutableString Reverse(MutableString str) { 3 if (str.Length <= 1) 4 return new MutableString(str); 5 6 char[] reversed = new char[str.Length]; 7 int len = str.Length - 1; 8 for (int i = 0; i <= len; i++) 9 reversed[i] = str[len - i]; 10 11 return new MutableString(new String(reversed)); 12 }
  • riga 1: per specificare il nome che verrà usato in Ruby per il metodo e la sua visibilità sono stati creati rispettivamente l’attributo RubyMethodAttribute e l’enum RubyMethodAttributes. Come spiegato nel documento allegato, il motivo per cui viene utilizzato RubyMethodAttribute è semplicemente dovuto al fatto che in Ruby possono esistere nomi di metodi non validi in C# come empy? oppure downcase!, inoltre in questo modo è possibile ricreare la moltitudine di operatori come <=> (comparazione) oppure =~ (corrispondenza).
  • riga 2: i metodi che andremo a creare sono statici poiché in realtà il DLR sfrutta una propria implementazione degli extension method di C# 3.0, in cui i metodi di estensione si presentano appunto come statici e il primo argomento è rappresentato da this per i metodi di istanza. In questo caso, il primo argomento sarà rappresentato dall’oggetto chiamante (è come se da Ruby arrivasse self).

Commentiamo rapidamente l’implementazione, saltando solo la parte che si occupa semplicemente di invertire la stringa:

  • riga 3-4: se la stringa è vuota oppure è formata da un solo carattere allora è inutile proseguire dal momento che non c’è molto da invertire, tuttavia ritorniamo ugualmente una nuova istanza di MutableString poiché questo è il comportamento di Ruby, basta osservare in IRB con l’interprete originale come ogni chiamata a String#reverse restituisca un oggetto con un object_id sempre differente:
    irb(main):001:0> "CIAO".reverse.object_id
    => 23652180
    irb(main):002:0> "CIAO".reverse.object_id
    => 23648140
    irb(main):003:0> "C".reverse.object_id
    => 23644340
    irb(main):004:0> "C".reverse.object_id
    => 23640540
    irb(main):005:0> "".reverse.object_id
    => 23636820
    irb(main):006:0> "".reverse.object_id
    => 23633100
  • riga 11: non ci sono costruttori di MutableString che prevedano un array di caratteri come parametro, per cui creiamo una istanza intermedia di String partendo dal nostro array di caratteri e creiamo da quella la nostra nuova istanza di MutableString. Avremmo potuto fare in altro modo ma facciamoci andar bene questa riga, soffermarsi a questo punto dei lavori sui dettagli implementativi per spaccare il byte è abbastanza inutile visto che molte parti saranno soggette a cambiamenti nel corso dello sviluppo di IronRuby e del DLR.

Per compilare il tutto correttamente in base alle nostre modifiche, seguiamo alcuni passi:

  1. Modificare il file Build.cmd cambiando la configurazione da modalità Release a Debug ( /p:Configuration=Debug ) e poi modificare il file Src\Ruby\Builtins\GenerateInitializers.cmd togliendo il ..\ iniziale (c’è un errore nei path).
  2. Eseguire Build.cmd. Questa prima compilazione genererà l’utility Bin\Debug\ClassInitGenerator.exe che lanceremo attraverso Src\Ruby\Builtins\GenerateInitializers.cmd e che (ri)genera automaticamente il file Src\Ruby\Builtins\Initializer.Generated.cs contenente i vari dispatcher ai metodi. Ogni volta che a una classe builtin aggiungiamo un metodo visibile da Ruby, dobbiamo rigenerare questo file (si può anche editare a mano, ma è ovviamente meno comodo e aumenta la possibilità di errore).
  3. Lanciare nuovamente Build.cmd per compilare il progetto con il nostro Initializer.Generated.cs aggiornato per riflettere l’aggiunta del metodo MutableString#reverse.

Non ci resta da fare altro che verificare i risultati tramite RBX, lanciandolo da Bin\Debug\rbx.exe:

>>> "CIAO".reverse
=> "OAIC"
>>> "CIAO".reverse.object_id
=> 43
>>> "CIAO".reverse.object_id
=> 44
>>> "C".reverse.object_id
=> 45
>>> "C".reverse.object_id
=> 46
>>> "".reverse.object_id
=> 47
>>> "".reverse.object_id
=> 48

La stringa viene correttamente invertita e il comportamento è il medesimo di quello riscontrato in IRB con l’interprete Ruby originale.

Impressioni e primi passi con IronRuby

Ormai si sta parlando in diversi lidi del rilascio dei sorgenti di una prima versione “parecchio alfa” (non per niente definita pre-alfa 1) di IronRuby tuttavia non c’è niente di meglio che esplorare e sperimentare in prima persona sia le potenzialità che il codice sorgente stesso di questa nuova implementazione di Ruby totalmente basata su .NET Framework. Trattandosi di una versione ampiamente preliminare è ovvio che non tutto funzioni come dovrebbe così come è ovvio che manchino molti pezzi, però personalmente trovo che il progetto sia già in uno stato relativamente più che ottimo: la maggior parte delle mancanze risiedono più in metodi non ancora implementati per i tipi base che altro, ma John Lam ha chiarito come lui e il suo team abbiano preferito concentrarsi maggiormente su AST e parser ma soprattutto sulla struttura del core language, dispatcher per gestire da subito i blocchi tipici di Ruby e interoperabilità con .NET. Credetemi però, avere già tutto questo funzionante dopo pochi mesi e con buone prestazioni nonostante la scarsa ottimizzazione messa in atto è un risultato non indifferente, soprattutto nel contesto di un linguaggio che purtroppo manca ancora oggi di specifiche scolpite sulla pietra e dove l’unico set di specifiche è rappresentato dall’MRI ufficiale, di cui però il team di IronRuby non può consultare i sorgenti per motivi prettamente legali. Per un’altra iniezione di ottimismo basta poi dare un’occhiata al sorgente stesso di IronRuby potendone già constatare pulizia e chiarezza, tanto che nel giro di pochi minuti è possibile avere un’idea di massima su come e dove andare a mettere le mani per cominciare ad estendere in prima persona l’implementazione. Per quanto mi riguarda ho l’impressione che IronRuby si evolverà in un progetto molto importante e mi sembra che ci sia anche un crescente entusiasmo da parte dei rubysti che al tempo stesso sono o sono stati sviluppatori .NET, cosa che mi fa ben sperare per una buona partecipazione al progetto da parte della comunità.

Ora però cominciamo a fare qualche esperimento, compilando prima di tutto i sorgenti di IronRuby. Fortunatamente non è necessario avere installato Visual Studio o altri ambienti di sviluppo, basta avere una normale installazione di .NET 2.0 (anche soltanto redist, non serve l’SDK) e un editor di testo/codice decente. All’interno dell’archivio compresso troverete il file Build.cmd che contiene già le impostazioni per msbuild.exe, molto probabilmente però dovrete specificarne all’interno il path completo sostituendo le variabili. Nel mio caso, Build.cmd è diventato:

C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\msbuild.exe
      /p:Configuration=Release /t:Rebuild IronRuby.sln

Per la cronaca IronRuby può essere compilato anche con Mono in Windows o Linux, anche se allo stato attuale è necessario sfruttare una build realizzata dai sorgenti nel repository SVN. La compilazione genererà tra le varie DLL anche l’eseguibile rbx.exe che è una sorta di incrocio tra ruby.exe (interprete) e IRB (console interattiva): eseguendolo senza alcun parametro RBX parte in modalità interattiva, in pieno stile IRB, altrimenti specificando un file/script in Ruby come parametro quest’ultimo verrà eseguito direttamente proprio come con l’interprete tradizionale. Per le nostre prime prove giocheremo un po’ con la classe String. Anzi, per ora sarebbe meglio dire MutableString visto il seguente output da RBX:

>>> "CIAO".class
=> MutableString

L’output in IRB invece è:

irb(main):001:0> "CIAO".class
=> String

Come già esplicitamente detto da John Lam, questo comportamento verrà sistemato con i prossimi rilasci, tuttavia l’occasione ci permette di mettere in evidenza come una stringa in Ruby sia effettivamente mutabile dal momento che il suo contenuto può essere modificato senza necessariamente generare una nuova istanza. Proseguendo con le prove, si può incontrare fin da subito una testimonianza dell’incompletezza dei metodi per i tipi base:

>>> "CIAO".reverse
System.MissingMethodException: undefined local variable or method `reverse' for
ciao:MutableString   in Ruby.Builtins.Kernel.MethodMissing(CodeContext context, Object self,
Proc proc, SymbolId name, Object[] args) in j:\ir\pre-alpha1\Src\Ruby\Builtins\Kernel.cs:riga 169
[...]

La mancanza del metodo MutableString#reverse genera un’eccezione, tuttavia in Ruby potremmo intercettare la chiamata a un metodo non esistente sfruttando l’hook Object#method_missing che è chiamato automaticamente ogni volta che viene invocato un metodo non definito per un oggetto. Proviamo a sfruttare questo hook per vedere come si comporta IronRuby:

class MutableString def method_missing(method_name, *args) puts "Sorry, #{method_name} does not belong to me." end end

Basta copiare e incollare il codice in RBX (proprio come faremmo in IRB) e poi chiamare un metodo non definito per un oggetto di tipo MutableString, come per esempio lo stesso reverse:

>>> "CIAO".reverse
Sorry, reverse does not belong to me.
=> nil

Grande, Object#method_missing c’è e funziona! Però a noi il metodo MutableString#reverse serve proprio, riusciremo a sfruttare già in questa versione di IronRuby il concetto di classi aperte implementandolo direttamente via codice Ruby? Proviamo ampliando il codice di esempio di poco fa:

class MutableString def method_missing(method_name, *args) puts "Sorry, #{method_name} does not belong to me." end def reverse reversed = " " * self.length precalculated = self.length - 1 self.length.times do |i| reversed[i] = self[precalculated - i] end reversed end end

Come prima, incolliamo il codice in RBX e poi proviamo a invocare il metodo che abbiamo appena definito:

>>> "CIAO".reverse
=> "OAIC"
>>> "CIAO".my_undefined_method
Sorry, my_undefined_method does not belong to me.
=> nil

Funziona tutto, veramente ottimo! Ora però la faccenda comincia a farsi un po’ più complicata perché vogliamo che il nostro metodo MutableString#reverse sia implementato “nativamente” in IronRuby, per motivi puramente prestazionali ma anche perché è giusto che sia così trattandosi di un metodo effettivamente appartenente alla classe String in Ruby. In pratica è giunto il momento di cominciare a giocare con i sorgenti in C# di IronRuby, anche se lo faremo in un messaggio successivo :-)

PhpXmlRpc vs Zend_XmlRpc

In questi giorni sto implementando un servizio XmlRpc con PHP 5 di cui sto sviluppando anche una classe che faccia da wrapper al client grezzo, in modo da poterla poi passare a un mio collega senza che debba badare ai dettagli dell’implementazione sottostante. Di norma per scrivere i miei server quando lavoro in PHP utilizzo la libreria PhpXmlRpc che tutto sommato svolge decentemente il suo compito, sia in termini di velocità di esecuzione che in termini di verbosità del codice (anche se per inciso fino a non molte versioni fa era un po’ allucinante da questo punto di vista). Anche per quanto riguarda la classe client avevo deciso di utilizzare la libreria in questione, tuttavia la necessità di buone prestazioni richieste dal progetto in cui verrà integrata questa parte mi ha fatto notare una bruttissima cosa nel momento in cui sono andato a studiare il risultato del profiling di XDebug in concomitanza con la chiamata a un metodo XmlRpc che restituisce circa 72 KB di stringa XML. Ecco uno stralcio del tracciato di XDebug, all’estrema sinistra c’è il tempo cronometrato dalla partenza dello script:

0.0900 0 -> xmlrpcmsg->parseResponse() D:\wwwroot\rpc\lib\xmlrpc.inc:1369
0.0900 0 -> substr() D:\wwwroot\rpc\lib\xmlrpc.inc:2442
0.0901 0 -> xmlrpcmsg->parseResponseHeaders() D:\wwwroot\rpc\lib\xmlrpc.inc:2444
[.....]
1.1307 0 -> xml_parser_free() D:\wwwroot\rpc\lib\xmlrpc.inc:2566
1.1309 0 -> is_object() D:\wwwroot\rpc\lib\xmlrpc.inc:2580
1.1309 0 -> xmlrpcresp->xmlrpcresp() D:\wwwroot\rpc\lib\xmlrpc.inc:2629

Un’eternità: poco più di 1 secondo solo per il parsing della risposta, inaudito (per la cronaca, il test è stato effettuato su un server Dual XEON 2.80 GHz con PHP 5.2.3 caricato come modulo ISAPI in IIS 6.0). Non ci avevo mai fatto caso poiché non ho usato molto PhpXmlRpc per realizzare client RPC e soprattutto non mi è mai capitato di ottenere risposte abbastanza sostanziose dal server. Il problema è dovuto al fatto che durante il parsing dell’XML di questa risposta la funzione xmlrpc_cd (che svolge la funzione di character data handler impostato tramite xml_set_character_data_handler) viene invocata una quantità spropositata di volte… e con spropositata, intendo sull’ordine delle 19.000 chiamate! A questo punto mi sono detto “Ehi, ma recentemente non era uscita la versione 1.0 definitiva di Zend Framework? Era incluso un set di classi per XmlRpc, proviamo un po’…“. Il risultato dal punto di vista della stesura del codice è nettamente migliore, solamente due righe per creare il client e per chiamare un metodo remoto con parametri. Dal punto di vista delle prestazioni della classe client invece…

0.0905 0 -> Zend_Http_Response::fromString() D:\wwwroot\rpc\Zend\Http\Client.php:768
0.0905 0   -> Zend_Http_Response::extractCode() D:\wwwroot\rpc\Zend\Http\Response.php:591
0.0906 0     -> preg_match() D:\wwwroot\rpc\Zend\Http\Response.php:430
0.0906 0   -> Zend_Http_Response::extractHeaders() D:\wwwroot\rpc\Zend\Http\Response.php:592
[.....]
0.1104 0 -> Zend_XmlRpc_Value_String->getValue() D:\wwwroot\rpc\Zend\XmlRpc\Response.php:215
0.1104 0   -> html_entity_decode() D:\wwwroot\rpc\Zend\XmlRpc\Value\String.php:57
0.2602 0 -> Zend_XmlRpc_Response->setReturnValue() D:\wwwroot\rpc\Zend\XmlRpc\Response.php:215

Con la classe Zend_XmlRpc_Client il parsing della stessa risposta porta via solamente 0,170 secondi, mettendo in evidenza una differenza enorme rispetto a 1 secondo impiegato dalla classe client di PhpXmlRpc per ottenere lo stesso risultato. Sinceramente ho sempre snobbato Zend Framework, anche perché non sviluppando siti web non ho la necessità di un framework MVC, tuttavia potrebbe contenere classi particolarmente interessanti quindi penso proprio che comincerò a dargli un’occhiata.

… e IronRuby fu!

John Lam sul suo blog ha finalmente annunciato il primo rilascio del codice sorgente di IronRuby (versione pre-alpha1). Il suo post non è un mero annuncio ma fornisce qualche dettaglio interessante (oltre alla conferma del suo talk riguardante IronRuby ad OSCON), di cui però vorrei estrapolare questa parte mentre vi inviterei a leggere il resto:

We’re also happy to announce that we will be accepting source code contributions into the IronRuby libraries. Right now we have a lot of logistical work that we still need to do, but we fully intend on getting IronRuby onto Rubyforge by the end of August.

Per conto mio si tratta di un’ottima notizia che tra l’altro mi sorprende anche non poco. BTW per chi non lo sapesse, anche se dal nome è facile intuirlo, Rubyforge è il sito che ospita la maggior parte dei progetti Ruby. Insomma che dire, a parte i complimenti a John e a tutti i membri del team di sviluppo? Ora inizia il bello, per tutti :-) Domani spero di ritagliarmi del tempo per cominciare a giocarci…

Lavoro, arretrati, caldo e cavallette

Un titolo a caso per riassumere i motivi della frequenza piuttosto dilatata negli aggiornamenti del blog in questo periodo, anche se conto di scrivere ancora un po’ prima di levarmi dalle scatole per un po’ di meritate (???!) vacanze.
A un passo dalle ferie, al lavoro giustamente si comincia a pensare a come rimettere mano a uno dei progetti più grossi che abbiamo e che è in servizio da ormai 3 anni, lo si vuole riammodernare per stare al passo con i tempi cogliendo l’occasione per potenziare tutte le componenti dell’applicazione e farvi convergere caratteristiche di altri progetti. Molte idee particolarmente buone, molti punti ancora oscuri, un sacco di studi di fattibilità in vista per alcune parti, fornitori che non mandano i preventivi necessari, etc. Insomma, la solita trafila (o meglio, la quiete prima della tempesta. E se questa è la quiete….). Tra la moltitudine di tecnologie che saranno sfruttate, questa volta avrò il piacere di poter fare un maggiore affidamento su Ruby rispetto al passato recuperando parti già realizzate per la precedente versione del progetto, convertendone altre scritte con altri linguaggi e creandone di nuove in base alle esigenze che sono nate e che nasceranno strada facendo.
Per quanto riguarda gli arretrati c’è solo l’imbarazzo della scelta tra libri, fumetti ed episodi sparsi di serie che seguo. Anche se per i libri ci penserò durante le ferie, ho iniziato dal resto a smaltire la coda prima che diventi troppo tardi e che comincino a uscire dalle fottute pareti.
Caldo. Sì insomma me ne sono accorto pure io che fortunatamente sono condizionato nell’85% dei posti che frequento, ma quel rimanente 15% negli ultimi giorni è stato abbastanza pesante relegandomi quasi interamente ad attività dal basso consumo energetico. NRKfreq.
Cavallette perché ci stava bene nel titolo.