mercoledì, marzo 26, 2008

OOP in VimScript

Vim è uno dei (due) editor di testo più potenti che esistano, e come il suo concorrente (Emacs) può essere programmato in (almeno) un linguaggio di scripting apposito: il VimScript (in Emacs si usa l'elisp). Il VimScript è un linguaggio molto potente, articolato, con strutture dati flessibili (simili alle strututre dati di Python), tra le quali i dizioniari. I dizioniari, utilizzati in maniera particolare, consentono la costruzione di funzioni in cui sia possibile accedere al loro contentuo, una sorta di self pointer. Buttiamo lì un'identità di cui discuterò con voi volentieri:
strutture+function_handler+self=object orientation
Vediamo come questa cosa possa essere vera in VimScript: per prima cosa vi esorto a scaricare questo archivio con all'interno del codice VimScript, fra cui quello che ho scritto per l'OOP. In secundis aprite ed osservate il file "OO.vim" presente nella cartella "VimPresentation\Automation\Scripting". In esso:
  • BaseClass: salva in una variabile globale la classe corrente, costruita come dizionario con l'entry speciale "__name" contenente i nomi delle classi da cui l'oggetto eredita (per ora solo la corrente) e l'array (ora vuoto) delle interfacce implementate. Successivamente lo restituisce.
  • Interface: nefinisce una struttura simile a quella di una classe ma senza array delle interfacce.
  • Hinerit: l'ereditatrietà è gestita all'interno del costruttore chiamando questa funzione, qui viene creata l'istanza della superclasse, viene poi aggiunto ai nomi delle classi da cui si eredita il nome della sottoclasse, sarà poi compito del costruttore terminare la propria opera aggingendo nuovi attributi e svolgendo altri compiti di inizializzazione.
  • Virtual: i metodi virtuali non sono altro che metodi che vengono (nella sottoclasse, non nella superclasse come in C++) salvati all'interno della strututra d'istanza sotto nuovo nome tipo "superclasse_metodo" e che quindi vengono sostituiti nella sottoclasse.
  • PureVirtual: un metodo virtuale puro invece è un metodo che non è ancora stato implementato, se ne genera quindi una versione dummy ( che genera solo un'eccezione) e si utilizza la stessa tecnica dei metodi virtuali.
  • Implements: l'implementazione delle interfacce significa due cose:
    1. l'interfaccia (il suo nome) deve figurare tra le interfacce implementate,
    2. tutti i metodi virtuali puri presenti nell'interfaccia devono essere in quelche modo "importati" all'interno della classe corrente (starà poi al programmatore implementarli).
  • Isa: usando i nomi da dizionario si può sapere se una istanza è (da qualche parte nell'albero d'ereditarietà) istanza di una determinata classe.
  • IsImplemented: in modo analogo si può controllare se un istanza implementa una determinata interfaccia.
  • Import: un po' di comodità non guasta! ;)
Detto questo, sperando che abbiate contemporaneamente visionato il (semplicissimo) codice in VimScript che definisce queste funzioni, vediamo come utilizzarle per definire una classe, il classico contatore. Riporto e commento il file "Counter.vim":
let s:_default = 42
All'interno di uno script le variabili nello scope s: sono locali allo script stesso, per una classe questo significa avere a disposizione uno strumento per gli attributi statici.
function! Counter(...)
...
endfunction
Al solito definiamo una classe come funzione con funzioni innestate, la funzione di suo farà da costruttore. In fondo si può trovare il codice:
function! Counter_compare(a,b)
" Contronto actual:
return a:a._actual>a:b._actual
endfunction
che definisce una funzione friend. Il costruttore svolge le seguenti azioni:
if a:0<1
let initial = s:_default
else
let initial = a:1
end
let counter = BaseClass('Counter') | Implements ICounter
call extend(counter,{'_actual': initial})
che rappresentano:
  1. inizializzazione dell'attributo "initial" come variabile (per ora) locale,
  2. creazione dell'istanza con implementazione dell'interfaccia ICounter,
  3. agginta dell'attributo privato "initial".
Vedremo dopo come è stata definita l'interfaccia, ovviamente la sintassi deriva dal fatto che è stato creato un comando apposito per la definizione delle interfacce implementate, che richiama la funzione omonima.
A questo punto vengono aggiunti i metodi (eventualmente dichiarati virtuali se devono sovrascrivere i metodi di una superclasse o di un'interfaccia):
Virtual actual
function! counter.actual() dict
return self._actual
endfunction
E la classe è pronta! ;) Si osservi che i metodi vengono dichiarati con la speciale sintassi:
funciton! struttura.nome(...) dict
...
endfunction
dove la parola chiave "dict" assicura che all'interno del metodo si possa fare riferimento al dizionario "counter" con la parola chiave self. Si noti anche che il nome della funzione inizia con il nome dell'istanza che si sta costruento e che verrà restituita al termine del costruttore, infatti si sta semplicemente dichiarando un funciotn_handler di nome (in questo esempio) "actual" all'interno del dizionario "counter" richiedendo di generare il riferimento locale "self" con la parola chiave "dict".
Nel file "StepCounter.vim" si può osservare che l'ereditarietà è ottenuta semplicemente sostituendo la chiamata a "BaseClass" con la chiamata a "Hinerit" passando anche il nome della superclasse e la lista degli argomenti.
Con la definizione dei comandi si è resa semplicissima la dichiarazione delle interfacce:
fun! ICounter()
let counter = Interface('ICounter')
Abstract actual next prev set
return counter
endfun

Infatti:
  1. Si crea l'interfaccia in una fuzione "costruttore",
  2. si crea "l'istanza" con "Interface",
  3. si creano i metodi virtuali puri col comando "Abstract",
  4. si restituisce l'interfaccia.

OO in Matlab: Single inheritance

Belli guaglioni, ricordate questo post rtelativo alla programmazione OO in Matlab? Bè ogni tanto per sfizio mi diletto a giocherellare con la metaprogrammazione e ho da proporvi un paio di chicche, una (banale) in matlab e una più interessante in VimScript (prossimo post).
Iniziamo pensare alla tecnica proposta per programmare OO in Matlab, abbiamo compreso come ottenere molte features, non abbiamo ottenuto però l'ereditarietà. Per aggiungere l'ereditarietà vogliamo tenere d'occhio i seguenti aspetti:
  • Overriding: vogliamo poter "sovrascrivere" i metodi della superclasse con le nostre nuove versioni senza perdere però la possibilità di accedere esplicitamente alle versioni della superclasse.
  • Accessors: non abbiamo descritto in maniera molto dettagliata come poter realizzare dei metodi accessori al meglio della comodità d'utilizzo, l'idea è quella di ottenere una sorta di property come in C#, per intenderci.
  • Protected access: nell'antico post sulla OOP in Matlab abbiamo descritto in maniera precisa come ottenere accesso pubblico e privato ai metodi, come costruire funzioni private accessorie non legate agli oggetti, come memorizzare gli attributi come privati ma non come pubblici (cosa che non faremo, usando invece i metodi accessori). Resta però da efinire come ottenere un livello protetto di accesso ai metodi o attributi. Si cominci a considerare il fatto che se si ottiene l'accesso protetto ai metodi si può sfruttare quello per avere accesso protetto verso gli attributi semplicemente con degli accessori protetti. Per ora però purtroppo non siamo in grado di creare metodi ad accesso protetto, quindi ci limitiamo ad utilizzare la tecnica delle property.
Dopo tutta sta pizza passiamo al codice: vogliamo creare la classe stepCouter sottoclasse di counter, in essa dovremo sfruttare il valore attuale del contatore per poterlo leggere, modificare e reimpostare; per questo motivo creiamo il seguente metodo accessorio nel contatore:
% The actual value accessor:
function ao = Actual(ai)
% Getting the actual value:
if nargin>0; start=ai; end

% Setting the output:
if nargout>0; ao=start; end
end
Collegheremo questo metodo all'oggetto salvando il function_handler con il nome "actual" all'interno dell'istanza restituita:
% Returning the counter: this is the public methods' set exported.
c = struct('next',@Next,'prev',@Prev, ...
'reset',@Reset,'actual',@Actual);
Fatto questo abbiamo la possibilità, nella sottoclasse, di istanziare il contatore e aggiungervi dei metodi che, sfruttando la property 'actual', possano modificare anche l'istanza della superclasse.
Si noti che la superclasse e la sottoclasse avranno collegati a sé due workspace differenti con all'interno i loro rispettivi attributi privati; questo significa che con questa tecnica comunque ogni classe genera istanza che hanno il proprio workspace separato da tutti gli altri, senza possibilità di interferenze. Ecco il codice:

function c = stepCounter(start,step)
% STEPCOUNTER Generates a new counter that uses a step.
%
% Here the single hineritance is obtained by generating an object of the
% superclass and then joining the method calls, notice that the mantained
% workspaces are distinguished so there are difficulties in the private
% attributes access (there is no protected access, only public or private);
% so an accessor in the class "counter" must be added (in this case
% "actual"), this allows to have the actual attribute to be public.

% =========================================================================
% Creating the object:
% =========================================================================

% Using the superclass' constructor:
c = counter(start);
clear start;

% Adding local methods:
c.step = @Step;
% Saving the old method so that can be explicitly called if required:
c.counter.next = c.next;
c.next = @Next;
% Same for the prev:
c.counter.prev = c.prev;
c.prev = @Prev;

% =========================================================================
% The public methods body.
% =========================================================================
% The next integer function:
function n = Next
% Increment:
n = c.actual(c.actual() + step);
end
% The prev integer function:
function n = Prev
% Decrement:
n = c.actual(c.actual() - step);
end
% Setting and getting the step value:
function so = Step(si)
% Getting the actual value:
if nargin>0; step=si; end

% Setting the output:
if nargout>0; so=step; end
end
% =========================================================================
% Private methods are simple functions and can be defined here.
% =========================================================================
end

% =========================================================================
% Support functions that are not methods (no access to attributes) are
% defined here.
% =========================================================================
Quindi in questa sottoclasse "step" è un attributo locale privato, ed è l'unico presente nel workspace della sottoclase "stepCounter", il parametro "start" del costruttore viene rimosso una volta utilizzato perché dopo la chiamata al costruttore della superclasse non serve più (è stato memorizzato nel workspace della superclasse) e se non eliminato rimarrebbe nel workspace privato della sottoclasse "stepCounter" occupando spazio inutile e potenzialmente dando la possibilità di scrivere codice errato e confuso. Per "step" si è creata una property, i metodi "next" e "prev" sono stati sostituiti e le vecchie versioni sono state salvate nella struttura stessa in una sottostruttura con il nome della superclasse. Ora è possibile scrivere codice come il seguente:
c1 = counter(10);
c2 = stepCounter(10,10);
c1.next()
c2.next() % Uso la versione "overridata":
s=c2.step %ottengo lo step:
c2.step(20); % Imposto lo step:
c2.counter.next(); % Uso la versione originale: