delete
è una ricerca modale. Ha due diverse modalità operative: se è già stato trovato o meno il risultato. È possibile utilizzare foldr
per creare una funzione che passa lo stato lungo la linea quando viene controllato ciascun elemento. Quindi, nel caso di delete
, lo stato può essere un semplice Bool
. Non è esattamente il miglior tipo, ma lo farà.
Una volta identificato il tipo di stato, è possibile iniziare a lavorare sulla costruzione foldr
. Ho intenzione di camminare attraverso a capire come ho fatto. Abiliterò ScopedTypeVariables
solo così posso annotare meglio il tipo di sottoespressioni. Uno conosci il tipo di stato, sai che vuoi foldr
per generare una funzione prendendo un valore di quel tipo e restituendo un valore del tipo finale desiderato. Questo è sufficiente per iniziare a delineare le cose.
{-# LANGUAGE ScopedTypeVariables #-}
delete :: forall a. Eq a => a -> [a] -> [a]
delete a xs = foldr f undefined xs undefined
where
f :: a -> (Bool -> [a]) -> (Bool -> [a])
f x g = undefined
È un inizio. Il significato esatto di g
è un po 'complicato qui. In realtà è la funzione per elaborare il resto dell'elenco. È accurato guardarlo come una continuazione, infatti. Rappresenta in modo assoluto l'esecuzione del resto della piegatura, con il tuo stato che scegli di passare. Detto questo, è il momento di capire cosa mettere in alcuni di questi posti undefined
.
{-# LANGUAGE ScopedTypeVariables #-}
delete :: forall a. Eq a => a -> [a] -> [a]
delete a xs = foldr f undefined xs undefined
where
f :: a -> (Bool -> [a]) -> (Bool -> [a])
f x g found | x == a && not found = g True
| otherwise = x : g found
Questo sembra relativamente semplice. Se l'elemento corrente è quello cercato, e non è stato ancora trovato, non inviarlo e continuare con lo stato impostato su True
, a indicare che è stato trovato. otherwise
, emette il valore corrente e continua con lo stato corrente.Questo lascia solo il resto degli argomenti a foldr
. L'ultimo è lo stato iniziale. L'altra è la funzione di stato per una lista vuota. Ok, anche quelli non sono male.
{-# LANGUAGE ScopedTypeVariables #-}
delete :: forall a. Eq a => a -> [a] -> [a]
delete a xs = foldr f (const []) xs False
where
f :: a -> (Bool -> [a]) -> (Bool -> [a])
f x g found | x == a && not found = g True
| otherwise = x : g found
Indipendentemente dallo stato, produrre una lista vuota quando si incontra una lista vuota. E lo stato iniziale è che l'elemento cercato non è stato ancora trovato.
Questa tecnica è applicabile anche in altri casi. Ad esempio, può essere scritto come foldr
in questo modo. Se si guarda foldl
come una funzione che trasforma ripetutamente un accumulatore iniziale, è possibile indovinare che è la funzione prodotta - come trasformare il valore iniziale.
{-# LANGUAGE ScopedTypeVariables #-}
foldl :: forall a b. (a -> b -> a) -> a -> [b] -> a
foldl f z xs = foldr g id xs z
where
g :: b -> (a -> a) -> (a -> a)
g x cont acc = undefined
I casi di base non sono troppo difficile da trovare quando il problema viene definito come manipolare l'accumulatore iniziale, di nome z
lì. L'elenco vuoto è la trasformazione dell'identità, id
e il valore trasmesso alla funzione creata è z
.
L'implementazione di g
è più complessa. Non si può fare ciecamente sui tipi, perché ci sono due diverse implementazioni che usano tutti i valori attesi e il controllo del tipo. Questo è un caso in cui i tipi non sono sufficienti e devi considerare il significato delle funzioni disponibili.
Iniziamo con un inventario dei valori che sembrano come dovrebbero essere utilizzati e il loro tipo. Le cose che sembrano debbano essere utilizzate nel corpo di g
sono f :: a -> b -> a
, x :: b
, cont :: (a -> a)
e acc :: a
. f
prenderà ovviamente il x
come secondo argomento, ma c'è una domanda sul posto appropriato da usare cont
. Per capire dove va, ricorda che rappresenta la funzione di trasformazione restituita elaborando il resto dell'elenco e che foldl
elabora l'elemento corrente e quindi passa il risultato di tale elaborazione al resto dell'elenco.
{-# LANGUAGE ScopedTypeVariables #-}
foldl :: forall a b. (a -> b -> a) -> a -> [b] -> a
foldl f z xs = foldr g id xs z
where
g :: b -> (a -> a) -> (a -> a)
g x cont acc = cont $ f acc x
Questo suggerisce anche che foldl'
può essere scritto in questo modo con un solo piccolo cambiamento:
{-# LANGUAGE ScopedTypeVariables #-}
foldl' :: forall a b. (a -> b -> a) -> a -> [b] -> a
foldl' f z xs = foldr g id xs z
where
g :: b -> (a -> a) -> (a -> a)
g x cont acc = cont $! f acc x
La differenza è che ($!)
è usato per suggerire la valutazione di f acc x
prima che sia passato a cont
. (Dico "suggerire", perché ci sono alcuni casi limite in cui ($!)
non forza la valutazione anche per quanto riguarda WHNF.)
Apprezzo la tua risposta e trovalo utile Avrei votato in su se avessi abbastanza reputazione, ma non accetto ancora perché la tua risposta non è proprio ciò che sto cercando. Ma è colpa mia se non ho fatto la domanda più precisa, vedrò se posso modificare. Sono più interessato a un approccio che utilizza foldr per generare una funzione. Hai ragione che la tua prima implementazione è praticamente ciò che ho provato. – user168064
@ user168064 Ok. Dopo aver trascorso circa 3 ore nell'apprendimento dell'argomento, posso rispondere anche alla domanda desiderata. Penso che meriti di essere una seconda risposta più di una modifica a questa, quindi è in arrivo un'altra risposta. – Carl