Archivio per la categoria "Ruby"



Ruby Social Club, Milano - Atto terzo: missione compiuta

Anche se con un po' di ritardo, ci tenevo a esprimere la mia soddisfazione per la bella serata che è stata organizzata mercoledì scorso in quel di Milano dai ragazzi di Mikamai per il terzo Ruby Social Club. Una trentina di persone (successone), atmosfera molto informale, talk veloci ma interessanti e una transumata di massa al locale dove sono continuate le discussioni sugli argomenti più disparati tra birra e qualche piatto di cibarie. Purtroppo non sono mai riuscito a partecipare alle precedenti edizioni e devo dire che questa volta è valsa la pena fare di tutto per esserci. Il buon riffraff (a cui tra l'altro devo anche una birra, lo scrivo qui così rimane fino alla prossima occasione) ha già scritto qualche dettaglio sparso sulla serata, per cui non mi dilungherò ulteriormente. Sicuramente ci saranno altri appuntamenti in futuro, per cui... al prossimo RSC 🙂

Sinatra has taken the stage!

Non si tratta del compianto Frank, ma di uno degli ultimi framework per lo sviluppo web arrivati in casa Ruby. La caratteristica principale di Sinatra è la sua semplicità, ancora più spinta rispetto a quella a volte anche un po' troppo magica di Camping: con sole 5 righe è possibile ottenere una risposta dal server. Non ci credete? Allora, dopo averlo installato usando rubygems (gem install sinatra -y), provate il seguente codice...

# main.rb require 'rubygems' require 'sinatra' get '/' do 'Cool, Sinatra is performing on the main stage!' end

Usando il comando ruby main.rb si ottiene un'istanza di Mongrel con la nostra applicazione pronta a ricevere le chiamate dal browser, mentre nella finestra del terminale si viene avvisati che Sinatra has taken the stage on port 4567! Non resta da far altro che verificare l'effettivo funzionamento con un semplice wget -qO- http://localhost:4567/ (oppure curl http://localhost:4567/ se preferite):

Cool, Sinatra is performing on the main stage!

Sinatra può essere definito un micro-framework traverstito da DSL dotato di una grammatica ridotta, semplice e immediata. Sinatra non obbliga lo sviluppatore a strutturare la propria applicazione seguendo l'architettura MVC (ma è comunque possibile replicarla), non è legato a un particolare motore di template (ma supporta molto bene ERB e l'ottimo HAML), non offre la miriade di helpers inclusi nel prezzo come altri framework e inoltre è assolutamente ORM-agnostico: tutto ciò può avere i suoi pro e contro, ma queste caratteristiche lo rendono indubbiamente un ottimo framework per lo sviluppo di prototipi o di applicazioni semplici ma relativamente veloci. Sinatra, per come è strutturato, promuove inoltre lo sviluppo di interfacce REST per le proprie applicazioni web, rendendolo quindi uno strumento agevole per prototipizzare API.

get '/' do # mostra risorsa end post '/' do # crea risorsa end put '/' do # aggiorna risorsa end delete '/' do # cancella risorsa end

Viene naturale a questo punto chiedersi come vengano gestite le rotte, ma anche in questo caso è tutto molto semplice e intuitivo:

get '/hello' do redirect '/hello/anonymous' end get '/hello/anonymous' do 'Hello Mr. Anonymous, I would be glad to know your name!' end get '/hello/:name' do user = params[:name] "Hello, #{user}!" end # wget -qO- http://localhost:4567/hello # Hello Mr. Anonymous, I would be glad to know your name! # wget -qO- http://localhost:4567/hello/anonymous # Hello Mr. Anonymous, I would be glad to know your name! # wget -qO- http://localhost:4567/hello/NRK # Hello, NRK!

