Comments for post Pipeline programming

riffraff writes: in effetti in haskell c'è una cosa che è più pipe del $, se non ricordo male. Afaik, $ è solo un operatore di raggruppamento a bassa priorità, ergo fun2 $ fun1 () è solo fun2(fun1()) ma puoi usare le arrows... non che ne sia sicuro ma mi pare che l'operatore apposito sia >>> quindi: fun1 >>> fun2 >>> fun3 Con partial application inclusa :) Anche in perl6 esiste una cosa analoga, e sempre affidandomi alla mia scarsa memoria... fun1 ==> fun2 ==> fun3
panta writes: Si, l'elaborazione parallela a questo punto discende quasi naturalmente. L'accoppiata lightweight threads stile erlang, e parallelismo su posix threads darebbe notevole flessibilita` su architetture sia moderne che antiche. Come dici tu il pipe e` un meccanismo IPC che nasconde dall'utente i problemi di sincronizzazione. Niente locks o altri casini. Sarebbe un po' come portare il concetto di message passing di qnx dal sistema operativo al linguaggio.
antirez writes: per panta: Esatto... io pensavo solo ad una sintassi alternativa per la composizione e implicitamente per l'applicazione parziale (sintassi dell'asterisco nell'argomento mancante). E' solo zucchero sintattico ma cattura delle due tecniche un caso di utilizzo cosi' specifico che permette quasi di ragionare in termini diversi anche se in fondo e' esattamente la stessa cosa. La tua proposta e' assolutamente affascinante... e a questo punto oltre a continuazione e lazy evaluation perche' tale sintassi non dovrebbe addirittura (in tempi di processori con piu' core) nascondere l'elaborazione parallela? Anche se non in maniera perfetta scrivere degli algoritmi in termini di pipe potrebbe permettere di parallelizzare la computazione in maniera quasi trasparente su piu' thread.
panta writes: Ok, quindi tu pensi alle pipe come ad una semplice sintassi diversa per la "composizione" di funzioni? Se e` cosi` non sono sicuro di volerla in un linguaggio, perche' seppure sicuramente piu` leggibile e` poco DRY, per dirla come va di moda oggi, o in altri termini non e` ortogonale alla sintassi gia` esistente - e` zucchero sintattico... Poi temo che la nuova sintassi troppo facilmente indurrebbe a dimenticarsi che l'output di un comando e` generato per intero prima di essere passato al successivo, poiche' uno e` portato a pensare alla pipe di unix. Si arriverebbe a facilmente a inefficienze. Banalizzando ed estremizzando, uno potrebbe essere tentato di scrivere un tool di masterizzazione dividendolo in una funzione "find" e una "makeiso". La prima creerebbe un array con forse decine di milioni di file... Proprio per queste applicazioni invece, non immediate da scrivere altrimenti, sarebbe utile un supporto nativo che nasconda l'abbinata continuazione/lazy evaluation. Sarebbe utile anche in applicazioni numeriche tra l'altro.
antirez writes: per pp: L'operatore di composizione di Haskell e' praticamente la pipe :) Molto bello. Inoltre si... hai perfettamente ragione, l'applicazione parziale e' il modo matematicamente corretto di evitare la sintassi dell'asterisco dove manca l'argomento. Grazie per il tuo interessante contributo.
antirez writes: Ciao Marco! Il tuo commento mette le cose in una luce completamente diversa. In realta' ci sono due semantiche distinte per la pipe ma lo sto realizzando solo ora leggendo il tuo commento. Una e' quella da me proposta ed implementata in Tcl qua (http://wiki.tcl.tk/17879), in cui ogni elemento della pipe prende il precedente input, lo elabora per intero, e lo passa al prossimo. L'altra semantica e' invece quella in cui c'e' un flusso anche temporale, come accade nella pipe di unix, e i programmi in realta' vengono eseguiti simultaneamente aspettando uno l'input dell'altro. La differenza puo' sembrare piccola, ma invece e' molto grande perche' da una parte la semantica a cui pensavo io e' molto piu' semplice da implementare, mentre quella che realizza il vero flusso tra una funzione e l'altra necessita le co-routine o qualche altra forma di continuazioni, ma ha anche il grande vantaggio che non deve tenere tutto in memoria ad ogni step! Comunque per molti usi pratici sono convinto che la semantica da me proposta sia piu' pratica perche' piu' facile da implementare nei linguaggi comuni, permette il look ahead, e risolve il secondo problema... quello dell'innesto a T :) Questa cosa della T in effetti l'avevo pensata, e anche se volgarmente imperativa una soluzione e' questa (in Tcl): pipe a b c d > subtotal \ d1 $subtota e1 f1 > res1 \ d2 $subtota e2 f2 > res2 puts $res1 $res2 In pratica nella funzione Tcl che ho proposto nel wiki di Tcl un po' come accade nella shell di Unix il simbolo > seguito da un nome di variabile mette il risultato corrente della pipe in quella variabile. A quel punto avviare due diverse computazioni per simulare l'innesto a T diventa semplice. Anche in questo caso e' solo una illusione rispetto alla reale semantica della pipe che giustamente necessiterebbe di un ben piu' complesso meccanismo. Grazie del commento!
pp writes: In haskell la cosa riesce particolarmente bene. Con l'operatore di applicazione ($): fun3 $ fun2 $ fun1 something O con l'operatore di composizione (.): (fun3 . fun2 . fun1) something E l'applicazione parziale risulta comodissima in caso di funzioni con piu' di un argomento (+ 10) . (* 2) . sum $ [1,2,3] In python si potrebbe lavorare su __or__, ma ogni funzione "componibile" dovrebbe essere decorata e wrappata in un oggetto. Forse in ruby (classi aperte) si potrebbe implementare comodamente, ma non conosco ruby :) Comunque una combinazione di compose e partial per python non dovrebbe essere troppo scomoda. fun = reduce(compose, (fun1, partial(fun2, 10), fun3)) fun(arg)
panta writes: L'idea e` interessante e va sicuramente approfondita. Mi sembra pero' che per "mappare" bene il concetto di unix pipe ai linguaggi di programmazione si debba estendere la dimensionalita` dell'input/output tra le funzioni. Le pipe di unix offrono un collegamento "temporale", trasferendo dati temporalmente diversi ma morfologicamente dello stesso tipo (linee di un file con lo stesso formato ad esempio). Se pensiamo a portare il concetto ai linguaggi di programmazione dovremmo mantenere questo aspetto. Entra in gioco il fatto che le funzioni sono n-arie invece che 1-arie. Basta pensare allora a flussi bidimensionali di dati (un asse e` il tempo, l'altro la "n-arieta`" dei parametri). Possiamo anche pensare che un parametro non e` piu` un valore ma uno stream temporale di valori. Serve un operatore di slicing evoluto per selezionare una fetta o un sottoinsieme degli stream di parametri. Ad esempio: ... | getparameters | multiply{1,3} dove getparameters ipoteticamente fornisce un flusso di tuple a piu` di tre valori e multiply moltiplica due colonne del flusso in ingresso, in questo caso la prima e la terza. Andando avanti con l'esempio ci si rende pero' subito conto dell'esigenza di un nuovo operatore, il "raccordo a T", rimanendo nell'analogia idraulica. Basta pensare a come estendere l'esempio per addizionare 1 al flusso di valori prodotto da multiply (o peggio addizionare un flusso derivante da qualche altra parte). Qui non saprei come trovare una sintassi soddisfacente. Servirebbe qualcosa di questo tipo: [... | getparameters | multiply{1,3}], /1/ -| sum{1,2} L'operatore /1/ serve a creare un flusso a partire da una costante. All'inizio avevo scritto identity(1), ma ovviamente non si puo` piu` fare, perche' la funzione identity si aspetta comunque un flusso... Direi che la cosa forse non e` nemmeno difficile da fare in Lisp o Scheme ricorrendo alla lazy evaluation. Commenti?
home