Displaying articles with tag

Il Distributive Conjunction Pattern

Posted by Chiaroscuro, Mon May 14 16:43:00 UTC 2007

La soluzione in stile dichiarativo che utilizza each non ci soddisfa ancora completamente. Questo codice contiente infatti ancora varie ripetizioni e ridondanze. Consideriamo ad esempio il termine person che deve venir specificato una volta come parametro ed una volta come variabile. Certo, senza l’uso di person non potremmo utilizzare l’iteratore perchè non avremmo alcuna referenza all’oggetto su cui vogliamo andare ad operare.

Tuttavia è possibile immaginare l’aggiunta di un metodo capitalize! alla classe Array. Questo metodo, a sua volta, andrebbe ad invocare capitalize! su ogni suo singolo elemento, utilizzando il proprio iteratore interno.

1
2
3
4
5
 class Array
    def capitalize!
      self.each { |e| e.capitalize! }
    end
  end

Vi vedo già rabbrividire. Non solo perchè oso violare la classe Array con i miei noiosi metodi, totalmente fuori contesto all’interno di un Array, ma anche perchè a questo punto Array dovrebbe venir invasa da un duplicato di qualsiasi metodo io possa voler invocare su un qualsiasi oggetto.

Lasciamo in sospeso il primo punto per un attimo (vi prometto di ritornarci) e affrontiamo il secondo. Possiamo difenderci dall’invasionedei metodi clonati utilizzando un primo tocco di magia dinamica: method_missing. Invece di andare a specificare un doppione per ogni potenziale metodo invocabile su ogni potenziale membro di un array, andiamo a supporre che qualunque metodo invocato sull’array e non riconosciuto dall’array stesso, sia in realtà indirizzato ai suoi elementi.

Proviamo:

1
2
3
4
5
 class Array
    def method_missing method, *args
      self.each { |e| e.send method, *args }
    end
  end

E’ stato un piccolo colpo di tacco, ma già funziona.


puts ["john","mike","sam"].capitalize!

produce il risultato desiderato:

1
2
3
  John
  Mike
  Sam

Tramite questa generalizzazione abbiamo anche ottenuto l’effetto secondario di essere in grado di gestire automaticamente i parametri di questi proxy-metodi.

Proviamo a trasformare in ‘1’ tutte le ‘i’ di una lista di nomi (no, non chiedetemi perchè):


puts ["simon","mike","jim"].tr!('i','1')

ancora successo:

1
2
3
  s1mon
  m1ke
  j1m

Ora che abbiamo il nostro iteratore automatico possiamo divertirci ad invocare ogni sorta di metodi sui nostri array.

Basteranno però pochi esperimenti per andare a sbattere contro un piccolo problema. Come possiamo, ad esempio, ottenere la lunghezza di tutti gli elementi di un array? Risulta non essere possibile, in quanto il metodo size che dovremmo invocare su ogni elemento, ha già un significato nel contesto di Array dove rappresenta la lunghezza dell’array stesso.

E qui ritorniamo al problema che avevo precedentemente posticipato: i pericoli dell’inquinamento semantico. Da una lato abbiamo trovato un modo rapido ed espressivo per distribuire chiamate agli elementi di un array, mentre dall’altro abbiamo scoperto che questo può portare ad uno scontro di significato tra le esigenze dell’array-ospite e quelle degli oggetti-ospitati.

La soluzione che ho elaborato, e che ho visto essere utilizzata con alcune verianti in diversi progetti, cerca di ottenere un punto di equilibrio tra le due opposte tensioni di iperspecificità e sintesi (a volte ambigua). Ho chiamato questa tecnica Distributive Conjunction Pattern e il suo utilizzo si esplica in:


people.all.capitalize!

Il codice è espressivo ed è possibile capire al colpo d’occhio l’intento dello sviluppatore. E’ forse meno chiaro risalire a come questo codice possa funzionare.

Intanto vediamo perchè si tratta di una conjunction. Con il termine congiunzione non mi riferisco letteralmente alle congiunzioni dei linguaggi parlati, ma ne reinterpreto in qualche modo lo spirito. Il metodo all è una congiunzione perchè di per se non fa nulla ma genera significato mettendo in relazione il people su cui è chiamato e il capitalize! che è a sua volta invocato sull’oggetto risultante da all.

Perchè questa congiunzione sarebbe distributive? Come la proprietà distributiva trasforma (a + b + c) * X in a*X + b*X + c*X , così una congiunzione distributiva andrà a distribuire un certo metodo su ogni membro dell’array d’origine della congiunzione.

