2015-10-20 13 views
7

Ho una struttura in stile schema di ricorsione e vorrei ottenere un elenco di tutte le sottostrutture inclusa la struttura completa, ovvero l'equivalente di ciò che fa la funzione tails su un List . Penso che sarebbe possibile implementarlo chiamando lo para, mappando di nuovo alla struttura originale ad ogni passaggio, e poi attaccando separatamente la struttura originale sul davanti, ma ciò sembra molto ingombrante: (non verificato, scuse se Haskell non è corretto; scritta in termini di Mu come non ho davvero capito l'ancora Base costrutto)Generalizzazione schemi di ricorsione di `code '

gtails :: Functor f => Mu f -> [Mu f] 
gtails = para (\a -> (fmap fst a) : (foldMap snd a)) 

(vale a dire nel caso f=Prim questo è tails, per altri f è una generalizzazione)

c'è un modo più bello ? Mi rendo conto che questo non è poi così male, ma il fmap fst a per recuperare la struttura "originale" in quel passaggio è piuttosto ingombrante e lo foldMap snd a è qualcosa che trovo ripetuto molto quando uso para (allo stesso modo che si sente di nuovo come dovrebbe essere inutile).

risposta

10

Non vedo alcun problema con para. Basta Contro indietro la testa e la coda ad ogni passo Cons:

import Data.Functor.Foldable 

tails :: [a] -> [[a]] 
tails = para (\case Nil -> [[]]; Cons a (as, res) -> (a : as) : res) 

Per chiarezza, specializzato per le liste e senza recursion-schemes:

para :: (a -> [a] -> b -> b) -> b -> [a] -> b 
para f z []  = z 
para f z (a:as) = f a as (para f z as) 

tails :: [a] -> [[a]] 
tails = para (\a as res -> (a : as) : res) [[]] 

Tuttavia, se si desidera essere più generale, la meno bello para formulazione viene portata di mano:

import qualified Data.Functor.Foldable as Rec (Foldable) 

tails :: (Rec.Foldable t, Base t ~ Prim [a]) => t -> [t] 
tails as = as : para (\case Nil -> []; Cons a (as, res) -> as : res) as 

Questo funziona per gli elenchi come al solito, ma si può anche dare type instance Base t = Prim [a] per altri t -s, insieme all'istanza Foldable, e usarli pure.

In alternativa, possiamo mantenere il primo tails definizione a costo di introdurre un vincolo :

tails' :: (Unfoldable t, Rec.Foldable t, Base t ~ Prim [a]) => t -> [t] 
tails' = para (\case Nil -> [embed Nil]; Cons a (as, res) -> embed (Cons a as) : res) 

Questo non è troppo male, dal momento che per ogni project ci dovrebbe essere un inverso embed per punti fissi di funtori in ogni caso, quindi le istanze Foldable e vengono naturalmente in coppia.

+0

ho bisogno di una versione in termini di ricorsione-sistemi perché il mio caso reale non è 'List' ma una struttura in stile schemi di ricorsione. AIUI gli schemi di ricorsione 'para' è' para :: Foldable t => (Base t (t, a) -> a) -> t -> a' che non ha il parametro 'b' dal tuo esempio. Puoi dare una versione in termini di questo 'para' (o altro codice di schemi di ricorsione) che è' tails' se lo chiamiamo con 'tfa = Maybe (a, f)' (nel senso che 'Base t' è quindi isomorfo a 'List', a meno che non mi sbagli) ma possa anche essere chiamato con altri' t'? – lmm

+0

@lmm Vedi modifica; Spero che questo sia ciò che intendevi. –

+0

'Prim' sembra essere una cosa specifica di List', mentre io voglio una versione che funzioni per strutture di dati non-list, e non penso che necessariamente potrei sempre definire un' Base t ~ Prim [a] ' ? La funzione a cui sto pensando non dovrebbe richiedere ulteriori vincoli: ho aggiornato la domanda con un'implementazione che ritengo sarebbe valida (ma che mi aspetterei sia una funzione standard, o almeno più concisa della mia versione). – lmm

1

para è davvero la funzione giusta da utilizzare qui. Ho messo tutto in un self-contained gist con esempi se vuoi giocare con esso.

Iniziamo con la definizione del punto di fissazione Mu e il solito fold e para.

module Tails where 

import Data.Function 

newtype Mu f = In { out :: f (Mu f) } 

fold :: Functor f => (f a -> a) -> Mu f -> a 
fold alg = alg . fmap (fold alg) . out 

para :: Functor f => (f (a, Mu f) -> a) -> Mu f -> a 
para alg = alg . fmap (\m -> (para alg m, m)). out 

Possiamo quindi implementare tails utilizzando para e un ulteriore Foldable vincolo che permetta di usare foldMap per raccogliere il risultato intermedio nella lista monoide:

tails :: (Functor f, Foldable f) => Mu f -> [Mu f] 
tails m = m : para (foldMap $ uncurry $ flip (:)) m 
+0

È questo lo stile preferito quando si usa 'para' allora? Ho pensato di fare la ricorsione in questo modo, ma non mi piace il 'm:' più di quanto mi piaccia il 'fmap fst' - in entrambi i casi mi sembra leggermente l'astrazione sbagliata. – lmm