Occorre solamente far notare che Sinatra effettua il lookup delle rotte nell'ordine di definizione, per questo motivo la seconda rotta /hello/anonymous non viene catturata dalla terza che invece specifica un più generico /hello/:name. Concentrandosi appunto su quest'ultima, possiamo notare come più in generale le variabili all'interno di una rotta possano essere definite semplicemente sfruttando la stessa sintassi utilizzata per definire un simbolo in Ruby e come esse vengano automaticamente rese disponibili nell'hash params.

Se avessimo bisogno di un approccio più in stile MVC? Partendo dal presupposto che con Sinatra non serve creare una classe controller ma basta definire le singole azioni, a questo punto mancano solo le viste. Occorre quindi creare una directory views nel path della nostra applicazione, preparare il proprio template salvando il file relativo nella suddetta directory (per esempio greet.haml oppure greet.erb a seconda di quale motore di template vogliate usare) e scrivere una semplice riga di codice per la rotta:

get '/hello/:name' do haml :greet end

Per convenzione Sinatra effettua l'autocompletamento del nome del file della vista aggiungendo .haml o .erb, per cui basta specificarne il nome. Se avessi voluto utilizzare erb avrei scritto erb :greet ma dal momento che haml tutto sommato mi piace (gem install haml), ecco qui il codice d'esempio assolutamente minimale:

!!! %html %head %title Greetings! %body %span Hello, %strong= params[:name]

A questo punto la chiamata a http://localhost:4567/hello/NRK produrrà il seguente output:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html> <head> <title>Greetings!</title> </head> <body> <span> Hello, <strong>NRK</strong> </span> </body> </html>

Tutto molto semplice, no?

Sinatra è in una fase di sviluppo abbastanza attiva ma è un framework già particolarmente interessante per tutte quelle necessità in cui Ruby on Rails oppure framework similari risulterebbero soluzioni spropositate. Essendo basato su Rack è possibile servire le applicazioni usando server differenti da Mongrel, come per esempio Thin o Phusion Passenger via Apache. Personalmente lo sto provando nei ritagli di tempo per creare una sorta di gateway da un servizio JSON-RPC che avevo realizzato qualche tempo fa a un servizio REST-oriented con tanto di interfaccia web per la gestione delle azioni e la visualizzazione dei dati, per ora sono molto soddisfatto della sua semplicità.

Interagire con Windows tramite Ruby

Oggi stavo effettuando alcune ricerche su internet quando sono incappato in un mio messaggio inviato qualche mese fa sulla mailing list di ruby-it in risposta a chi chiedeva aiuto su come poter recuperare l'elenco delle unità montate su un sistema Windows. Ruby ormai offre da tempo un supporto particolarmente maturo all'interazione con il sistema su piattaforme Windows tramite Win32OLE e alle svariate librerie del progetto Win32 Utils. In questo caso, a dimostrazione della flessibilità delle soluzioni offerte da Ruby, lo stesso task può essere risolto in tre modi molto differenti tra loro.

Il primo approccio sfruttando il modulo Windows::Volume delle Win32 Utils consiste nell'utilizzare in maniera diretta le API di Windows e la sua implementazione, anche se può apparire grezza e magari non troppo elegante agli occhi dello sviluppatore Ruby puro, è comunque piuttosto facile:

require 'windows/volume' include Windows::Volume def get_drive_letters buffer = " " * 1024 length = GetLogicalDriveStrings(buffer.length, buffer) buffer[0..length].split(0.chr) end p get_drive_letters # => ["A:\\", "C:\\", "D:\\", "E:\\", "F:\\", "G:\\", "H:\\", "I:\\"]

Utilizzando GetLogicalDriveStrings si ottiene una lista di tutte le unità logiche montate nel sistema, indipendentemente dalla loro natura fisica, il tutto sotto forma di buffer contenente l'elenco di lettere di unità separate dal carattere NULL. Questo ci costringe a istanziare un buffer e a estrarne il contenuto con cui è stato riempito dalla funzione API in una maniera abbastanza lontana dal tipico stile di programmazione in Ruby, ma è pur sempre fattibile e tutto sommato non è poi così brutto a vedersi, c'è di peggio in altri linguaggi.

In Windows esistono poi gli oggetti COM messi a disposizione dalla Scripting Runtime Library e uno di questi, tra l'altro forse tra i più utilizzati, è FileSystemObject. Sinceramente sentire questo nome mi fa venire gli incubi tornare in mente a quando svariati anni fa mi è toccato usare ASP 3.0 che, per la cronaca, non ho mai sopportato. Il fatto di non dover usare le API Win32 salendo quindi di livello ci permette di utilizzare semplicemente Win32OLE:

require 'win32ole' def get_drive_letters drives = [] fso = WIN32OLE.new('Scripting.FileSystemObject') fso.drives.each { |drive| drives << "#{drive.driveletter}:\\" } drives end p get_drive_letters # => ["A:\\", "C:\\", "D:\\", "E:\\", "F:\\", "G:\\", "H:\\", "I:\\"]

L'astrazione messa a disposizione da Win32OLE per le collection restituite da oggetti COM include automaticamente un metodo each che ci permette di iterare in maniera familiare e comoda il risultato dalla proprietà Drives dell'istanza di FSO.

Esiste infine la possibilità di utilizzare l'interfaccia messa a disposizione da WMI (Windows Management Instrumentation) per ottenere le stesse informazioni in maniera ancora più intuitiva con un approccio molto SQL-like nell'interrogazione delle informazioni sui sistemi Windows:

require 'win32ole' def get_drive_letters drives = [] wmi = WIN32OLE.connect('winmgmts://') devices = wmi.ExecQuery('SELECT * FROM Win32_LogicalDisk') devices.each { |drive| drives << "#{drive.DeviceId}\\" } drives end p get_drive_letters # => ["A:\\", "C:\\", "D:\\", "E:\\", "F:\\", "G:\\", "H:\\", "I:\\"]

Interrogando la classe WMI Win32_LogicalDisk si può ottenere rapidamente una nutrita serie di informazioni risparmiandosi un sacco di giri o chiamate, delegando tutto al sistema operativo. Inoltre è possibile scremare già in fase di richiesta e molto facilmente il set di risultati desiderato, del resto come abbiamo visto le query WMI ricordano molto da vicino SQL. Per fare un esempio, cambiando la nostra query in SELECT * FROM Win32_LogicalDisk WHERE FileSystem = 'NTFS' è possibile ottenere l'elenco di tutte le unità il cui filesystem è NTFS. Un altro vantaggio non indifferente è quello di poter richiedere le stesse informazioni a computer remoti semplicemente cambiando la stringa di connessione al provider WMI, per esempio utilizzando una stringa di connessione come winmgmts:{impersonationLevel=impersonate}!\\192.168.1.2\root\cimv2 (in un dominio AD può tornare molto comodo per tenere sotto controllo le proprie installazioni).

E' possibile fare tutto e in molte maniere, ce ne è per tutti i gusti. Il vantaggio di poter interagire con Windows attraverso Ruby anziché con i soliti VBScript e JScript risiede prima di tutto nella sua estrema flessibilità rispetto ai suddetti linguaggi di scripting rivali forniti di serie nel sistema operativo, ma anche nella possibilità di sfruttare nei propri script di automazione tutte le classi standard e di terze parti che gravitano intorno a Ruby è un vantaggio da non sottovalutare nello sviluppo di task amministrativi più complessi. Ok posso percepire anche coloro che ci terrebbero a sottolineare come sia possibile fare le stesse cose con Perl e Python... sì, avete perfettamente ragione, ma qui si pubblicizza Ruby ;-P

Ruby 1.9.0 in arrivo...