Ora che sappiamo che l’unico scopo di all è di mettere in relazione due termini e distribuire il secondo sul primo, vediamo come implementarlo in pratica.

Il primo passo consiste nell’aggiungere il metodo all ad Array, e delegare a quel qualcosa che verrà ritornato dal metodo all la responsabilità di distribuire tutte le chiamate agli elementi dell’array. Questo qualcosa ritornato da all possiamo allora chiamarlo ArrayDistributor? o ArrayDistributiveConjunction?, per rendere chiaro che si tratta di un oggetto temporaneo utilizzato al solo fine di congiungere l’array con qualcosaltro.

1
2
3
4
5
6
7
8
9
10
11
12
13
  class ArrayDistributiveConjunction
    def initialize
       ... 
    end
  end

  class Array

    def all
      ArrayDistributiveConjunction.new
    end

  end

Come sarà strutturata una conjunction? Dobbiamo saperlo anche per capire come poterla inizializzare adeguatamente entro il nuovo metodo all di Array.

Dal momento in cui la conjuction viene generata vive di vita propria e deve essere in grado di gestire tutte le potenziali chiamate indirizzate verso gli elementi dell’array di origine. Dobbiamo quindi:

  1. ricordarci l’array d’origine – altrimenti su cosa andiamo a distribuire?
  2. accettare qualunque chiamata – da distribuire per l’appunto sull’array di origine della conjunction.

Affrontiamo il primo punto, adeguando anche la classe Array:

1
2
3
4
5
6
7
8
9
10
11
  class ArrayDistributiveConjunction
    def initialize array
       @array = array
    end
  end

  class Array
    def all
      ArrayDistributiveConjunction.new self
    end
  end

non è stato difficile.

Per il secondo punto, invece, possiamo riutilizzare la soluzione del method_missing illustrata in precedenza. L’unica differenza è che in questo caso il contesto della distribuzione non è più l’array stesso, ma un array esterno che viene puntato da un attributo della conjunction:

1
2
3
4
5
6
7
8
9
  class ArrayDistributiveConjunction
    def initialize array
       @array = array
    end
   
    def method_missing method, *args
        @array.map { |e| e.send method, *args }
    end
  end

In realtà abbiamo applicato un altro piccolo cambiamento, utilizzando l’iteratore map piuttosto che each per poter gestire una più ampia varietà di metodi che non solamente quelli che modificano direttamente gli oggetti su cui operano.

Il codice finale è semplice e breve:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  class ArrayDistributiveConjunction
    def initialize array
       @array = array
    end
   
    def method_missing method, *args
        @array.map { |e| e.send method, *args }
    end
  end

  class Array
    def all
      ArrayDistributiveConjunction.new self
    end
  end

e fa il lavoro che deve fare con efficienza e senza fronzoli.

Una chiamata diretta alla size di un array


  puts ["simon","mike","jim"].size

genera


  3

mentre una chiamata a size, distribuita tramite all


  puts ["simon","mike","jim"].all.size

genera un array delle size dei suoi elementi

1
2
3
  5
  4
  3

Incidentalmente questa soluzione ha un effetto interessante. Oltre a distribuire delle chiamate sugli elementi di un array, potete anche distribuire delle estrazioni dagli stessi. Un pò come una SELECT su una TABLE in SQL, per capirci.

Supponiamo di avere una semplicissima classe Person:


  Person = Struct.new :name, :surname, :age

e una lista people:

1
2
3
4
5
  people = [
    Person.new('john','smith',30),
    Person.new('mike','hammer',45),
    Person.new('sam','spade',33)
  ]

ora, l’uso della distributive conjunction ci permetterà di estrarre al volo una lista dei nomi, cognomi o età delle persone:

1
2
3
  puts people.all.name       # >> ['john','mike','sam']
  puts people.all.surname  # >> ['smith','hammer','spade']
  puts people.all.age          # >> [30, 45, 33]

Abbiamo visto la distributive conjunction, ma vi sono molti altri usi interessanti delle congiunzioni al fine di rendere il proprio codice più leggibile, compatto ed espressivo che esploreremo in futuri articoli.

1 comment | Filed Under: Linguaggio Metodologia | Tags:

Programmare dall'interno!

Posted by bard, Mon Jan 29 22:59:00 UTC 2007

Ma è tardi: l’Allievo ha già maturato dubbi sull’eterno ciclo dello Scrivi-Compila-Esegui.

Durante i suoi esercizi, infatti, un evento strano, insolito e affascinante si è verificato:

Qualcosa Non Ha Funzionato.

Ed ha scoperto, con sgomento, che la frase più importante nella sua futura vita di programmatore minaccia di essere un semplice:


  System.out.println("A questo punto x vale " + x); 

Dove sono finiti gli eleganti algoritmi, le magnifiche strutture invisibili, fatte della materia di cui sono fatti i sogni? Tutto è mutevole, egli osserva, tutto è passeggero nell’avvicendarsi dei sorgenti. Un’unica costante: “System.out.println()”.

L’Allievo si è poi reso conto che l’evento è tutt’altro che strano, insolito, o affascinante. Frugando tra le carte di Allievi di altre Scuole, ha notato una sinistra ricorrenza di:


  printf("A questo punto y vale %d\n", y);

e:


  window.alert("A questo punto z vale " + z);

Preda dello sconforto, va dal Maestro. Lo trova indaffarato a tracciare, su una lavagna bianca, una rete intricata di rettangoli neri, pieni di scritte, collegati da frecce. Per un attimo, alla vista di una simile gloria, dimentica il motivo che lo ha portato lì, e lascia che lo sguardo si perda nella complessità. “Maestro”, inizia, e indica uno dei rettangoli, “che cos’è un Logger?” “Oh, niente di particolare. Consideralo… hmmm… una specie di System.out.println() evoluto…”

Un grido atroce taglia l’aria. L’Allievo fugge, lasciando una complessità non più tanto gloriosa e un esterrefatto Maestro.

Cartoline dal Tempo di Esecuzione

Perché System.out.println(), window.alert(), printf(), e altre forme più “evolute” dello stesso inquilino finiscono sempre col trovare un posto sotto il tetto dei nostri programmi?

In molti linguaggi di programmazione, printf() e amici sono tra i pochi in grado di attraversare quella barriera che pare alzarsi tra programmatore e programma quando questo viene avviato.

Finché il sorgente è nell’editor possiamo esplorarlo in lungo e in largo, manipolarlo, modellarlo, insomma tutto (purché non si tratti di commentarlo!). Quando però ”...il compilatore lo traduce in un linguaggio di più basso livello e comprensibile alla macchina. La macchina lo esegue…”, bam!, non siamo più nel programma. Il programma è sgusciato via e il massimo che possiamo convincerlo a fare è mandarci di tanto in tanto cartoline attraverso le “Poste PrintF” dicendoci come sta.

Se stai pensando che sarebbe bello, a tempo di esecuzione, avere la stessa facoltà di esaminare, e perché no, di modificare il programma, che avevi prima di avviarlo, rallegrati, non tutto è perduto: basta scegliere gli strumenti giusti. E no, non sto parlando del debugger.

Uno di questi strumenti è (sorpresa) Ruby.

Dalla User Interface alla Programmer Interface in Ruby

Tutti i programmi hanno una interfaccia per l’interazione con l’utente, ben pochi ne hanno una per l’interazione col programmatore. Vediamo come creare un programma che abbia entrambe, e come un’interfaccia per il programmatore aiuti lo sviluppo fin dalla prima riga.

Il programma che scriveremo preleverà un dato dal web su richiesta dell’utente, ne farà un’elaborazione minima, e mostrerà i risultati attraverso una interfaccia grafica. Il dato sarà un feed RSS, l’elaborazione consisterà nell’estrazione dei titoli, e l’interfaccia grafica sarà scritta con Tk (che non è esattamente una gioia per gli occhi, ma è incluso in tutte le distribuzioni di Ruby).

Il codice che segue crea e avvia un’interfaccia minima:

1
2
3
4
5
6
7
8
9
10
require 'tk'
root = TkRoot.new { title 'Hello, world!' }

button = TkButton.new(root) { text 'Get data' }
button.pack 'side' => 'left', 'fill' => 'y'

output = TkText.new(root) { width 20; height 5 }
output.pack 'side' => 'left', 'fill' => 'y'

Tk.mainloop

Il sorgente a questo punto: Versione #1.

Resistiamo alla tentazione di cominciare a scrivere subito la logica di controllo. Modifichiamo l’ultima riga in modo che l’interfaccia venga eseguita in un thread separato e il programma prosegua:


  Thread.new { Tk.mainloop }

Aggiungiamo una manciata di righe che avvieranno IRb, la console interattiva, all’interno del programma, facendone la nostra interfaccia programmatore:

1
2
3
4
5
6
7
8
9
10
11
12
require 'irb'

