Hai centrato la questione secondo me... a parte il fatto che una scrittura di questo tipo:
[codice]
hash.each_pair do |key, value|
puts "#{key} is #{value}"
end
[/codice]
non mi piace proprio (ma sara` sempre per il bias di cui sopra).
Esatto. Siamo completamente nel mondo dei gusti personali.
Sull'esempietto qua sopra siamo molto vicini come scrittura all'equivalente Python.
La differenza e' che in Python abbiamo una special form (e le special forms in python non si aggiungo)
e in ruby abbiamo un metodo (e i metodi in ruby -- e in python -- si aggiungono liberamente).
Per il resto e' un rimescolamento di termini...
Il discorso e' che "in generale" fra blocchi e funzioni la questione e' che da un lato e' brutto dover definire
una funzione per usarla una volta sola. Dall'altro si finisce per duplicare il codice in piu' blocchi che starebbero
bene astratti in una sola funzione.
Beh pensandoci, definire una funzione per utilizzarla una sola volta puo` avere i suoi perche`... Refactoring per esempio...
invece che definire una funzione/metodo con un corpo eccessivamente pregno, e` buona norma frammentare e responsabilizzare le varie parti
- Sebbene, in generale, queste cose capitano piu` con linguaggi tipo Java che con Python/Ruby[0] - .
Sono assolutamente d'accordo sul caso generale. Definire una funzione per refactoring o per documentazione e' sicuramente
una cosa buona e ha senso, anche se la funzione e' usata una sola volta (dopotutto quando si progetta una libreria, molte
funzioni sono usate 0 volte... le useranno i clients). Conversamente, immagina di "dovere" trasformare i body dei for in Python
in funzioni "obbligatoriamente".
Siamo d'accordo che ci sembrerebbe molto strana come pratica. Con il for non c'e' problema, ma se usiamo uno stile
un po' piu' funzionale di programmazione con lc e combriccola, a volte il desiderio di qualcosa di piu' ricco in una
funzione si pone. Questo essenzialmente traccia in Python il limite di buon senso su quando si *deve* usare un for.
Con i blocchi alla ruby questo limite sarebbe probabilmente 2 tacche avanti. Non credo niente di drammatico.
[codice]In generale astrarre con una funzione utilizzata una sola volta non ha tanto senso, di contro pero`, per ovviare alla mancanza in Python dei blocchi sintattici,
non si puo` all'ccorrenza ricorrere a soluzioni tipo *map*, *lamda expressions* e a "blocchi sintattici" utilizzando lo statement *with* ? [/codice]
Ci ricolleghiamo a sopra. Esatto! La differenza e' che tutta la parte funzionale di Python e' "volutamente" tenuta limitata.
E mi sta bene, per carita': quando si comincia a fare eccessivamente i fighi con quella parte di Python si scrive codice molto poco leggibile.
Credo che sia una questione sintattica: in Lisp le stesse cose sono molto piu' umane. Quindi si, sono d'accordo.
Poi with vince a mani basse sui blocchi per le cose che si fanno bene con i blocchi. *Ma* e' un po' piu' verboso da mettere giu'.
Provo a spiegare: quando si usa un "prefabbricato" with e' il massimo:
[codice]
with context() as c:
do_something(c)
[/codice]
e' ottimo. Specie nei casi semplici:
[codice]
with file(...) as f:
for line in f:
process_line(line)[/codice]
e' veramente bello. Guardiamolo in Ruby:
[codice]
File.open('log.txt') do |file_object|
file_object.each_line do |line|
print line
end
end
[/codice]
ancora una volta la forma e' veramente simile. Ancora una volta abbiamo un metodo
contro una special form. Qui si vedono due cose essenzialmente: io credo che il with
sia leggermente piu' chiaro.
La versione Ruby e' piu' facile da scrivere: usare i blocchi per il pattern
[codice]def f(...)
setup
begin
injected_block
ensure
cleanup
end
end[/codice]
e' davvero facile e comodo. In particolare i blocchi in ruby si usavano gia' per fare queste cose
quando noi non avevamo ancora with. All'epoca in effetti un po' invidiavo questa cosa. Scriversi
da 0 questo tipo di pattern (ovvero un context manager) e' parecchio piu' complesso *concettualmente*
esattamente come e' piu' comodo da usare.
Questo, se ci pensiamo, e' *esattamente* lo stesso pattern degli iteratori. Gli iteratori li abbiamo
semplificati con i generatori: due botte di yield e diventano semplici da scrivere.
Ok, concordo! Ma non credi che la questione sia piu` legata al problema che non a Python in generale?
Si, certo.
Alla fine, quando cerchi soluzioni *generlaiste* che tentanto di gestire in un unica maniera (unica e apparentemente flessibile) lo stesso problema, perdi sempre qualcosa.
In questo caso la possibilita` di trattare l'errore in maniera specifica per ciascuna situazione.
Ni. Qui entra l'arte del design: ci sono, immagino, esempi di problemi su cui non ci si puo' fare nulla.
Pero' spesso il problema e' che non si riesce a farlo mantenendo la feature *semplice*.
Per esempio... possiamo ridurre *ogni* forma di controllo di flusso ad un singolo costrutto? Si. Possiamo.
First class continuations. Ci fai eccezioni, backtracking, qualunque cosa. Cosa paghi? Paghi che il mondo sarebbe un posto migliore perche' i cialtroni non riuscirebbero a fare i programmatori e noi prenderemmo stipendi 10 volte piu' alti. Ma a parte questo perderemmo il fatto che la maggior parte della gente non saprebbe programmare (e di fatto qualcuno inventerebbe dei linguaggi umani per queste persone tornando al mondo odierno).
Una soluzione potrebbe essere quella di utilizzare dei decoratori che invochino un metodo di una istanza di delegate specifica
(immagino di essere stato poco chiaro - pero` ammetto che e` una idea estemporanea e molto probabilmente eccessivamente sovraingegnerizzata)

Interessante... un esempio?
------
[codice]
File f=new File("test.txt");
FileInputStream fis=new FileInputStream(f);
InputStreamReader isr=new InputStreamReader(fis);
BufferedReader br=new BufferedReader(isr);
String line = br.readLine()
while(line != null) {
System.out.println(line);
line=br.readLine();
}
[/codice]
VS
[codice]
file f = open("test.txt")
for l in f:
print l
[/codice]
EDIT
Vabeh, ammetto che la soluzione in Java si puo` compattare ancora.... per correttezza compatto
[codice]
BufferedReader br=new BufferedReader(new InputStreamReader(new FileInputStream(new File("test.txt"))));
String line = br.readLine()
while(line != null) {
System.out.println(line);
line=br.readLine();
}
[/codice]
Ecco... leggibile soprattutto! :D
[/quote]
Lol. Quoto.
Ma in Java anche questo ha un suo fascino (ecco il lato dinamico e funzionale di Java).
[codice=Java]
import java.io.*;
import java.util.Iterator;
import java.util.NoSuchElementException;
public class Prova {
static public void main(String[] s) throws FileNotFoundException {
final BufferedReader br = new BufferedReader(new FileReader("/Users/enrico/log.txt"));
for(String line : new Iterable<String>() {
public Iterator<String> iterator() {
return new Iterator<String>() {
private String bufferedLine = null;
private boolean good = true;
public boolean hasNext() {
if(bufferedLine == null) {
try {
bufferedLine = br.readLine();
} catch (IOException e) {
good = false;
return false;
}
}
return good && (bufferedLine != null);
}
public String next() {
if(!good) { throw new NoSuchElementException(); }
if(bufferedLine != null) {
String tmp = bufferedLine;
bufferedLine = null;
return tmp;
} else {
try {
String tmp = br.readLine();
if(tmp != null) {
return tmp;
} else {
good = false;
throw new NoSuchElementException();
}
} catch (IOException e) {
good = false;
throw new NoSuchElementException();
}
}
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
}) {
System.out.println(line);
}
}
}
[/codice]
In clojure...
[codice]
(import [java.io BufferedReader FileReader])
(with-open [f (BufferedReader. (FileReader. "log.txt"))]
(doseq [line (line-seq f)]
(println line)))
[/codice]