Il conto alla rovescia è ormai cominciato: tra meno di una dozzina di ore farà capolino la nuova major release di Ruby. Il rilascio di questa nuova versione, salvo imprevisti dell'ultimo minuto, è programmato per le 15.00 JST (le 7.00 di mattina ora italiana) e, come sottolineato da Matz, quella che verrà distribuita sarà appunto la versione 1.9.0 e non la 1.9.1 come da roadmap. Il motivo di questa variazione di programma rispetto ai piani è semplicemente legato al fatto che, pur avendo completato e implementato tutti i cambiamenti previsti per questa release, la stabilità nel suo insieme risulta essere inferiore rispetto alle aspettative e ci sono già alcuni bug conosciuti che verranno indicati nelle note di rilascio. Si tratterà comunque della prima vera occasione per iniziare il porting dei propri script e applicazioni dal momento che è stata introdotta una nutrita quantità di cambiamenti, tantissimi piccoli e alcuni grossi, che rendono questa versione deliberatamente incompatibile, pertanto la 1.8 rimarrà ancora per un po' di mesi la versione di riferimento in ambito di produzione. Tre delle più importanti novità di Ruby 1.9 sono la nuova VM (ex YARV), il supporto per unicode e il passaggio da green-thread a thread nativi (seppur con qualche limitazione volutamente imposta, il nuovo modello di threading arriverà nella sua completezza solo con Ruby 2.0), il tutto seguito a ruota da una vasta lista di cambiamenti più o meno piccoli che potete trovare elencati in maniera piuttosto completa in questa pagina. Preparatevi...

Microbenchmark: Ruby 1.9.0 contro tutti (... non proprio...)

Il rilascio di Ruby 1.9 è previsto intorno a Natale e con il passare dei giorni la curiosità dei rubyisti si fa sempre più intensa. Stuzzicato da un microbenchmark pubblicato da Antonio Cangiano sul suo blog in cui ha messo a confronto la velocità di esecuzione di Ruby 1.8.6, Ruby 1.9.0 e Python 2.5.1 usando volutamente una funzione ricorsiva che calcolasse i primi 35 numeri della successione di Fibonacci, ho pensato che sarebbe stato interessante fare lo stesso test con i suoi script ma con condizioni diverse. A differenza di Antonio, che ha eseguito il suddetto microbenchmark sul suo MacBook Pro con OS X 10.5, ho pensato di effettuare i miei test su Windows chiamando in causa a questo punto anche IronRuby e, tanto per fare, anche PHP e Lua. Mi sarebbe piaciuto provare anche JRuby, ma purtroppo non ho nessuna esperienza e non saprei nemmeno da dove cominciare.

  • Configurazione: Microsoft Windows XP SP2 (x86) - AMD Athlon64 3700+ (2,2 GHz) - 2 GB RAM
  • Dettaglio versioni:
    • Ruby 1.9.0 (SVN rev. 14067 compilata con VC++ 2005)
      ruby 1.9.0 (2007-12-01 patchlevel 0) [i386-mswin32_80]
    • Ruby 1.8.6 (Ruby OneClick Installer 1.8.6-25)
      ruby 1.8.6 (2007-03-13 patchlevel 0) [i386-mswin32]
    • IronRuby 1.0 pre-alpha (SVN rev. 65 compilata con CSC di VS 2005)
      IronRuby Pre-Alpha (1.0.0.0) on .NET 2.0.50727.832
    • Python 2.5.1 (Standard Python 2.5.1 Windows Installer)
      Python 2.5.1 (r251:54863, Apr 18 2007, 08:51:08) [MSC v.1310 32 bit (Intel)]
    • Python 3.0a1 (Standard Python 3.0a1 Windows Installer)
      Python 3.0a1 (py3k:57844, Aug 31 2007, 16:54:27) [MSC v.1310 32 bit (Intel)]
    • PHP 5.2.5 (Binari ufficiali per Win32 - versione TS - no debug - Zend Engine 2.2.0)
      PHP 5.2.5 (cli) (built: Nov  8 2007 23:18:51)
    • PHP 5.3.0 (Snapshot ufficiale - versione TS - no debug - Zend Engine 2.3.0)
      PHP 5.3.0-dev (cli) (built: Dec  1 2007 04:19:04)
    • PHP 6.0.0 (Snapshot ufficiale - versione TS - no debug - Zend Engine 3.0.0-dev)
      PHP 6.0.0-dev (cli) (built: Dec  1 2007 02:04:58)
    • Lua 5.1.2 (LuaBinaries per Win32 di LuaForge)
      Lua 5.1.2  Copyright (C) 1994-2007 Lua.org, PUC-Rio

Le build di Ruby 1.9 e IronRuby sono state effettuate direttamente da me allineandomi questa mattina con i trunk nei rispettivi repository SVN, nel primo caso senza badare a ottimizzazioni particolari e nel secondo caso usando la modalità release. Per le versioni di sviluppo di PHP ho prelevato questa mattina da snaps.php.net i binari ufficiali e ho effettuato i test con la configurazione (php.ini) di default, così come per la versione stabile 5.2.5. Lo script per Python è stato leggermente modificato per la versione 3.0 dal momento che print è diventata una funzione e funziona diversamente dalla versione 2.5.

Gli script usati per Ruby, IronRuby e Python sono gli stessi di Antonio, aggiungo solo le mie varianti Lua e PHP (occhio, si tratta solamente di un becero port ai rispettivi linguaggi). Ecco come si presenta la versione in Lua:

function fib(n) if n == 0 or n == 1 then return n end return fib(n - 1) + fib(n - 2) end for n = 0, 35 do print("n=" .. n .. " => " .. fib(n)) end

Ed ecco invece la versione in PHP:

function fib($n) { if ($n == 0 || $n == 1) return $n; return fib($n - 1) + fib($n - 2); } for ($i = 0; $i <= 35; $i++) echo "n=", $i, " => ", fib($i), "\n";

I tempi riportati di seguito rappresentano la media calcolata sui risultati di 6 esecuzioni scartando la prima. Ecco i risultati ottenuti:

Ruby 1.9.0 : 21,123s
Ruby 1.8.6 : 94,736s
IronRuby : 26,123s
Python 2.5.1 : 33,827s
Python 3.0a1 : 48,718s
PHP 5.2.5 : 91,672s
PHP 5.3.0 : 77,813s
PHP 6.0.0 : 73,969s
Lua 5.1.2 : 14,912s

Ora un po' di considerazioni:

  • Ruby 1.9 è dannatamente più veloce di Ruby 1.8.6. Punto.
  • IronRuby non è nemmeno uscito dallo stato di pre-alpha e già offre prestazioni molto buone, vicine a quelle di Ruby 1.9 che invece si sta avvicinando al rilascio. Ottimo!
  • Mi incuriosisce la differenza tra i tempi di Ruby 1.8.6 registrati da Antonio su OS X e quelli registrati da me su Windows: 159 secondi rispetto a 95 secondi mi sembra un abisso, soprattutto perché immagino che un Core 2 Duo 2.2 GHz sia più performante di un Athlon64 3700+ 2.2GHz.
  • Python 3.0 è più lento di Python 2.5.1, ma la versione 3.0a1 risale a fine Agosto per cui sarebbe il caso di ripetere lo stesso benchmark con una versione più aggiornata.
  • PHP sembra migliorare abbastanza con il passaggio da Zend Engine da 2.2.0 a 2.3.0, ma comunque le prende alla grande da Ruby 1.9 e Python.
  • Lua li straccia senza alcun ritegno.
  • Ordine di arrivo: Lua 5.1.2, Ruby 1.9.0, IronRuby 1.0 pre-alpha, Python 2.5.1, Python 3.0a1, PHP 6.0, PHP 5.3, PHP 5.2.5, Ruby 1.8.6.

Ovviamente un singolo microbenchmark di questo tipo non può essere assolutamente considerato come un valido indicatore delle prestazioni offerte da un linguaggio in situazioni reali, però i risultati sono comunque curiosi. A quanto pare i rubyisti avranno già un bel regalo assicurato per questo Natale 🙂

PS: noto al termine della scrittura che Antonio ha scritto un altro articolo, aggiungendo Lisp e Haskell.