module IRB
  def IRB.start_in_binding(b)
    setup(nil)
    irb = IRB::Irb.new(WorkSpace.new(b))
    @CONF[:MAIN_CONTEXT] = irb.context
    catch(:IRB_EXIT) { irb.eval_input }
  end
end

IRB.start_in_binding(TOPLEVEL_BINDING)

Il sorgente a questo punto: Versione #2.

Avviamo il programma da linea di comando. Dopo la comparsa della finestra, anche la linea di comando ci saluterà con un:


 irb(main):001:0> 

E’ proprio quella IRb familiare banco di prova di frammenti di codice, ma stavolta nasce in un mondo già popolato. Infatti:

1
2
3
4
5
6
7
irb(main):001:0> output
=> #<TkText:0xb70a6a38 @tags={}, @cmdtbl=[], @path=".w00001">
irb(main):002:0> root
=> #<TkRoot:0xb710c388 @path=".">
irb(main):003:0> button
=> #<TkButton:0xb710bac8 @path=".w00000">
irb(main):004:0>

Benvenuto dentro il programma.

Guardiamoci ancora intorno. Un’occhiata approfondita a button e root conferma che sono proprio i button e root definiti all’inizio:

1
2
3
4
5
6
7
8
9
10
11
12
13
 irb(main):006:0> button.text
=> "Get data"
irb(main):007:0> root.title
=> "Hello, world!"<macro:code>

La lista dei thread conferma che c'è un secondo thread oltre quello
nel quale stiamo lavorando:


