Lisp – oggetti – funzioni generiche – 3

hilbertContinuo, oggi sono qui.

Altre combinazioni di metodi

Oltre al metodo standard di combinazione dei metodi il linguaggio specifica nove altri metodi conosciuti come metodi di combinazione simple built-in. E poi puoi definire i tuoi.. :shock:

Tutti i metodi di combinazione semplici seguono lo stesso modello: invece di invocare il metodo primario più specifico e lasciare che invochi i successivi metodi primari uno dopo l’altro, attraverso call-next-method la combinazione di metodi semplici produce un metodo effettivo che contiene il codice di tutti i metodi primari tutti impacchettati in una chiamata alla funzione o macro o operatore speciale che danno alla combinazione i loro nomi. Le nove combinazioni sono nominate dagli operatori +, and, or, list, append, nconc, min, max e progn. Le combinazioni semplici consentono solo due tipi di metodi, metodi primari che sono combinati come descritti e metodi :around che vengono combinati come i metodi :around standard.

Per esempio una funzione generica che usa la combinazione di metodo + ritorna la somma di tutti i risultati ritornati dai suoi metodi primari. Notare che le combinazioni and e or non eseguono necessariamente tutti i metodi primari per via del comportamento short-circuiting –una funzione generica usante la combinazione and ritornerà nil appena uno dei suoi metodi lo farà e altrimenti ritornerà il valore del suo ultimo metodo. Similmente la combinazione or ritornerà il primo non-nil valore ritornato da uno dei suoi metodi.

Per definire una funzione generica che usi una particolare combinazione di metodi si include :method-combination della form defgeneric. Il valore fornito con questa opzione è il nome della combinazione che si vuole usare. Per esempio per definire la funzione generica priority che ritorna la somma dei valori ritornati dai metodi individuali usando la combinazione di metodi + si può scrivere:

(defgeneric priority (job)
  (:documentation "Return the priority at which the job should be run.")
  (:method-combination +))

Di default queste combinazioni di metodo combinano i metodi primari nell’ordine most-specific-first tuttavia si può invertire l’ordine con la keyword :most-specific-last dopo il nome della combinazione di metodi nella form defgeneric.
Probabilmente se si usa + il risultato non cambia a meno che i metodi abbiano side effects ma per vedere come si fa ecco:

(defgeneric priority (job)
  (:documentation "Return the priority at which the job should be run.")
  (:method-combination + :most-specific-last))

I metodi primari in una funzione generica che usa una di queste combinazioni devono essere qualificati con il nome della combinazione di metodi. Quindi un metodo primario definito per priority sarà:

(defmethod priority + ((job express-job)) 10)

Questo rende ovvio quando si esamina una definizione di metodo che questa è parte di un particolare tipo di funzione generica.

Tutte le combinazioni di metodi simple built-in supportano anche i metodi :around come nella combinazione standard e call-next-method è usato per passare il controllo a metodi :around via via meno specifici fino a giungere ai metodi primari combinati. L’opzione :most-specific-last non influisce sull’ordine dei metodi :around. E, come già detto, le combinazioni built-in non supportano i metodi :before e :after.

Come le combinazioni di metodi standard queste combinazioni non consentono di fare cose che non si possano fare “a mano”. Invece semplificano, consentendo di esprimere cosa si vuole e lasciare che il linguaggio colleghi il tutto per te, rendendo il codice più conciso ed espressivo :wink:

Ciò detto il 99% delle volte la combinazione standard fa esattamente quello che serve. Nel rimanente 1% si cerca per define-method-combination nella Common Lisp Reference preferita.

g16-0

Multimetodi

I metodi che esplicitamente specializzano più di una funzione generica sono chiamati multimetodi. I multimetodi sono dove le funzioni generiche e il passaggio di messaggi divergono. I multimetodi non rientrano nei linguaggi a passaggio di messaggi perché non appartengono a una particolare classe; invece ognuno di loro definisce una parte delle implementazioni di una data funzione generica che viene applicata quando la funzione generica è invocata con argomenti che soddisfano tutti i parametri specializzatori del metodo.

Qui il Peter mette un box che riassumo: i multimetodi sono come il method overloading di C++ e Java, con la differenza che in questi linguaggi i metodi overloadati (sovraccaricati?) sono scelti nella compilazione.

I multimetodi sono perfetti per quelle situazioni dove in un linguaggio message-passing ti trovi con un comportamento che appartiene a una classe sbagliata (parafrasi mia ma credo che il senso sia quello). È il suono che un tamburo fa quando è colpito con una bacchetta una funzione di che tipo di tamburo è o è che tipo di bacchetta usi per colpirlo? Per modellare questa situazione in Common Lisp si definisce semplicemente una funzione generica beat che prende due argomenti:

(defgeneric beat (drum stick)
  (:documentation
   "Produce a sound by hitting the given drum with the given stick."))

quindi si possono definire i vari multimetodi che implementano beat per le combinazioni che servono, per esempio:

(defmethod beat ((drum snare-drum) (stick wooden-drumstick)) ...)
(defmethod beat ((drum snare-drum) (stick brush)) ...)
(defmethod beat ((drum snare-drum) (stick soft-mallet)) ...)
(defmethod beat ((drum tom-tom) (stick wooden-drumstick)) ...)
(defmethod beat ((drum tom-tom) (stick brush)) ...)
(defmethod beat ((drum tom-tom) (stick soft-mallet)) ...)

I multimetodi non aiutano con l’esplosione combinatoriale– se hai cinque tipi di tamburi e sei tipi di bacchette e ogni combinazione produce un suono diverso non c’è modo di evitare trenta differenti metodi per implementare tutte le combinazioni, con o senza multimetodi. Quello che i multimetodi fanno è di evitare di scrivere un mucchio di codice che ti consenta di usare la stessa funzione polimorfa per i diversi casi con un singolo parametro.

I multimetodi evitano inoltre di dover accoppiare strettamente un set di classe con un altro. Nell’esempio tamburi/bacchette niente richiede che l’implementazione delle classi drum di conoscere le varie classi drumstick e viceversa. I multimetodi connettono le classi altrimenti indipendenti per descrivere i loro comportamenti congiunti richiesti dalle collaborazioni tra le classi stesse.

OK, adesso passiamo a come definire le classi. :evil:
Prossimamente :mrgreen:

Lisp – oggetti – funzioni generiche – 2

192Continuo da qui a copiare qui.

Combinazione di metodi

call-next-method non ha senso all’esterno del corpo di un metodo. Dentro a un metodo è la funzione generica che da il meccanismo che costruisce un metodo effettivo ogni volta che la funzione generica è invocata usando tutti i metodi applicabili per questa invocazione. Questa nozione di costruire un metodo effettivo come combinazione dei metodi applicabili è il cuore del concetto di funzione generica e il vantaggio delle funzioni generiche rispetto al sistema del message-passing. Ragion per cui vale la pena osservare più da vicino cosa succede.

Concettualmente il metodo effettivo è costruito in tre fasi:

  • la funzione generica costruisce una lista di metodi applicabili basata sugli argomenti che le sono stati passati;
  • la lista dei metodi applicabili è sortata in base alla specificità dei loro specializzatori dei parametri;
  • infine i metodi sono presi nell’ordine della lista sortata e il loro codice è combinato per produrre il metodo effettivo.

Per trovare i metodi applicabili la funzione generica compara gli argomenti attuali con i corrispondenti specializzatori dei parametri in ognuno dei suoi metodi. Un metodo è applicabile se e solo se tutti i suoi specializzatori sono compatibili con gli argomenti corrispondenti.

Quando uno specializzatore è nel nome di una classe è compatibile se nomina la classe attuale dell’argomento o una sua superclasse. Da tener presente che i parametri senza specializzatori sono implicitamente specializzati dalla classe T e quindi sono compatibili con ogni argomento. Uno specializzatore eql è compatibile sono quando l’argomento è lo stesso oggetto che era stato specificato nello specificatore.

Siccome tutti gli argomenti sono controllati contro i corrispondenti specializzatori tutti quanti determinano se un metodo è applicabile. I metodi che esplicitamente specializzano in più di un parametro sono chiamati multimetodi, prossimamente…

Dopo che i metodi applicabili sono stati trovati il meccanismo della funzione generica deve sortarli (ordinarli) prima di poterli combinare nel metodo effettivo. Per ordinare due metodi la funzione generica compara i loro parametri specializzatori da sinistra a destra (normalmente, si potrebbe anche invertire ma è raro) e il primo specializzatore che è diverso tra i due metodi determina il loro ordine, dando la precedenza al metodo con specializzatore più specifico.

Siccome solo i metodi applicabili sono sortati, si conoscono tutti gli specializzatori di classe che nominano classi i cui argomenti corrispondenti ne sono un’istanza. Nel caso tipico se due specificatori di classe sono diversi uno apparterrà a una subclasse dell’altro. In questo caso lo specializzatore della subclasse è considerato più specifico. Cosa che si può vedere nell’esempio del post precedente dove checking-account è stato considerato più specifico di bank-account.
Nota mia: è la solita cosa che capita con C++, solo detta con molte più parole.

L’ereditarietà multipla complica leggermente la nozione di specificità visto che l’argomento attuale può essere un’istanza di due classi nessuna delle quali è una subclasse dell’altra. Se queste classi sono usate come parametri specializzatori la funzione generica non può ordinarle usando solo la regola che le subclassi sono più specifiche delle loro superclassi. Vedremo poi come la nozione di specificità viene estesa per gestire l’eredità multipla. Per adesso ci basta sapere che c’è un algoritmo deterministicoper ordinare gli specializzatori di classe.

Infine uno specializzatore eql è sempre più specifico di ogni specializzatore di classe e perché solo i metodi applicabili sono considerati se più di un metodo ha uno specializzatore eql per un particolare parametro questi devono avere lo stesso specializzatore eql. La comparazione di tali metodi verrà quindi decisa in base a altri parametri.

Il metodo di combinazione standard

Visto come i metodi applicabili sono trovati e sortati passiamo a esaminare come vengono combinati in un singolo metodo effettivo. Di default le funzioni generiche usano quello che viene chiamato metodo di combinazions standard. Questo combina i metodi nel modo di call-next-method visto nell’esempio del post precedente, per primo il più specifico che passa il controllo al successivo più specifico attraverso call-next-method.

Tuttavia c’è un qualcosa in più di questo. I metodi visti finora sono chiamati metodi primari (primary methods). Come il loro nome dice sono responsabili dell’implementazione primaria della funzione generica. Il metodo di combinazione standard prevede anche tre tipi di metodi ausiliari (auxiliary methods): i metodi :before, :after e :around. La definizione di un metodo ausiliario è scritta con defmethod come un metodo primario ma con un qualificatore di metodo (method qualifier) che definisce il tipo del metodo tra i nomi del metodo e la lista dei parametri. Per esempio un metodo :before in withdraw (sempre esempio precedente) che specializza il parametro account della classe bank-account comincia così:

(defmethod withdraw :before ((account bank-account) amount) ...)

Ogni tipo di metodo ausiliario è combinato nel metodo effettivo in modo diverso. Tutti i metodi :before applicabili –non solo il più specifico– sono eseguiti come parte del metodo effettivo. Come il loro nome suggerire sono eseguiti prima del più specifico metodo primario, nell’ordine prima-il-più-specifico. Quindi i metodi :before possono essere usati per fare le preparazioni richieste per l’esecuzione dei metodi primari. Per esempio si sarebbe potuto  usarne uno in checking-account per implementare la protezione di sforo così:

(defmethod withdraw :before ((account checking-account) amount)
  (let ((overdraft (- amount (balance account))))
    (when (plusp overdraft)
      (withdraw (overdraft-account account) overdraft)
      (incf (balance account) overdraft))))

Questo metodo :before ha tre vantaggi rispetto a un metodo primario:

  • rende immediatamente ovvio come il metodo cambia il comportamento complessivo della funzione withdraw –non interferisce con il comportamento principale e non cambia il valore ritornato;
  • un metodo primario specializzato su una classe è più specifico che checking-account non interferirà con questo metodo :before facilitando per un autore di una subclasse di checking-account di estendere il comportamento di withdraw mantenendo il vecchio comportamento;
  • infine, siccome un metodo :before non ha da chiamare call-next-method per passare il controllo ai metodi rimanenti, è impossibile dimenticarselo.

Gli altri metodi ausiliari si inseriscono nel metodo effettivo nel modo che il loro nome suggerisce. Tutti i metodi :after vengono eseguiti dopo i metodi primari in ultimo-il-più-specifico, cioè l’inverso dei :before. Quindi la combinazione dei metodi :before e :after crea un involucro (wrapper) attorno alle funzionalità provviste dai metodi primari.

Infine i metodi :around sono assemblati come i metodi primari ma eseguiti attorno a tutti gli altri metodi. Cioè il codice del più specifico metodo :around è eseguito prima di ogni altra cosa. Dentro il corpo di un metodo :around call-next-method porterà al codice del successivo metodo :around più specifico o, se è l’ultimo (il meno specifico) dei metodi :around al complesso dei metodi :before, primari e :after. Quasi tutti i metodi :around conterranno una chiamata a call-next-method perché un metodo :around che non lo faccia dirotterà completamente l’implementazione della funzione generica da tutti i metodi eccetto quelli :around più specifici.

Occasionalmente questo dirottamento è voluto ma tipicamente i metodi :around sono usati per installare qualche contesto dinamico in cui il resto dei metodi sarà eseguito, per esempio per collegare una variabile dinamica o un gestore di errore. Il solo caso appropriato di non usare call-next-method in un metodo :around è quando si ritorna da un risultato memorizzato (chached) da una precedente chiamata a call-next-method. In ogni caso il metodo :around che non chiama call-next-method e responsabile di implementare la semantica della funzione generica per tutte le classi di argomenti ai quali il metodo può essere applicabile, incluse le subclassi future.

I metodi ausiliari sono solo un modo conveniente per esprimere modelli comuni più concisamente e concretamente. Non permettono di fare niente che non si possa fare con metodi primari. Peraltro il loro maggior pregio è che provvedono uno schema uniforme per estendere le funzioni generiche. Spesso una libreria definisce una funzione generica e provvede un metodo primario di default, permettendo agli utenti della libreria di personalizzare i suoi comportamenti definendone metodi ausiliari approssimati.

Continua appena mi riprendo :evil: :mrgreen:

Lisp – oggetti – funzioni generiche – 1

B9AIl Lisp è nato prima della programmazione orientata agli oggetti (OOP). E l’OOP non è decollata che molto dopo, con Smalltalk e soprattutto C++, ma anche Turbo Pascal poi diventato Delphi.
Sorprendentemente, il Lisp, il Common Lisp nato appena prima dell’esplosione degli oggetti, si è dimostrato ponto per l’inserimento delle nuove idee. Diversi esperimenti emersero sotto il nome di CLOS (Common Lisp Object System) poi inglobati nello standard ANSI per cui non ha più senso di parlare di CLOS come di un’entità separata. Tutto questo lo dice (molto meglio) Peter Seibel che sto copiando, qui.

In realtà lui usa il termine “Object Reorientation” che nel titolo non ho messo, sapete com’è –carenze mie :oops:
Inoltre certe cose ormai si sanno (il libro ha appena compiuto 10 anni che in questo campo è tanto (ma è attualissimo, consigliatissimo)), le riporto, repetita juventus :lol:

Due cose: non è che si dice proprio tutto sugli oggetti, caso mai saranno da approfondire poi e gli oggetti nel Lisp sono trattati diversamente da come fanno gli altri linguaggi. Qui, secondo me, il discorso potrebbe essere lungo, ci sono differenze anche molto marcate, p.es. l’eredità multipla.
Quindi qui seguiremo la via del Lisp, diversa (il Lisp è diverso), migliore (il Lisp è meglio) :grin:

Funzioni generiche e classi

L’idea fondamentale della OOP è che il modo di organizzare un programma è di definire dei tipi di dati e associare operazioni ad essi. In particolare si vuol essere in grado di invocare un’operazione e avere il comportamento determinato dal tipo di oggetto o oggetti per cui l’operazione è stata invocata. L’esempio classico è quello del disegno di forme geometriche. Differenti implementazioni per disegnare le figure sono fornite così che si possano disegnare triangoli, cerchi, quadrati e il risultato a una chiamata a draw disegnerà la forma dipendente dal tipo di oggetto cui si riferisce. le differenti implementazioni di draw sono definite separatamente e una nuova versione può essere definita per disegnare altre forme senza dover cambiare il codice per le vecchie implementazioni. OK, polimorfismo.

Il sistema degli oggetti nel Common Lisp è, come molti altri linguaggi, basato su classi; tutti gli oggetti sono istanze di una particolare classe. La classe di un oggetto determina la sua rappresentazione, le classi built-in come number e string hanno rappresentazioni opache accessibili solo attraverso le funzioni standard per manipolare questi tipi mentre le istanze di classi definite dall’utente, come vedremo prossimamente, consistono di slots.

Le classi sono organizzate in una gerarchia, una tassonomia per tutti gli oggetti. Una classe può essere definita come una subclass di altre classi, chiamate le sue superclassi. Una classe eredita parte delle sue definizioni dalle superclassi e le istanze di una classe sono altresì considerate istanze delle superclassi. In Common Lisp la gerarchia delle classi ha una singola radice, la classe T che è una diretta o indiretta superclasse di ogni classe. Quindi ogni dato in Common Lisp è un’istanza di T. La classe T e il simbolo t per true non hanno nessuna relazione particolare, hanno solo lo stesso nome. t come valore è un istanza diretta alla classe symbol e solo indirettamente alla classe T. Come si vede Common Lisp supporta l’eredità multipla (multiple inheritance).

Common Lisp, come quasi tutti i linguaggi a oggetti condividono lo schema stabilito da Simula di avere un comportamento associato con classi attraverso metodi o funzioni membro che appartengono a una particolare classe. In questi linguaggi un metodo è invocato per un particolare oggetto e la classe dell’oggetto determina che codice eseguire. In modello è chiamato, seguendo Smalltalk, message assing. Concettualmente l’invocazione del metodo parte dall’invio di un messaggio contenente il nome del metodo da eseguire e ogni argomento all’oggetto il cui metodo è invocato. L’oggetto usa allora la sua classe per trovare il metodo associato con il nome del messaggio e lo esegue. Siccome ogni classe può avere il suo proprio metodo per un dato nome lo stesso messaggio inviato a differenti oggetti può invocare differenti metodi.

I primi Lisp object systems funzionavano in questo modo, attraverso una funzione speciale, send che può essere usata per inviare un messaggio a un particolare oggetto. Tuttavia non era completamente soddisfacente, era un modo diverso dalle normali chiamate a funzione.

Sintatticamente le invocazioni a metodo erano scritte così:

(send object 'foo)

invece che in quest’altro modo:

(foo object)

Più significativamente, poiché i metodi non erano funzioni, non potevano essere passati come argomenti a funzioni higher-order, come mapcar; se si voleva chiamare un metodo per tutti gli elementi di una lista con mapcar si doveva scrivere:

(mapcar #'(lambda (object) (send object 'foo)) objects)

invece di:

(mapcar #'foo objects)

Alla fine i lispers che lavoravano sul Lisp object systems unificarono i metodi con le funzioni creando un nuovo tipo di funzione chiamata generic function, funzione generica. Oltre a risolvere il problema appena descritto le funzioni generiche aprirono nuove possibilità per il sistema degli oggetti, incluse parecchie caratteristiche che non avrebbero senso con il meccanismo del message-passing.

Le funzioni generiche sono il cuore del sistema a oggetti di Common Lisp e l’argomento del resto di questo post e del prossimo. Intanto partiamo con le funzioni generiche poi seguiranno le classi.

Funzioni generiche e metodi

Una funzione generica definisce un’operazione astratta, specificando il suo nome una lista di parametri ma nessuna implementazione. Ecco l’esempio per come si può definire una funzione generica, draw, che sarà usata per disegnare diverse forme sullo schermo:

(defgeneric draw (shape)
  (:documentation "Draw the given shape on the screen."))

Per intanto vediamo che la funzione non contiene codice.
Una funzione è generica nel senso che, almeno in teoria, può accettare qualsiasi oggetto come argomenti. Tuttavia da sola una funzione generica non fa nulla, se la si chiama con non importa che argomenti segnala un errore.
L’implementazione reale di una funzione generica è fornita dai metodi. Ogni metodo fornisce in implementazione della funzione generica per una particolare classe di argomenti. Questa è forse la differenza principale tra il sistema basato su funzioni generiche e quello a passaggio di messaggi.

I metodi indicano che tipo di argomenti possono gestire specializzando i parametri richiesti definiti dalla funzione generica. Per esempio con la funzione generica draw si può definire un metodo che specializza la forma per gli oggetti che sono istanze della classe circle mentre un altro metodo specializza forme per oggetti che sono istanze alla classe triangle. Saranno di questa forma:

(defmethod draw ((shape circle))
  ...)

(defmethod draw ((shape triangle))
  ...)

Quando una funzione generica è invocata compara gli argomenti che le sono passati con gli specializzatori di ognuno dei suoi metodi per trovare quelli applicabili, quelli che sono compatibili con i suoi argomenti. Se si invoca draw con un’istanza a circle il metodo che specializza la forma nella classe circle è applicabile, mentre se gli passi un triangle la classe triangle sarà quella applicabile. In casi semplici solo un metodo sarà applicabile e gestirà l’invocazione. In casi più complessi ci saranno molti metodi applicabili e saranno combinati come vedremo in un singolo metodo che gestirà l’invocazione.

Si può specializzare un parametro in due modi. Normalmente si specifica una classe per cui l’argomento sarà un’istanza. Poiché le istanze di una classe sono considerate istanze delle sue superclassi un metodo con un parametro specializzato per una particolare classe può essere applicabile ovunque il corrispondente argomento è un’istanza diretta della classe specializzante o di ogni sua superclasse. L’altro modo di specializzatore è il cosidetto eql specializzatore che specifica un particolare oggetto per il quale il metodo è applicabile.

Quando una funzione generica ha solo metodi specializzati su un singolo parametro e tutti gli specializzatori sono classi specializzanti il risultato di invocare una funzione generica è molto simile al risultato di invocare un metodo in un sistema message-passing, la combinazione del nome dell’operazione e la classe dell’oggetto con cui è invocato determina il metodo da eseguire.

Tuttavia invertendo l’ordine della ricerca si aprono possibilità non disponibili nel sistema message-passing. Le funzioni generiche che supportano metodi che specializzano con parametri multipli forniscono uno schema che rende l’ereditarietà multipla molto più agevole e permette di usare costrutti dichiarativi per controllare come i metodi sono combinati in un metodo effettivo supportante diversi usi comuni di situazioni simili senza troppo codice generico. Peter ci parlerà di questo tra breve, dopo uno sguardo ai fondamenti di due macros usate per definire le funzioni generiche: defgeneric e defmethod.

defgneneric

Per vedere come la macro funziona ecco un’applicazione bancaria giocattolo, non per far vedere come si scrivono le applicazioni gestionali ma per illustrare alcune caratteristiche del linguaggio.

Siccome non abbiamo ancora visto come definire una classe supponiamo che una classe esista già, la classe bank-account che ha due subclassi, checking-account e saving-account. La gerarchia è:

account-hierarchy

La prima funzione generica sarà withdraw che diminuisce il saldo di uno specifico prelievo. Se il saldo è meno del prelievo verrà segnalato un errore e il saldo sarà lasciato invariato. Si può partire definendo la funzione generica con defgeneric.

La base di defgeneric è simile a defun eccetto che manca il corpo. La lista dei parametri specifica i parametri che devono essere accettati per tutti i metodi che saranno definiti sulla funzione generica. Al posto del corpo una defgeneric può contenere diverse opzioni. Un’opzione che si deve sempre includere è :documentation che si usa per fornire una stringa descrivente lo scopo della funzione generica. Siccome una funzione generica è puramente astratta è importante che sia chiaro sia all’utente che all’implementatore cosa deve fare la funzione. Quindi si può definire withdraw così:

(defgeneric withdraw (account amount)
  (:documentation "Withdraw the specified amount from the account.
Signal an error if the current balance is less than amount."))

defmethod

Adesso possiamo definire i metodi che implementano withdraw.
La lista dei parametri del metodo dev’essere congruente con la sua funzione generica. In questo caso vuol dire che devono esserci esattamente due parametri richiesti. Più genericamente i metodi devono avere lo stesso numero di parametri richiesti e opzionali e devono essere capaci di accettare ogni argomento corrispondente a ogni parametro &rest o &key passato alla funzione generica.

Dal momento che i prelievi sono gli stessi per tutti gli accounts si può definire un metodo che specializza il parametro account nella classe bank-account. Si può assumere che la funzione balance ritorni il valore corrente di balance dell’account e che possa essere usato da setf (e decf) per settare balance. La funzione error è una funzione standard usata per segnalare un errore, sarà discussa in seguito. Usando queste due funzioni si può definire withdraw così:

defmethod withdraw ((account bank-account) amount)
  (when (< (balance account) amount)
    (error "Account overdrawn."))
  (decf (balance account) amount))

Come si vede defmethod è ancora più simile a defun di defgeneric. La sola differenza è che i parametri richiesti possono essere specializzati sostituendo il nome de parametro con una lista di due elementi, il primo è il nome del parametro e il secondo lo specializzatore, può essere sia il nome di una classe o un eql specializer come vedremo. Il nome del parametro può essere qualunque, non è necessario che sia uguale al nome usato nella funzione generica benché di solito lo sia.

Questo metodo si applicherà ovunque il primo argomento a withdraw sia un’istanza a bank-account. Il secondo parametro, amount, è implicitamente specializzato a T e siccome ogni oggetto è un’istanza di T, non altera l’applicabilità del metodo.

Adesso supponiamo che tutti i conti checking-account abbiamo una protezione contro lo sforo. Cioè, ogni account è collegato a un altro account che è usato quando l’account  non può coprire il ritiro. Si può supporre che la funzione overdraft-account prenda un oggetto checking-account e ritorni un bank-account rappresentante il conto collegato.

Quindi prelevare da un oggetto conto richiede alcuni passi in più rispetto a uno standard oggetto bank-account. Si deve dapprima verificare se il prelievo è maggiore del saldo e, se sì, trasferire la differenza dal conto collegato. Quindi si può procedere con l’oggetto bank-account standard.

Quindi cosa vogliamo è definire un metodo per withdraw che specializza su checking-account per gestire  il trasferimento e poi lasciare che sia il metodo specializzato su bank-account a prendere il controllo. Tale metodo risulta:

(defmethod withdraw ((account checking-account) amount)
  (let ((overdraft (- amount (balance account))))
    (when (plusp overdraft)
      (withdraw (overdraft-account account) overdraft)
      (incf (balance account) overdraft)))
  (call-next-method))

La funzione call-next-method fa parte del meccanismo della funzione generica usata per combinare i metodi applicabili. Indica che controllo dev’essere passato da questo metodo al metodo specializzato bank-account. È un po’ il meccanismo di specificare esplicitamente la classe in Python o C++. Quando, come in questo caso, è chiamato senza argomenti call-next-method è invocato con qualunque argomento passato originariamente alla funzione generica. Può essere chiamato anche con argomenti, che saranno quindi passati al prossimo metodo.

Non è richiesto di invocare call-next-method per ogni metodo. Tuttavia se non lo si fa il nuovo metodo è responsabile per implementare il comportamento desiderato della funzione generica. Per esempio se si ha una subclasse di bank-account, proxy-account che non tiene traccia dei suoi saldi ma delega i prelievi a un altro account si può scrivere un metodo come questo (assumendo proxied-account che ritorna il saldo proxied):

(defmethod withdraw ((proxy proxy-account) amount)
  (withdraw (proxied-account proxy) amount))

Infine defmethod consente di creare metodi specializzati per un particolare oggetto con uno specializzatore eql. Per esempio supponiamo che l’app banking sia destinata a una banca corrotta. Supponiamo che la variabile *account-of-bank-president* contenga un riferimento a un particolare conto appartenente al presidente. Inoltre che la variabile *bank* rappresenti la banca nel suo insieme e che la funzione embezzle rubi denaro alla banca. Il presidente della banca può chiedere di aggiustare withdraw per gestire il suo account speciale:

(defmethod withdraw ((account (eql *account-of-bank-president*)) amount)
  (let ((overdraft (- amount (balance account))))
    (when (plusp overdraft)
      (incf (balance account) (embezzle *bank* overdraft)))
  (call-next-method)))

Da notare che l’eql specializer è valutato una volta sola, quando il defmethod è valutato. Questo metodo sarà specializzato per il valore *account-of-bank-president* quando il metodo verrà definito. Cambiando la variabile successivamente non cambierà il metodo.

Lisp – una libreria per i pathnames – 2

mit-nbContinuo seguendo Peter qui.

Elenco di una directory

Si può elencare il contenuto di una directory (listare si diceva una volta quando non c’erano le finestre) creando una funzione list-directory, un wrapper (ecco un’ltra parola che non so tradurre, un vestito) a directory. directory prende un pathname speciale, chiamato wild pathname, che ha uno o più componenti contenenti lo speciale valore :wild e ritorna una lista di pathnames rappresentanti i files che soddisfano il wild pathname. Insomma le opzioni di quei tempi di cui si diceva prima.

La funzione directory ha due problemi che list-directory risolverà (addresserà non me lo passate vero?). Il primo e più importante è che il suo comportamento varia da un’implementazione all’altra, l’altro è che è piuttosto difficile da usare, con parecchie sottigliezze e idrosincrasie. Scrivendo list-directory le affrontiamo questa volta e mai più :grin:

Una delle sottigliezze è quella della doppia rappresentazione del pathname di una directory, nelle forme directory e file, raccontate precedentemente, qui.

Per ottenere che directory ritorni la lista di /home/juhan/lab/lisp/L15-2/ si deve passare un pathname wild di che componenti nella directory vuoi elencare e che tipo di componenti sono. quindi si deve scrivere qualcosa come:

l15-2

Se si passa il pathname in file form /home/juhan/lab/lisp/L15-2 si ottiene invece

l15-3

cioè vengono listati i file in /home/juhan/lab/lisp/ e L15-2 viene rimpiazzato da :wild.

Per evitare di preoccuparci di di convertire le due forme di rappresentazione si può definire list-directory ad accettare non-wild pathnames in entrambe le forme che convertirà in quella appropriata.

Per agevolare questo definiamo alcune funzioni di supporto. La prima, component-present-p, testa se il pathaname dato è presente, significante né nil né il valore speciale :unspecific che ritorna qualche implementazione al posto di nil. Un’altra funzione directory-pathname-p testa se il pathname è già nella directory form e una terza funzione pathname-as-directory converte ogni pathname alla directory form:

l15-4

Adesso sembrerebbe che si possa generare un wild pathname da passare a directory chiamando make-pathname con un nome in directory form ritornato da pathname-as-directory; sfortunatamente non è così semplice, c’è un inghippo nell’implementazione di CLISP che non ritorna files con estensione a meno che il componente type sia nil invece di :wild. Così definiamo una funzione directory-wildcard che prende un pathname sia nella form directory che file e ritorna la wildcard appropriata per l’implementazione usando il real-tme conditionalization per creare il pathname con il componente type :wild per tutti tranne che per CLISP dove lo setta a nil.

l15-5

Visto com’è facile operare con il read-time--quella-roba-lì grin:
Adesso si può curare una prima crepa della funzione list-directory:

(defun list-directory (dirname)
  (when (wild-pathname-p dirname)
    (error "Can only list concrete directory names."))
  (directory (directory-wildcard dirname)))

Ma –indovina– funziona con SBCL, CMUCL e LispWorks ma non con altre implementazioni; c’è ancora da fare. Alcune implementazioni non ritornano le sotto-directories: OpenMCL richiede che gli si passi una keyword specifica :directories; CLISP richiede che … etc.

Una volta che tutte le implementazioni ritornino le directories si scopre poi che differiscono per come li ritornano, se in directory o file form. Vogliamo invece che le ritornino in directory form sempre. Salto qualche passaggio, il concetto è chiaro e poi emerge tutto dal codice:

(defun list-directory (dirname)
  (when (wild-pathname-p dirname)
    (error "Can only list concrete directory names."))
  (let ((wildcard (directory-wildcard dirname)))

    #+(or sbcl cmu lispworks)
    (directory wildcard)

    #+openmcl
    (directory wildcard :directories t)

    #+allegro
    (directory wildcard :directories-are-files nil)

    #+clisp
    (nconc
     (directory wildcard)
     (directory (clisp-subdirectories-wildcard wildcard)))

    #-(or sbcl cmu lispworks openmcl allegro clisp)
    (error "list-directory not implemented")))

La funzione clisp-subdirectories-wildcard non è specifica di CLISP ma siccome non è richiesta da nessun’altra implementazione si può considerarne la definizione con un read-time conditional. In ogni caso siccome l’espressione seguente #+ è un’intera defun la funzione completa sarà o meno inclusa a seconda che clisp sia presente o meno in *features*

#+clisp
(defun clisp-subdirectories-wildcard (wildcard)
  (make-pathname
   :directory (append (pathname-directory wildcard) (list :wild))
   :name nil
   :type nil
   :defaults wildcard))

Verificare l’esistenza di un file

Per rimpiazzare probe-file si può definire la funzione file-exists-p. Deve accettare un pathname e ritornarne un equivalente se il file esiste e nil se no. Deve essere capace di accettare il nome di una directory nelle due forme usuali ma deve ritornare sempre il pathame nella directory form, se questa esiste. Questo consentirà di usare file-exists-p con directory-pathname-p per testare se un nome è l’uno o l’altra.

In teoria file-exists-p è molto simile alla funzione standard probe-file; spesso è vero ma ci sono le solite implementazioni che stonano. Al solito: c’è tutto nel codice o leggete l’ottimo Peter :wink:

(defun file-exists-p (pathname)
  #+(or sbcl lispworks openmcl)
  (probe-file pathname)

  #+(or allegro cmu)
  (or (probe-file (pathname-as-directory pathname))
      (probe-file pathname))

  #+clisp
  (or (ignore-errors
        (probe-file (pathname-as-file pathname)))
      (ignore-errors
        (let ((directory-form (pathname-as-directory pathname)))
          (when (ext:probe-directory directory-form)
            directory-form))))

  #-(or sbcl cmu lispworks openmcl allegro clisp)
  (error "file-exists-p not implemented"))

l15-6

Percorrere un albero di directories

Infine, per completare la libreria, la funzione walk-directory. Diversamente dalle funzioni precedenti questa non richiede aggiustamenti per differenze tra le varie implementazioni. Tuttavia è utile e la useremo spesso prossimamente. Prende il nome di una directory e una funzione e chiama questa funzione con i pathnames di tutti i files sotto questa directory, ricorsivamente. Avrà anche due keyword arguments: :directories e :test. Quando :directories è vero chiamerà la funzione sia per le directories che per i files. L’argomento :test se presente specifica un’altra funzione da chiamare prima della funzione principale che verrà chiamata con il valore risultante da questa.

(defun walk-directory (dirname fn &key directories (test (constantly t)))
  (labels
      ((walk (name)
         (cond
           ((directory-pathname-p name)
            (when (and directories (funcall test name))
              (funcall fn name))
            (dolist (x (list-directory name)) (walk x)))
           ((funcall test name) (funcall fn name)))))
    (walk (pathname-as-directory dirname))))

Ho provato a costruire un esempio semplice, non ci sono riuscito. La funzione è troppo specialistica, imho. E poi il post è già lungo :wink:

Lisp – una libreria per i pathnames – 1

b1lPortabile, dice Peter, ho tolto l’aggettivo nel titolo ma sì, certo.
L’astrazione fornita da Common Lisp per i pathnames è vecchia, troppo generica e allora eccone una nuova, non è difficile farla, pronti? via!

L’API

Le operazioni base della libreria saranno di determinare la lista dei files in una directory e determinare se un file o una directory esiste. Si passerà poi a percorrere ricorsivamente una directory, chiamando una funzione per ogni pathname nell’albero.

In teoria queste funzioni già ci sono, sono le funzioni standard directory e probe-file. Ma noi faremo di meglio :wink:

*features* e read-time conditionalization

Prima di passare all’implementazione di questa API dobbiamo vedere il meccanismo per scrivere codice implementation-specific.

Nota: il solito problema mio con le traduzioni dei termini tecnici. Siccome questi post hanno pochissime visite non mi preoccupo più di tanto. Lo scopo principale è che pubblicando quanto leggo sono costretto a chiarirmi le cose. Poi c’è sempre la versione originale. E devo proprio compralo il libro di carta :grin:

C’è il solito problema che qualunque codice portabile avrà bisogno di alcune parti specifiche per l’implementazione corrente. Per permettere ciò il Common Lisp fornisce un meccanismo chiamato read-time conditionalization che consente di includere codice condizionalmente.

Il meccanismo consiste di una variabile *features* e due aggiunte extra alla sintassi del Lisp reader. *features* è una lista di simboli ognuno dei quali rappresenta una caratteristica che è presente nell’implementazione o nella sottostante piattaforma.

l15-0

Questi simboli sono usati in feature expressions che ritornano vero o falso a seconda della loro presenza o meno nella lista. L’espressione più semplice è un singolo simbolo; l’espressione è vera se il simbolo c’è, falsa altrimenti. Altre espressioni sono costruite con gli operatori not, and e or. Per esempio se si vuol condizionare qualche codice alla presenza di foo e bar si scrive la feature expression (and foo bar).

La lista iniziale di *features* è dipendente dall’implementazione come pure che funzionalità sono presenti. Tuttavia ogni implementazione include almeno un simbolo che identifica l’implementazione. Per esempio Allegro Common Lisp include il simbolo :allegro, CLISP include :clisp, SLBC (il mio) e CMUCL includono :cmu. Verifico:

l15-1

Per evitare dipendenze nei packages (non ancora visti) che possono essere presenti o meno i simboli in *features* sono usualmente keywords e il reader collega *package* alla keyword package quando legge le espressioni di feature. Quindi un nome senza qualificazione di package sarà letto come simbolo. Così si può scrivere una funzione che si comporta in modo leggermente diverso per ognuna delle implementazioni menzionate, così:

(defun foo ()
  #+allegro (do-one-thing)
  #+sbcl (do-another-thing)
  #+clisp (something-else)
  #+cmu (yet-another-version)
  #-(or allegro sbcl clisp cmu) (error "Not implemented"))

In Allegro il codice sarà letto come se fosse stato scritto così:

(defun foo ()
  (do-one-thing))

mentre per SBCL:

(defun foo ()
  (do-another-thing))

Per un’implementazione non specificatamente menzionata risulterà:

(defun foo ()
  (error "Not implemented"))

Poiché la conditionalization (condizionalizzazione) avviene nel reader, il compilatore non vede le espressioni che sono state saltate. Questo vuol dire che a runtime non c’è differenza ad avere diverse implementazioni. E anche, quando il reader salta le espressioni condizionalizzate non si preoccupa dei simboli dipendenti dalle altre implementazioni (circa, pasticcio mio qui).

Qui Peter mette un box relativo al suo package (com.gigamonkeys.pathnames). Lo salto, non sono ancora arrivato ai packages. Ci tornerò se sorgeranno problemi :mrgreen:

Pausa :grin:

Save the Internet

Logo
La Net Neutrality è continuamente minacciata. Dopo la battaglia vinta negli USA c’è l’Europa.
Si può fare qualcosa? È importante secondo me, ecco, qui.

Posso contare su di te, vero?
Grassie nèh! :grin:

Lisp – files e file I/O – 3

l18Oggi finisco il capitolo, continuando da qui.

Due rappresentazioni dei nomi delle directories

C’è un aspetto particolare nel trattare i nomi delle directories. Questi sono considerati dai sistemi operativi correnti come solo un altro tipo di files. Ci sono pertanto due tipi di rappresentazione.

Una, la file form, tratta la directory come ogni altro file e mette nell’ultimo elemento della namestring name e type. L’altra, la directory form, mette tutti gli elementi ella componente directory lasciando name e type nil. Se /foo/bar/ è una directory entrambi i pathname seguenti la identificano:

f14-15

La prima è la file form, la seconda la directory form.

Quando si crea un pathname con make-pathname si può controllare che forma si ottiene ma bisogna fare attenzione quando si trattano le namestring. Tutte le implementazioni correnti creano i pathname nella file form a meno che la namestring termini con il path separator (per me lo slash). Ma non si può fare affidamento sulle namestring fornite dall’utente; di solito si omette lo slash finale…
In questo caso useremo una funzione che Peter creerà nel prossimo capitolo: pathname-as-directory che converte un pathname nella directory form. Con questa funzione funzionerà quest’istruzione:

(make-pathname :name "foo" :type "txt" 
               :defaults (pathname-as-directory 
               "/home/juhan/lab/lisp/L14-3")) 

Interagire con il file system

Le operazioni tipiche sono di apertura, lettura, scrittura di file ma capita di dover verificare se un file esiste, elencare il contenuto di una directory, cancellare e rinominare files, creare una directory, determinare generalità di un file (proprietario, data di creazione/modifica, lunghezza) e così via. Qui salta fuori un po’ di panico perché lo standard del linguaggio non dice niente, lasciando che sia l’implementatore a sbrogliarsela.

Ciò detto la maggior parte delle funzioni che interagiscono con il file system sono tranquille. Qui Peter descrive quelle standard e identifica quelle con problemi di portabilità per le quali nel prossimo capitolo svilupperà una libreria ad hoc.

Per verificare se un file esiste si usa probe-file che, se il file esiste, ritorna il file truename che risolve i link simbolici (se del caso). Altrimenti ritorna nil. Non tutte le implementazioni supportano questa funzione per una directory. Inoltre non si riesce a distinguere un file da una directory (ma nel prossimo capitolo…).

f14-16

delete-file e rename-file fanno quel che il loro nome suggerisce. delete-file prende un pathname e cancella il file designato, ritornando true se ha funzionato e segnalando un file-error in caso opposto (in futuro vedremo come gestire questi errori).
rename-file prende due pathnames e rinomina il file dal primo nome al secondo:

f14-17

Si possono creare directories con ensure-directories-exists. Prende un pathname e verifica che i componenti della directory esistano e che siano directories, creandoli se necessario. ritorna il pathname che le è stato passato, conveniente per l’uso online.

(with-open-file (out (ensure-directories-exist name) :direction :output)
   ...
   )

Il nome dev’essere in directory form o ensure-directories-exist non funziona o quella terminale non verrà creata.

La funzione file-write-date ritorna il numero di secondi da mezzanotte del 1° gennaio 1900 GMT passati d quando il file è stato scritto:

f14-18

La funzione file-author ritorna il proprietario del file:

f14-19

La lunghezza di un file si trova con la funzione file-length. Per ragioni storiche prende uno stream invece di un pathname. Non è che sia proprio il massimo, cmq:

f14-20

Esiste anche la funzione file-position che chiamata con uno stream ritorna la posizione corrente nel file, cioè il numero di elementi che sono stati letti o scritti. Se chiamata con due argomenti, lo stream e un designatore di posizione setta la posizione dello stream a questo designatore. Questo dev’essere :start, :end o un intero non negativo. Anche qui problemi…

Altri tipi di I/O

Oltre ai file streams Common Lisp supporta altri tipi di streams che possono essere usati per operazioni di lettura, scrittura e visualizzazione. Per esempio si possono leggere dati da o scrivere su una stringa usando string-stream che puoi creare con le funzioni make-string-input-stream e make-string-output-stream.

make-string-input-stream prende una stringa e opzionalmente indici di inizio e fine per collegare la porzione di stringa che dev’essere letta e ritorna uno stream di caratteri che si può passare a qualunque funzione character-based come read-char, read-line o read. Esempio:

f14-21

Similmente make-string-output-stream crea uno stream che si può usare con format, print, write-char, write-line e compagnia. Non ha argomenti, qualunque cosa si scriva, viene inserito in una stringa che può essere ottenuta con la funzione get-output-stream-string. Ogni volta che si chiama get-output-stream-string la stringa interna dello stream viene svuotato così da poterlo riutilizzare.

Tuttavia si usano raramente queste funzioni direttamente per via delle macros with-input-from-string e with-output-to-string più comode. with-input-from-string è simile a with-open-file:

f14-22

Similmente with-output-to-string:

f14-23

Gli altri tipi di streams definiti dal linguaggio gestiscono altre cose. Esiste un broadcast-stream per inviare dati a un set di streams di output, con la funzione make-broadcast-stream. Complementariamente c’è il concatenated-stream che è un input stream che prende gli inputs da un set di streams. È costruita con make-concatenated-streams.

Due tipi di streams bidirezionali possono connettere streams in una coppia di modi, two-ways-stream e echo-stream. Credo di poter saltare i dettagli, pronto ormai per la libreria che semplifica le operazioni sui pathnames promessa :grin:

Argomenti passati a funzione – 3

fortrnIl titolo del post è fuorviante, è quello solo perché si collega a questo: Argomenti passati a funzione – 2.
Ed è un post atipico, solo per un paio di precisazioni.
Intanto come dicevo in un commento nel post citato l’amico (ex über-boss) AP mi ha dato le fotocopie del manuale del Fortran 77 di Apollo, DOMAIN FORTRAN User’s Guide Order No. 000530 Revison 04 Software Release 7.0, 1984.

Ricordavo male: non c’era ancora la distinzione tra gli argomenti passati a funzione (o subroutine) modificabili o no. Anzi ci sono in diversi punti le solite raccomandazioni:

apollo

Riletto oggi il manuale è “vecchio”, molto standard 77. Le uniche estensioni, rivoluzionaria a quel tempo sono le variabili di tipo pointer (puntatore) e il supporto della ricorsività. Quest’ultima era vietata da sempre in Fortran e anche da noi perché non efficiente. Invece i puntatori avevano la loro ragion d’essere per le librerie grafiche scritte in Pascal. Per quel che ne so Apollo è l’unico computer il cui sistema operativo sarebbe piaciuto a Niklaus Wirth :grin:

Invece l’istruzione intent con cui si può specificare il tipo di argomenti con i valori in, out e inout compare solo nel Fortran 90. All’epoca personalmente usavo ormai pochissimo il Fortran; e poi ho fatto confusione sulle date. La mia memoria non è più quella di una volta; e com’era allora me lo sono dimenticato :lol:

Ho cercato in rete, scoprendo di aver già pubblicato su questo stesso blog cose affini di cui mi sono completamente dimenticato: Nostalgia – I

Riporto nuovamente l’URL di un sito con tantissimo materiale, anche se non ho trovato quel che cercavo: bitsavers.org.

L’immagine della copertina del Fortran IV del Prime viene da qui.

OK, Mode nostalgia OFF :mrgreen:

Visto nel Web – 178

Ecco, come ogni domenica, una nuova puntata di cosa ho visto nel Web.
18623_836800089728556_6050318712627822734_n
Why Software Craftsmanship Needs To Stop Worrying About The Colour Of The Bike Shed & Focus On The Big Issues
::: Software People Inspiring

prose.io
::: Grab the Blaster

Make a bootable Windows USB from Linux (Ubuntu)
::: OneTransistor

After Anti-Donation Executive Order, Bitcoin Donations For Snowden Jump
::: Slashdot

By combining Haskell + Unicode
::: kaepora

IMG_25724301525549

LOL at all the people using the World Wide Web to complain about #RestartLHC
::: paulcoxon

Does your life have this infinite loop?
::: NowTalkIT

China’s Man-on-the-Side Attack on GitHub
::: NETRESEC

Operating Systems
::: xkcd

cellphones at the red sea

Ubuntu (and Unity) at CERN
::: 3v1n0

Tecnologie contro la paura di parlare in pubblico
::: Oggi Scienza

Basta con la teoria dei Nativi digitali!
::: ulaulaman

10 Years of Git: An Interview with Git Creator Linus Torvalds
::: :::Linux.com

CB25HCqW8AEu02I

È la vostra privacy
::: SIAMO GEEK

Judge Allows Divorce Papers To Be Served Via Facebook
piacerebbe a quelli di Famiglia Cristiana, assay :grin: :grin: :grin:
::: Slashdot

John Oliver to Edward Snowden: ‘No One Knows Who the Fuck You Are’
::: Motherboard

Ultra-fast charging aluminum battery offers safe alternative to conventional batteries
::: Phys.org

Turning the Arduino Uno Into an Apple ][
::: Slashdot

10250257_781783485165081_1067020898_n

How the New York Times is eluding censors in ChinaHow the New York Times is eluding censors in China
::: Quartz

Hackr.io: Share and discover the best programming tutorials and courses online
::: The Ubuntu Incident

Statement from original Snowden bust artists on last night’s hologram tribut
::: moneyries

Juste une exception, qu’ils disaient…
::: CommitStrip

Stack Overflow 2015 Developer Survey Reveals Coder Stats
::: Slashdot

11129927_876760212369511_8237958267124838766_n

Russia might have hacked the White House
::: Engadget

Turchia, i colossi web cedono ai censori – ed è un problema
::: Chiusi nella rete

The Cyberlearning Technologies Transforming Education
::: Slashdot

Parla il papà di ‪Arduino‬: “ecco perché (purtroppo) ci siamo rivolti ai giudici”
::: CheFuturo

U.S. secretly tracked billions of calls for decades
::: USA TODAY

10599509_10152389551001245_7787402551965580557_n

Finding the forgotten women who programmed the world’s first electronic computer
::: PRI

The Woman who Hacked Hollywood
::: Medium

Why Netflix is embracing Python over Java
::: InfoWorld

Facebook resta il social più usato dai ragazzi
::: fabiochiusi

TV5 Monde piraté par un groupe djihadiste
::: lemondefr

988337_10153350888495494_1825012291_n

Remember, kids, JavaScript and Java are TOTALLY different
::: codinghorror

The Key To Interviewing At Google
::: Slashdot

Live long, Code & Prosper!
::: Codemotion

Using Wikipedia: a scholar redraws academic lines by including it in his syllabus
::: The Conversation

List of awesome university courses for learning Computer Science!
::: The Ubuntu Incident

10930882_972543782790701_3474156731437097304_n

24% of teens go online ‘almost constantly.’
::: OCLC

“Why don’t you use C++ instead of Python? It’s so much faster!”
::: jakevdp

Remember, the computer should be doing the work for you
::: climagic

11102605_407771019402225_5546142011008120843_n

Sulla reputazione e il valore, dentro e fuori dalla rete
::: Sergio Gridelli

Twitter rimuove 10 mila profili pro-ISIS in un giorno
::: Chiusi nella rete

Office Online e Dropbox si uniscono nella modifica dei documenti
::: Downloadblog

Linux Voice: an awesome Linux magazine
::: The Ubuntu Incident

CCN_W63UMAInhgH

Un’occhiata a “Periscope”, l’app del momento
::: La fumettista curiosa

Weekly Inspirational #1
::: Davide Aversa

Primo Forum dell’Industria Italiana del Software Libero!
::: Industria Italiana del Software Libero

China’s Great Cannon
::: Citizen Lab

10386741_583835838416253_279012542431164477_n

Whoever created Outlook
::: KyleStack

DIY: Make Your Own Programming Language
l’idea è OK, chissà se continua; e dove arriva.
::: Mattias Appelgren

Quanto ci costa Facebook?
::: Google+

Cervelli in funzione a pieno regime al #CoderDojo di Rosà
::: AViLUG ::: AViLUG

Fearless Concurrency with Rust
::: The Rust Programming Language

IMG_1468846459415581

Devuan will replace Ubuntu?
::: Open source electronic projects

Florida Teen Charged With Felony Hacking For Changing Desktop Wallpaper
::: Slashdot

Losing Your Loops: Fast Numerical Computing with NumPy (PyCon 2015)
Jake e NumPy: stupite!
::: Speaker Deck

apple

After EFF Effort, Infamous “Podcasting Patent” Invalidated
::: Slashdot

Today is the 10th anniversary
::: peterseibel

Locking the Web open: Why we need to rethink the World Wide Web
::: The Next Web
1504015_806215069434683_4223212347440198317_n

Lisp – files e file I/O – 2

l17Continuando  come al solito a copiare

Filenames

Finora abbiamo usato stringhe per identificare i nomi dei files; funziona ma ti lega al sistema operativo (questo era un grosso problema una volta, di meno adesso, imho). Ma il Common Lisp fornisce una soluzione a questo problema di non portabilità: gli oggetti pathname. L’onere di tradurre avanti e indietro tra le stringhe del sistema locale –chiamato namestring– e pathnames è inserito nell’implementazione del Lisp.
Peraltro quest’astrazione introduce le sue complicazioni. Come dicevo… ma OK, avanti così.

Siccome ogni tanto mi piace ravanare negli aspetti storici dell’informatica riporto tal quale un box di Peter, mi piace troppo :grin:

How We Got Here
The historical diversity of file systems in existence during the 70s and 80s can be easy to forget. Kent Pitman, one of the principal technical editors of the Common Lisp standard, described the situation once in comp.lang.lisp (Message-ID: sfwzo74np6w.fsf@world.std.com) thusly:

The dominant file systems at the time the design [of Common Lisp] was done were TOPS-10, TENEX, TOPS-20, VAX VMS, AT&T Unix, MIT Multics, MIT ITS, not to mention a bunch of mainframe [OSs]. Some were uppercase only, some mixed, some were case-sensitive but case- translating (like CL). Some had dirs as files, some not. Some had quote chars for funny file chars, some not. Some had wildcards, some didn’t. Some had :up in relative pathnames, some didn’t. Some had namable root dirs, some didn’t. There were file systems with no directories, file systems with non-hierarchical directories, file systems with no file types, file systems with no versions, file systems with no devices, and so on.

If you look at the pathname abstraction from the point of view of any single file system, it seems baroque. However, if you take even two such similar file systems as Windows and Unix, you can already begin to see differences the pathname system can help abstract away–Windows filenames contain a drive letter, for instance, while Unix filenames don’t. The other advantage of having the pathname abstraction designed to handle the wide variety of file systems that existed in the past is that it’s more likely to be able to handle file systems that may exist in the future. If, say, versioning file systems come back into vogue, Common Lisp will be ready.

E sì, il Pr1me, il VAX, il DOS, …

Come i pathnames rappresemntano i filenames

Un pathname è un oggetto strutturato che rappresenta un filename in sei componenti: host, device, directory, name, type e version. Parecchi di questi sono valori atomici, di solito stringhe. Solo il componente directory è ulteriormente strutturato contenente una lista di nomi di directory (stringhe) precedute dalle keyword :absolute o :relative. Tuttavia non tutti i sei componenti ci sono sempre, di solito non interessa a meno di doverne creare uno ex-novo, non capiterà mai. Invece usualmente si entra in possesso di un pathname lasciando all’implementazione di tradurre una specifica namestring in un oggetto pathname o crearne uno nuovo.

Per esempio per tradurre una namestring in un pathname si usa la funzione pathname. Prende un designatore di pathname e ritorna l’oggetto pathname. Se il designatore è già un pathname ritorna quello. Quando è uno stream ritorna il filename originale. Quando il designatore è una namestring, tuttavia, questa viene analizzata (parsed) secondo la sintassi locale dei filenames.

Nel file system Unix/Linux solo directory, name e type sono tipicamente usate. Con Windows c’è un componente in più –host o device– che contiene la lettera del disco. In queste piattaforme una namestring è analizzata dividendola nei suoi elementi in base a un separatore (slash o backslash). Questi elementi tranne l’ultimo sono posti in una lista, con l’attributo :absolute o :relative e questa lista diventa la componente directory del pathname. L’ultimo elemento è diviso all’ultimo punto, se c’è, e messo nelle componenti name e type del pathname. Ma ci sono anche i files nascosti di Unix/Linux –who cares? :wink:

Si possono esaminare queste componenti con le funzioni pathname-directory, pathname-name e pathame-type:

f14-4

Come molti altri oggetti built-in i pathname hanno una loro sintassi: #p seguita da una stringa tra virgolette. Questo consente di scrivere e rileggere s-expressions contenti oggetti pathname ma non è detto che sia portabile :shock:

f14-5

Per ritradurre un pathname in una namestring si usa (indovina?) namestring che prende un pathname e ritorna la namestring. Ci sono poi directory-namestring e file-namestring che comprende anche il tipo.

f14-6

Costruire nuovi pathnames

Si possono costruire nuovi pathnames con la funzione make-pathname che prende una keyword argument per ogni componente e ritorna un pathname con i componenti forniti e gli altri a nil.

f14-7

Tuttavia se si vuole che il programma sia davvero portabile non conviene crearne di nuovi: non ce la farete mai :evil:
per esempio sul Mac /home si chiama /Users e con Windows chissà:

f14-8

Nope!

Invece di crearne di nuovi puoi costruirne basandoti su un pathname esistente con make-patname con la keyword :defaults. Per esempio l’espressione seguente crea un pathname con l’estensione .html e tutte le altre componenti di input-file:

f14-9

Questa tecnica può essere usata per creare un pathname con la componente directory diversa:

f14-10

Ma questo crea un pathname in cui backup è indipendente da *i-f*. A volte occorre combinare due pathnames, spesso per la componente directory.  per esempio supponiamo di avere un pathname relativo, p.es. #p"foo/bar.html" che vogliamo combinarlo con il pathaname assoluto #p"/www/html/" per ottenere #p"/www/html/foo/bar.html". In questo caso si usa merge-pathnames.

merge-pathnames prende due pathnames e li aggrega riempiendo ogni componente nil nel primo pathname con il corrispondente valore del secondo pathname. Tratta in modo speciale il componente directory: se nel primo pathname è relativa la componente risultante sarà quella del primo relativa a quella del secondo, quindi:

f14-11

Anche per il secondo pathname questa può essere relativa, in tal caso il pathname risultante sarà relativo:

f14-11-1

Per invertire questo processo e ottenere il filename relativo a una particolare root directory si usa enough.namestring:

f14-12

Si possono combinare enough-namestring e merge-pathnames per creare un pathname con lo stesso nome ma una differente root:

f14-13

merge-pathnames è anche usata internamente per le funzioni che accedono ai file dati in modo incompleto; per esempio se si crea un file con solo nome e tipo:

(make-pathname :name "foo" :type "txt") ==> #p"foo.txt"

e si tenta di aprirlo con open i componenti mancanti come la directory saranno riempiti dal Lisp che li ottiene dal valore *default-pathname-defaults*:

f14-14

Di solito è la directory dove il Lisp è stato avviato.

Basta per oggi :mrgreen:

Iscriviti

Ricevi al tuo indirizzo email tutti i nuovi post del sito.

Unisciti agli altri 85 follower