Nel repository subversion di pugs si è materializzata una lista di 99 problemini di programmazione, list ispirata da una in Common Lisp, a sua volta ispirata da una in prolog.

Sicché ieri notte mi è venuta l’idea di provare a risolverne qualcuno, tra cui il semplice problema 28, che si compone di due parti, la prima delle quali è: ordina una lista di liste in base alla lunghezza degli elementi.
In ruby sarebbe:

>> a=[[1,2,3,4,5],[1,2],[1],[1,2,3],[4,3,2,1]]
=> [[1, 2, 3, 4, 5], [1, 2], [1], [1, 2, 3], [4, 3, 2, 1]]
>> a.sort
=> [[1], [1, 2], [1, 2, 3], [1, 2, 3, 4, 5], [4, 3, 2, 1]]

Ma c’è il problema che l’ultimo elemento non è ben posizionato, perché?
Perché Enumerable#sort si appoggia al metodo <=> degli argomenti, che è definito per gli interi, le stringhe, gli array e così via in modo ragionevole. Si tratta di un classico caso di progettazione OO ben fatta. Solo che per gli Array è definito in un modo simile:

class Array
 def <=>(other)
  each_with_index do |elem,i|
   res= elem <=> other[i]
   return res unless res.zero?
  end
 end
end

In pratica “confronta gli elementi uno a uno finché sono uguali, se sono diversi restituisci il risultato”.

Ciò significa che per ottenere un risultato sensato dobbiamo applicare una funzione di comparazione ad hoc, in questo modo

>> a.sort_by {|e| e.size}
=> [[1], [1, 2], [1, 2, 3], [4, 3, 2, 1], [1, 2, 3, 4, 5]]

In perl6 la cosa dovrebbe essere, altrettanto banalmente

pugs> my @a=[1,2,3,4,5],[1,2],[1],[1,2,3],[4,3,2,1]
(#<Array :0x11face4>,
 #<Array :0x17e53e4>,
 #<Array :0x17e53dc>,
 #<Array :0x11fc644>,
 #<Array :0x11fd1ac>)
pugs> @a.sort
((1,), (1, 2), (1, 2, 3), (1, 2, 3, 4, 5), (4, 3, 2, 1))

Ma otteniamo lo stesso errore che avevamo in ruby, sebbene la logica sottiostante sia differente.
Andiamo per gradi: cosa è sort()?
Si tratta di una multi sub (funzione generica in CLOS, multimetodo nel resto del mondo) cioè di una funzione che è definita un sacco di volte per differenti tipi, e il cui dispatch viene poi fatto a runtime.
sort può ricevere in input una funzione di due parametri (classica sort) di un parametro (shwartzian transform, come sort_by in ruby o sort(key=foo) in python) o nessun input, usando di default l’operatore cmp

cmp è l’equivalente generico di <=> che in perl è limitato ai tipi numerici.
Insomma, di fondo la sort funziona esattamente allo stesso modo.

Possiamo quindi usare lo stesso approccio usato in ruby, ordinare usando un comparatore a argomento singolo.
Sappiate che, per ragioni a me ignote, la lunghezza di una lista in perl6 si ottiene con +$lista. Un blocco in linea ottiene l’argomento attraverso $_ quindi sperimentiamo usando la funzione map:

pugs> map {+$_}, @list
(5, 2, 1, 3, 4)

Perfetto, andiamo al sort:

pugs> sort {+$_}, @list
((4, 3, 2, 1), (1, 2, 3), (1,), (1, 2), (1, 2, 3, 4, 5))

Ma che cavolo di ordinamento è?
E qui viene la parte divertente: parlando con larry wall su #perl6 scopro che la sort di un solo argomento non è che funzioni proprio benissimo in questo momento storico. Ok, non mi do per vinto, proviamo con quella di due argomenti.

Ora, come li prendo due argomenti un una sub in linea?
In Perl6 esistono due modi per definire delle funzioni/closure in linea, la sintassi che abbiamo appena usato, con i blocchi in stile perl5, e quella con la freccetta:

pugs> my $fun= -> @l { +@l }
->{Syn \"block\" {App &prefix:+ (: Var \"@l\")}}
pugs> $fun(@list)
5
pugs> $fun(@list[0])
5
pugs> $fun(@list[1])

Proviamo il sort:

pugs> my $comparator= -> $a,$b { +$a < => +$b }
->{Syn “block” {App &infix:>=> (:
                                App &prefix:+ (: Var “$a”), App &prefix:+ (: Var
 “$b”))}}
pugs> sort $comparator, @list
((1,), (1, 2), (1, 2, 3), (4, 3, 2, 1), (1, 2, 3, 4, 5))

Ops, scusate in linea:

pugs> sort(-> $a,$b { +$a <=> +$b }, @list)
((1,), (1, 2), (1, 2, 3), (4, 3, 2, 1), (1, 2, 3, 4, 5))

Perfetto funziona. Solo che è complicatissimo da scrivere ed illegibile.

E questo ci da l’occasione di introdurre una cosa che a me piace tantissimo in perl6, le variabili segnaposto, che ci permettono di riscrivere il tutto come:

pugs> sort {$^a <=> $^b}, @list
((1,), (1, 2), (1, 2, 3), (4, 3, 2, 1), (1, 2, 3, 4, 5))

Ora, il codice dovrebbe essere ovvio, l’unica cosa strana è la presenza di quelle due variabili con nomi assurdi, $^a e $^b, che sono appunto le variabili segnaposto.
In pratica nei “bare block” ovvero nei blocchi senza lista di argomenti esplicita, viene definita una lista implicita basata sulle variabili segnaposto, quelle con il “^”, ordinate lessicograficamente.

Si tratta a mio giudizio di una soluzione che permette di ottenere codice molto compatto e chiaro, e perfettamente in linea con la politica per cui implicito e meglio che esplicito. Chissà che non venga adottata in altri linguaggi, prima o poi.