<macro:code lang="ruby"> irb(main):010:0> Thread.current
=> #<Thread:0xb7ce27d4 run>
irb(main):011:0> Thread.list
=> [#<Thread:0xb7033240 sleep>, #<Thread:0xb7ce27d4 run>]

E una sbirciata alle costanti definite mostra che TK è stato importato:

1
2
3
4
irb(main):018:0> Module.constants.grep(/tk/i)
=> ["TkValidation", "TkcTag", "TkMenubar", "TkTextTagConfig",
"TkWarning2", "TkRadiobutton", "TkMacResource", "TkTextTag",
...

Se esaminare lo stato interno di un programma senza passare per cicli di ricompilazione e farciture di printf() è comodo e interessante, ancor più interessante di esaminarlo è modificarlo:

1
2
3
4
5
6
7
irb(main):001:0> output.insert('end', 'hello')
=> #<TkText:0xb6ffb9f8 @tags={}, @cmdtbl=[], @path=".w00001">
irb(main):002:0> output.insert('end', ', world!')
=> #<TkText:0xb6ffb9f8 @tags={}, @cmdtbl=[], @path=".w00001">
irb(main):003:0> output.clear
=> #<TkText:0xb6ffb9f8 @tags={}, @cmdtbl=[], @path=".w00001">
irb(main):004:0> 

Colleghiamo il click del bottone a un’azione che stampi qualcosa nell’area di output:

1
2
3
4
5
irb(main):001:0> count = 0
=> 0
irb(main):002:0> button.command = proc { output.clear; output.insert('end', 'Selezionato %d volte' % count); count += 1}
=> #<Proc:0xb675fd6c@(irb):2>
irb(main):003:0> 

Il codice scritto finora nella console interattiva non sarà aggiunto al programma, è “esplorativo”, serve cioè a capire che genere di codice servirà nel programma finale. Abbiamo visto come rispondere all’azione dell’utente e come presentargli un risultato; vediamo come recuperare i dati che serviranno a creare il risultato—nel nostro caso, scaricare un feed ed estrarne i titoli. Con un po’ di prove, potremmo approdare a qualcosa di simile a questo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
irb(main):001:0> require 'net/http'
=> true
irb(main):002:0> require 'uri'
=> false
irb(main):003:0> feed = Net::HTTP.get(URI.parse('http://www.therubymine.com/xml/rss20/feed.xml'))
[...]
irb(main):004:0> require 'rexml/document'
=> true
irb(main):005:0> doc = REXML::Document.new(feed)
=> <UNDEFINED> ... </>
irb(main):006:0> doc.elements.each('//item/title') {|title| puts title.text }
Convention over configuration... ma se voglio configurare?
Nuovo anno, nuovo tool per i deploy (o quasi)
Anche i Minatori Riposano
Liste di Natale con Rails e Ajax

Notiamo che i vari require, indipendentemente da come organizzeremo il resto nel programma finale, saranno sempre necessari. Promuoviamoli immediatamente e inseriamoli in testa al sorgente:

1
2
3
4
require 'net/http'
require 'uri'
require 'rexml/document'
require 'tk'

Il sorgente a questo punto: Versione #3.

Il resto è pensabile come una funzione che associ una URL a dei titoli.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
irb(main):016:0> def url_titles(url)
irb(main):017:1> titles = []
irb(main):018:1> feed = Net::HTTP.get(URI.parse(url))
irb(main):019:1> doc = REXML::Document.new(feed)
irb(main):020:1> doc.elements.each('//item/title') { |title| titles << title.text }
irb(main):021:1> titles
irb(main):022:1> end
=> nil<macro:code>

Testiamola immediatamente:


<macro:code lang="ruby">irb(main):023:0> url_titles('http://www.therubymine.com/xml/rss20/feed.xml')
=> ["Convention over configuration... ma se voglio configurare?", "Nuovo anno, nuovo tool per i deploy (o quasi)", "Anche i Minatori Riposano", "Liste di Natale con Rails e Ajax", "Trasmigrazione Dati con ActiveRecord", "Hello World con InstantRails in 5 minuti", "La Via del Meta", "Watir, il tuo Telecomandone", "26 Ottobre 2006 - Ruby Social Club meetup a Roma", "Gli algoritmi genetici e i rubini"]

Prima di promuoverla al sorgente, mettiamola ancora alla prova e vediamo se funziona quando richiamata dall’interfaccia:

1
2
3
4
5
6
7
irb(main):025:0> button.command = proc do
irb(main):026:1* titles = url_titles('http://www.therubymine.com/xml/rss20/feed.xml')
irb(main):027:1> output.clear
irb(main):028:1> output.insert('end', titles.join("\n"))
irb(main):029:1> end
=> #<Proc:0xb6f4f4dc@(irb):25>
irb(main):030:0>

Un click sul bottone e vedremo i titoli nell’area di output. Qualche colpo di copia e incolla e possiamo promuovere anche url_titles e la gestione del click al sorgente “ufficiale”: Versione #4.

Nota anche come il codice abbia viaggiato tra sorgente e programma in direzione inversa rispetto a quanto siamo abituati: non “Scriviamolo nel sorgente e, se è corretto, funzionerà nel programma” bensì “Scriviamolo nel programma e, se funziona, inseriamolo nel sorgente”.

Riassunto

Abbiamo toccato questi punti:

  • lo stile di sviluppo a cui siamo abituati presuppone una barriera tra programma e programmatore a tempo di esecuzione che in Ruby non esiste;
  • abbiamo spesso bisogno di sapere, piuttosto che immaginare, lo stato interno di un programma, e ci arrangiamo con printf() e derivati più o meno evoluti;
  • con printf() e derivati, la comunicazione è a senso unico da programma a programmatore, e richiedere nuove informazioni implica terminare il programma, modificare il sorgente, e riavviare il programma;
  • con una console interna al programma, si può richiedere qualunque informazione, senza averlo precedentemente pianificato, e mantenendo in esecuzione il programma; la comunicazione è a doppio senso: il programmatore non è limitato a ricevere informazione, può aggiungerne;
  • aggiungere funzionalità nella console interattiva di un programma dà modo di testarle immediatamente, e in un ambiente fedele a quello in cui vivranno; le includiamo nel sorgente già con una certa fiducia.

L’Angolo del Pedante

Alcuni fatti sono stati uccisi o feriti durante la stesura di questo articolo. Ciò è stato fatto per rimpiazzarli con generalizzazioni vicine all’esperienza dei più, e prevedendo una redenzione in queste ultime righe. In particolare:

  • in principio non era il compilatore; tanto il “motore analitico” di Babbage quanto la macchina di Turing erano interpreti;
  • la programmazione interattiva, che può sembrare una novità se si proviene da Java, C e affini, è ordinaria amministrazione in Lisp, Smalltalk, Erlang, e altri;
  • l’esistenza di un compilatore non necessariamente impone uno stile di sviluppo “dall’esterno”; per esempio, in molte implementazioni di Lisp è norma che le definizioni inserite interattivamente dal programmatore vengano compilate al volo e inserite nel programma durante il suo funzionamento;

Sull’autore: bard, alias Massimiliano Mirra, si occupa di consulenza e ricerca nei campi delle tecnologie web, della messaggistica istantanea, e della combinazione tra le due, con enfasi sulle applicazioni collaborative. Ha avviato, e porta avanti assieme alla comunità, i progetti xmpp4moz e SamePlace .

18 comments | Filed Under: Filosofia Linguaggio Metodologia Tutorial | Tags: