Ruby, Continuazioni e Generatori

Wednesday, 30 May 07
Merzia sta passando a Ruby per lo sviluppo delle prossime applicazioni. Abbiamo scelto di utilizzare mod_ruby con eruby, invece che il famoso framework Ruby on Rails, perche' la nostra filosofia di sviluppo e' quella di fare e usare cose semplici, con poche dipendenze, in cui abbiamo tutto il controllo del codice. In breve stiamo portando elementi del nostro mini-framework dal PHP al Ruby.

Insomma per questi motivi per ora gioco abbastanza con Ruby, ieri e' stata la volta di callcc, che esattamente come in Scheme chiama una funzione passando a questa la continuazione corrente del programma.

Cosa significa di preciso? Prendiamo questo codice per riferimento:
continuazione=nil
flag=nil

(1..10).each {|x| puts x if x == 7 callcc {|c| continuazione=c} end }

if flag == nil flag=true continuazione.call end


Il codice "conta" da 1 a 10, ma quando x ha il valore di 7 tramite callcc viene chiamata la funzione anonima (o blocco che dir si voglia) {|c| continuazione=c} che prende un argomento, c, che contiene la continuazione corrente, e la salva nella variabile continuazione.

Per il resto il ciclo continua normalmente.

Alla fine, se non e' gia' stato fatto in precedenza (serve a questo il flag), tale continuazione viene chiamata tramite il suo metodo call. Il risultato e' che il programma ricomincia da dove era arrivato.

L'output del programma e' il seguente
1
2
3
4
5
6
7
8
9
10
8
9
10
Si noti la ripetizione di 8, 9, 10. Siamo andati indietro nel tempo per un attimo col programma :) Il flag e' utile perche' altrimenti il programma entrerebbe in un loop infinito, perche' dopo aver scritto nuovamente 8, 9, 10 ... la continuazione verrebbe chiamata ancora una volta.

Non e' magia... per chi capisce un po' di implementazione di linguaggi di programmazione quello che succede e' che callcc salva lo stack frame in un oggetto, e il suo metodo call non fa altro che ripristinare lo stack frame precedentemente salvato.

Ovviamente nello stack frame ci sono solo le variabili locali, dunque gli attributi degli oggetti (o variabili di istanza se preferite), cosi' come le variabili globali non vengono ne salvate ne ripristinate dalla continuazione.

Insomma per farla breve ieri ho scritto un po' di codice per giocare con questa idea che simula i generatori di Python. Ecco il codice:

class Methodgen
    def initialize(f,*args)
        @f = f
        @args = args
        @gencont = nil
        @more = true
    end
    def next
        if @more == false
            return nil
        end
        if (@gencont == nil)
            @gencont=@f
        end
        callcc {|@retcont|
            @gencont.call(*@args) {|@ele|
                callcc {|@gencont|
                    @retcont.call
                }
            }
        }
        if @ele == nil
            @more = false
        end
        @ele
    end
end

def foobar(incr) (1..10).each {|x| yield incr+x } yield nil end

a = Methodgen.new(method(:foobar),5); b = Methodgen.new(method(:foobar),50); puts a.next puts b.next puts a.next puts b.next puts a.next puts b.next


Inutile spiegarlo perche' richiederebbe un articolo intero ;) Ma chi vuole giocare con queste idee forse potrebbe trovare in questo pezzo di codice uno spunto di approfondimento.

In ogni caso l'output del programma e' il seguente:
6
51
7
52
8
53


Ruby e' molto interessante, flessibile e leggero, e anche se alcune cose sembrano un po' arbitrarie mi sembra la migliore alternativa in giro attualmente.
2870 views*
Posted at 12:26:23 | permalink | 14 comments | print