|
|||||||
|
|
|
![]() |
|
|
Strumenti |
|
|
#1 |
|
Senior Member
Iscritto dal: Oct 2007
Città: Padova
Messaggi: 4131
|
[Haskell] implementazione di Data.List.intersperse
Salve,
mi sto cimentando con Haskell, e mentre studio ho preso l'abitudine di provare a reimplementare tutte le funzioni della libreria standard che incontro. Mi sono lambiccato un po' per capire come implementare intersperse, ho tirato fuori queste due versioni: Codice:
-- intersperse with patter matching & recursion intersperse'' :: a -> [a] -> [a] intersperse'' _ [] = [] intersperse'' _ [x] = [x] intersperse'' e (x:xs) = x: (e: (intersperse'' e xs)) -- intersperse with foldr, drop & partial application rIntersperse :: a -> [a] -> [a] rIntersperse e = (drop 1) . foldr (\ x acc -> e:x:acc) [] In particolare pensavo di poterla implementare con una scan, ma mi sono incartato, convincendomi che non si può fare.
__________________
As long as you are basically literate in programming, you should be able to express any logical relationship you understand. If you don’t understand a logical relationship, you can use the attempt to program it as a means to learn about it. (Chris Crawford) |
|
|
|
|
|
#2 |
|
Senior Member
Iscritto dal: Oct 2007
Città: Padova
Messaggi: 4131
|
uppete
Uè infami! 11 letture e nessuno che mi dica almeno "ma che cazzo fai?" Mi aspettavo qualcosa almeno dai più esotici (marco.r, shinya, ecc...) Completo la domanda: quale delle due versioni è più efficiente a runtime? Quale più idiomatica? Many thanks
__________________
As long as you are basically literate in programming, you should be able to express any logical relationship you understand. If you don’t understand a logical relationship, you can use the attempt to program it as a means to learn about it. (Chris Crawford) |
|
|
|
|
|
#3 | |
|
Senior Member
Iscritto dal: Dec 2005
Città: Istanbul
Messaggi: 1817
|
Quote:
comunque dipende da cosa intendi dire piu' efficiente, visto che possiamo parlari di performance, spazio, se hai a che fare con liste finite, infinite... (a naso non ci dovrebbero essere differenze rilevanti, poi verifico)
__________________
One of the conclusions that we reached was that the "object" need not be a primitive notion in a programming language; one can build objects and their behaviour from little more than assignable value cells and good old lambda expressions. —Guy Steele |
|
|
|
|
|
|
#4 | |
|
Senior Member
Iscritto dal: Oct 2007
Città: Padova
Messaggi: 4131
|
Ok, mi interessava soprattuto l'efficienza a runtime in termini di tempi di esecuzione.
Comunque mi hai messo la pulce nell'orecchio (per quanto riguarda l'uso della memoria). Se hai tempo (e piacere), potresti chiarirmi una cosa? Stavo leggendo del GC di GHC (sto usando GCHi) e leggevo: Quote:
Grazie di ogni eventuale spiegazione, marco.r
__________________
As long as you are basically literate in programming, you should be able to express any logical relationship you understand. If you don’t understand a logical relationship, you can use the attempt to program it as a means to learn about it. (Chris Crawford) |
|
|
|
|
|
|
#5 | |
|
Senior Member
Iscritto dal: Oct 2007
Città: Padova
Messaggi: 4131
|
Hum, ho appena incontrato foldl' e foldl1' e leggo or ora della questione dei "thunk" nella verisione lazy di queste due funzioni e dei problemi di stack overflow, comincio a capirci qualcosina.
Inoltre ho spulciato l'implementazione di interseperse in Data.List: Quote:
__________________
As long as you are basically literate in programming, you should be able to express any logical relationship you understand. If you don’t understand a logical relationship, you can use the attempt to program it as a means to learn about it. (Chris Crawford) Ultima modifica di banryu79 : 02-11-2011 alle 19:58. |
|
|
|
|
|
|
#6 | ||
|
Senior Member
Iscritto dal: Dec 2005
Città: Istanbul
Messaggi: 1817
|
Quote:
Comunque il tuo dubbio e' dovuto principalmente alla terminologia usata. Tenendo presente che quello di GHC e' un GC generazionale, fa conto che la "nursery" sia la generation 0, mentre la "main memory" le generazioni da 1 in su (non so se in pratica sia una sola generazione o piu'). La questione del non toccare il resto e' legata all'affermazione Quote:
Questo vuol dire che per decidere se i dati della generazione N sono garbage o meno, posso considerare solo le generazioni <= N, e ovviamente la root (ovvero lo stack, variabili globali, registri del processore...). In particolare la vita degli oggetti della generazione 0 (la nursery) e' totatlmente indipendente dalle altre generazioni, per cui l'idea e' che io la tengo di dimensione piccola, e la pulisco frequentemente. Questa e' una operazione molto rapida perche' devo controllare solo poche centinaia di kb invece che magari due GB di memoria dell'intero processo. Questo vuol dire che per GHC decidere di allocare le variabili locali della funzione nello heap invece che nello stack non e' molto piu' costoso. Il discorso fatto in quel link e' corretto, visto che se io produco 10 byte di garbage ogni 1 devo fare meta' lavoro piuttosto che generarne 5 ogni 1, ma ovviamente il garbage collector verra' chiamato in causa piu' spesso, per questo dicevo che e' una cavolata.
__________________
One of the conclusions that we reached was that the "object" need not be a primitive notion in a programming language; one can build objects and their behaviour from little more than assignable value cells and good old lambda expressions. —Guy Steele |
||
|
|
|
|
|
#7 | |
|
Senior Member
Iscritto dal: Dec 2005
Città: Istanbul
Messaggi: 1817
|
Quote:
Francamente trovo la prima versione piu' leggibile e preferibile (foldl e compagnia le trovo comode quando l'operazione e' a sua volta un parametro, per cui si riesce a programmare a piu' alto livello, altrimenti preferisco la versione esplicita). Anche l'impatto sulla memoria dovrebbe essere relativamente analogo (bisogna un po' capire come funziona la fold e l'impatto sullo stack, per maggiori dettagli puoi spulciarti http://www.haskell.org/haskellwiki/Fold e soprattutto http://www.haskell.org/haskellwiki/Stack_overflow). Non puoi usare una scan, e piu' che convincersi si puo' dimostrarlo. scanl (e parenti) ritorna una lista della stessa lunghezza di quella originale, mentre tu ne vuoi una che abbia lunghezza 2N-1. Direi che non sono compatibili le due cose. b.t.w. se vuoi una soluzione ancora piu' sintetica (e abbastanza leggibile, anche se probabilmente meno efficiente), il seguente e' un bel one-liner Codice:
intersperse c xs = tail $ concat [ [c,x] | x <- xs ]
__________________
One of the conclusions that we reached was that the "object" need not be a primitive notion in a programming language; one can build objects and their behaviour from little more than assignable value cells and good old lambda expressions. —Guy Steele |
|
|
|
|
|
|
#8 | ||
|
Senior Member
Iscritto dal: Dec 2005
Città: Istanbul
Messaggi: 1817
|
Quote:
Cerco di spiegarmi, ma non garantisco buoni risultati Parto intanto dalla definizione di foldr Codice:
foldr f z [] = z foldr f z (x:xs) = f x (foldr f z xs) Codice:
rIntersperse e xs = drop 1 (foldr f [] xs)
where f x acc = e:x:acc
Codice:
rIntersperse 10 [1,2,3] drop 1 (foldr f [] [1,2,3]) drop 1 (f 1 (foldr f [] [2,3])) drop 1 (10 : 1 : (foldr f [] [2,3])) 1 : (foldr f [] [2,3]) -- Qua ho gia' il primo elemento della lista 1 : (f 2 (foldr f [] [3])) 1 : (10 : 2 : (foldr f [] [3])) -- qui ho il secondo e il terzo 1 : (10 : 2 : (f 3 (foldr f [] []))) 1 : 10 : 2 : 10 : 3 : [] -- qua anche il resto In altri termini il seguente codice funziona Codice:
take 10 $ rIntersperse 0 [1..] Per il discorso foldl foldr... occhio che sono due cose distinte, a meno che l'operazione che vai a fare non sia commutativa. Per capirlo e' molto comodo pensare al fatto che, figurativamente, ottieni il risultato della fold{r,l} semlicemente sostituendo la funzione argomento alle virgole, e associando rispettivamente a destra e sinistra: Codice:
[1,2,3,4,5] -- lista iniziale (1 `f` (2 `f` (3 `f` (4 `f` 5)))) -- foldr (((((1 `f` 2) `f` 3) `f` 4) `f` 5) -- foldl Codice:
(1 + (2 + (3 + (4 + 5)))) ((((1 + 2) + 3) + 4) + 5) Nel tuo caso la intersperse fatta con la foldr e' piu' o meno la seguente cosa Codice:
intersperse 10 [1,2,3,4,5] (1 : 10 : (2 : 10 : (3 : 10 : ( 4 : 10 : 5)))) La foldl' e' simile alla foldl solo che "forza" la computazione dei risultati parziali durante l'esecuzione. Ad esempio quando faccio foldl (+) della solita lista Codice:
((((1 + 2) + 3) + 4) + 5) foldl' invece forza il calcolo (1+2) = 3 prima di incrociare l'elemento successivo, per cui ti porti dietro il risultato parziale. A questo punto (spero Vuoi produrre una lista o comunque una struttura dati che puoi cominciare ad usare quando la computazione (potenzialmente infinita) non e' ancora terminata ? Usa foldr: Codice:
(1 : 10 : (2 : 10 : (3 : 10 : ( 4 : 10 : 5)))) Codice:
((((1+2)+3)+4)+5) Quote:
Per capirsi l'alternativa e' Codice:
intersperse :: a -> [a] -> [a]
intersperse _ [] = []
intersperse sep (x:xs) = x : prependToAll sep xs
where prependToAll sep (x:xs) = sep : x : prependToAll sep xs
__________________
One of the conclusions that we reached was that the "object" need not be a primitive notion in a programming language; one can build objects and their behaviour from little more than assignable value cells and good old lambda expressions. —Guy Steele |
||
|
|
|
|
|
#9 | ||||
|
Senior Member
Iscritto dal: Oct 2007
Città: Padova
Messaggi: 4131
|
Quote:
Una considerazione forse scontata: quindi, per via dell'imutabilità dei valori in un linguaggio di programmazione puramente funzionale, non ha senso immaginarne uno che non preveda una piattaforma con GC e invece deleghi al programmatore la gestione della memoria, sarebbe assurdo, giusto? Quote:
Quote:
La cosa che mi colpisce di più, nella tua versione qui sopra analoga alla mia, è che per sbarazzarti del primo "separator" in testa alla lista tu dici "è la coda" e io invece dico "molla il primo elemento della lista": prospettiva funzionale (la tua) contro prospettiva imperativa (la mia), il nuovo paradigma sarà la cosa che mi richiederà più tempo in assoluto da adottare... Quote:
So che dare risposte così articolate richiede tempo, ti ringrazio tantissimo per le spiegazioni, mi sono state utilissime! Appena ho tempo (a casa) mi sviscero ben bene questo post, che per ora mi ha chiarito comunque alcuni dubbi e domandine che mi erano sorte circa l'opportunità o meno di usare le fold{l,r} le differenze tra loro e quando usare le versioni lazy piuttosto che le strict. Sto anche consultando le pagine su haskell.org che mi hai indicato, c'è parecchia carne al fuoco a quanto pare, però sto capendo un poco di più (fold' o foldr, la facenda della strictness del secondo argomento della funzione passata alla fold ecc...) Comuqnue anche a me piace (trovo più leggibile) la versione con il pattern matching e la ricorsione. Marco, mi sei stato di grandissimo aiuto, grazie!
__________________
As long as you are basically literate in programming, you should be able to express any logical relationship you understand. If you don’t understand a logical relationship, you can use the attempt to program it as a means to learn about it. (Chris Crawford) |
||||
|
|
|
|
|
#10 | |
|
Senior Member
Iscritto dal: Dec 2005
Città: Istanbul
Messaggi: 1817
|
Quote:
L'esempio classico e' quello dell'albero di ricerca funzionale Fa conto di avere definito un albero in questo modo Codice:
data Tree = Leaf | Node Int Tree Tree Codice:
insert :: Tree -> Int -> Tree
insert Leaf x = Node x Leaf Leaf
insert (Node k l r) = if x <= k then Node k (insert l x) r
else Node k l (insert r x)
Ma il nuovo albero che ottengo, non e' totalmente differente da quello vecchio, anzi gran parte dei dati sono condivisi. Le uniche differenze sono nel cammino tra la radice e l'elemento nuovo inserito. Ad esempio nel seguente albero Codice:
10 / \ 5 100 / / \ 1 50 200 Codice:
10 / \ 5 100 / \ / \ 1 7 50 200 Come puoi immaginare tenere traccia di tutto questo manualmente diventa impossibile.
__________________
One of the conclusions that we reached was that the "object" need not be a primitive notion in a programming language; one can build objects and their behaviour from little more than assignable value cells and good old lambda expressions. —Guy Steele |
|
|
|
|
|
|
#11 | |
|
Senior Member
Iscritto dal: Oct 2007
Città: Padova
Messaggi: 4131
|
Quote:
Codice:
-- Intersperse with foldr (lazy, can work on infinite list) -- example: -- intersperse 10 [1,2,3,4,5] -- with foldr expanded: -- 1 f (2 f (3 f (4 f 5))) -- 10:1:(10:2:(10:3:(10:4:(10:5([]))))) rIntersperse :: a -> [a] -> [a] rIntersperse e = tail . foldr (\ x acc -> e:x:acc) [] -- Intersperse with foldl' (strict, bad cause uses ++) -- -- intersperse 10 [1,2,3,4,5] -- with foldl expanded: -- ((((1 f 2) f 3) f 4) f 5) -- ((((([] ++ [1,10]) ++ [2,10]) ++ [3,10]) ++ [4,10]) ++ [5,10]) lIntersperse :: a -> [a] -> [a] lIntersperse e = init . foldl' (\ acc x -> acc ++ [x,e]) [] @EDIT: beh, guardando l'espansione della versione con foldl', si vede che è uno schifo
__________________
As long as you are basically literate in programming, you should be able to express any logical relationship you understand. If you don’t understand a logical relationship, you can use the attempt to program it as a means to learn about it. (Chris Crawford) Ultima modifica di banryu79 : 03-11-2011 alle 10:16. |
|
|
|
|
|
|
#12 | |
|
Senior Member
Iscritto dal: Dec 2005
Città: Istanbul
Messaggi: 1817
|
Quote:
non si puo' vedere
__________________
One of the conclusions that we reached was that the "object" need not be a primitive notion in a programming language; one can build objects and their behaviour from little more than assignable value cells and good old lambda expressions. —Guy Steele |
|
|
|
|
|
|
#13 | |
|
Senior Member
Iscritto dal: Oct 2007
Città: Padova
Messaggi: 4131
|
Quote:
Scusa le considerazioni forse banali, ma sai com'è quando uno è nuovo... A proposito di essere newbie: il tuo esempio dell'albero qua sopra l'ho capito, ma non ho ancora visto la definizione di tipi utente (mi riferisco a 'sta roba: data Tree = Leaf | Node Int Tree Tree; data type e compagnia bella per me ancora sono magia nera). Grazie di nuovo, marco.r
__________________
As long as you are basically literate in programming, you should be able to express any logical relationship you understand. If you don’t understand a logical relationship, you can use the attempt to program it as a means to learn about it. (Chris Crawford) |
|
|
|
|
|
|
#14 | |
|
Senior Member
Iscritto dal: Jul 2005
Città: Bologna
Messaggi: 1130
|
Quote:
Sono due costruttori: uno senza argomenti (Leaf) e uno che prende un intero e due Tree come parametri (Node). Un Tree lo puoi costruire in un modo o nell'altro. Mi spiego?
__________________
-> The Motherfucking Manifesto For Programming, Motherfuckers |
|
|
|
|
|
|
#15 | |
|
Senior Member
Iscritto dal: Oct 2007
Città: Padova
Messaggi: 4131
|
Quote:
Scusa, per chiudere il discorso sulle fold{l,r}, mi sembra che foldl e foldr siano concettualmente analoghe alle head e tail recursion, rispettivamente, ha senso o questa analogia la vedo solo io perchè non ho le idee chiare? (sto pensando a implementazioni di funzioni ricorsive in linguaggi imperativi, tipo in C) Grazie
__________________
As long as you are basically literate in programming, you should be able to express any logical relationship you understand. If you don’t understand a logical relationship, you can use the attempt to program it as a means to learn about it. (Chris Crawford) |
|
|
|
|
|
|
#16 | |||
|
Senior Member
Iscritto dal: Oct 2007
Città: Padova
Messaggi: 4131
|
Ancora però non mi è chiara una cosa; avendo queste due implementazioni di length:
Codice:
-- length implemented with sum and list comprehension length'' :: (Num b) => [a] -> b length'' xs = sum [1 | _ <- xs] -- length implemented with recursion and pattern matching recLength :: (Num b) => [a] -> b recLength [] = 0 recLength (_:xs) = 1 + recLength xs Quote:
Quote:
@EDIT: includo l'implementazione di sum, o usa una foldl oppure la ricorsione, quindi non capisco cosa causa la netta differenza che ho rilevato. Codice:
...
-- | The 'sum' function computes the sum of a finite list of numbers.
sum :: (Num a) => [a] -> a
-- | The 'product' function computes the product of a finite list of numbers.
product :: (Num a) => [a] -> a
#ifdef USE_REPORT_PRELUDE
sum = foldl (+) 0
product = foldl (*) 1
#else
sum l = sum' l 0
where
sum' [] a = a
sum' (x:xs) a = sum' xs (a+x)
product l = prod l 1
where
prod [] a = a
prod (x:xs) a = prod xs (a*x)
#endif
...
@EDIT2: Hum, pare abbia a che fare col fatto che recLength è una funzione "almost tail recursive", infatti se ne implmento la versione veramente "tail recurvise" i risultati sono molto vicini a quelli di length'' Codice:
-- length: real tail recursion (must use an accumulator) tailLength :: (Num b) => [a] -> b -> b tailLength [] acc = acc tailLength (x:xs) acc = tailLength xs (1 + acc) Quote:
Meglio che continuo con il tutorial guidato, mi sa che sono uscito troppo dal seminato, e questi sono aspetti più avanzati del linguaggio e forse riguardano anche come è implementata la piattaforma... torno a studiare.
__________________
As long as you are basically literate in programming, you should be able to express any logical relationship you understand. If you don’t understand a logical relationship, you can use the attempt to program it as a means to learn about it. (Chris Crawford) Ultima modifica di banryu79 : 03-11-2011 alle 12:34. |
|||
|
|
|
|
|
#17 | |
|
Senior Member
Iscritto dal: Dec 2005
Città: Istanbul
Messaggi: 1817
|
Quote:
Comunque l'analisi delle prestazioni e della memoria in un programma lazy e' argomento tutt'altro che banale eh (pero' ci si guadagna in una maggiore facilita' nel analizzarne la correttezza). Anche in questo caso, e visto che sommiamo valori (i.e. non dobbiamo generare liste parziali) puo' valere la pena forzare un po' di strictness. Ad esempio possiamo prendere la versione con accumulatore e forzare il calcolo della somma ad ogni passaggio: Codice:
length3 x = f 0 x
where f acc [] = acc
f acc (x:xs) = acc `seq` f (1+acc) xs
Per fare un esempio sulla mia macchina ottengo i seguenti valori Codice:
*Main Data.List> length'' [1..10000000] 10000000 (35.49 secs, 3178182056 bytes) *Main Data.List> length3 [1..10000000] 10000000 (4.91 secs, 2984878736 bytes) Codice:
length4 xs = foldl1 (+) 0 [ 1 | _ <- xs ] *Main Data.List> length4 [1..10000000] 10000000 (2.94 secs, 1691837800 bytes)
__________________
One of the conclusions that we reached was that the "object" need not be a primitive notion in a programming language; one can build objects and their behaviour from little more than assignable value cells and good old lambda expressions. —Guy Steele |
|
|
|
|
|
|
#18 | ||
|
Senior Member
Iscritto dal: Oct 2007
Città: Padova
Messaggi: 4131
|
Quote:
Guarda qua, sulla mia macchina, con sum e queste due funzioni: Codice:
import Data.Foldable (foldl') -- sum with lazy left fold lazySum :: (Num a) => [a] -> a lazySum = foldl (+) 0 -- sum with strict left fold strictSum :: (Num a) => [a] -> a strictSum = foldl' (+) 0 Quote:
Se non hanno scelto di usare foldl' ci sarà anche un perchè, e mi chiedo quale...
__________________
As long as you are basically literate in programming, you should be able to express any logical relationship you understand. If you don’t understand a logical relationship, you can use the attempt to program it as a means to learn about it. (Chris Crawford) Ultima modifica di banryu79 : 03-11-2011 alle 15:18. |
||
|
|
|
|
| Strumenti | |
|
|
Tutti gli orari sono GMT +1. Ora sono le: 14:20.




















