Come posso utilizzare un elenco di lunghezza minima fissa in modo totale ed elegante?


10

Attualmente ho a che fare con una funzione che va così:

foo = (\(a:b:c:d:e:f:_) -> foobar a b c d e f) . (++ repeat def)

In altre parole, dato un elenco, utilizza i primi sei elementi per qualcosa e, se l'elenco è lungo meno di sei elementi, utilizza defcome supporto per quelli mancanti. Questo è totale, ma i suoi pezzi non sono (proprio come map fromJust . filter isJust), quindi non mi piace. Ho provato a riscrivere questo in modo che non abbia bisogno di usare alcuna parzialità, e ho ottenuto questo:

foo [] = foobar def def def def def def
foo [a] = foobar a def def def def def
foo [a,b] = foobar a b def def def def
foo [a,b,c] = foobar a b c def def def
foo [a,b,c,d] = foobar a b c d def def
foo [a,b,c,d,e] = foobar a b c d e def
foo (a:b:c:d:e:f:_) = foobar a b c d e f

Tecnicamente ho fatto quello che volevo, ma ora è un casino gigantesco. Come posso farlo in un modo più elegante e meno ripetitivo?


2
Forse, scrivere un uncons :: Default a => [a] -> (a,[a])valore predefinito def. O un default takeWithDef. E / o un sinonimo di pattern / pattern di visualizzazione. Ciò richiede tuttavia la scrittura di un codice ausiliario ausiliario.
chi,

@chi Penso che sia quello con cui andrò. Se gli dai una risposta, la accetterò.
Joseph Sible: ripristina Monica il

2
Per quello che vale, penso che l'argomento della totalità case xs ++ repeat def of a:b:c:d:e:f:_ -> ...sia abbastanza locale da non pensarci due volte sul solo usarlo e saltare tutti i macchinari extra che le risposte esistenti stanno introducendo. Sono in genere gli argomenti di totalità più globali (che coinvolgono invarianti mantenuti attraverso molteplici chiamate di funzioni, ad esempio) che mi rendono nervoso.
Daniel Wagner,

In realtà takeWithDefnon è utilizzabile se restituisce un elenco regolare, poiché dobbiamo modellare la corrispondenza in questo modo: - / La soluzione corretta è quella che Daniel ha scritto di seguito nella sua seconda risposta. unconsottiene solo il primo elemento, quindi non è così utile.
chi,

Risposte:


8

Utilizzando il pacchetto sicuro , è possibile scrivere, ad esempio:

(!) = atDef def
foo xs = foobar (xs ! 0) (xs ! 1) (xs ! 2) (xs ! 3) (xs ! 4) (xs ! 5)

6

Questo è almeno più breve:

foo (a:b:c:d:e:f:_) = foobar a b c d e f
foo xs = foo (xs ++ repeat def)

Puoi facilmente vedere che gli schemi sono esaustivi, ma ora devi pensare un po 'per vedere che termina sempre. Quindi non so se puoi considerarlo un miglioramento.

Altrimenti possiamo farlo con la monade di stato, anche se è un po 'pesante:

foo = evalState (foobar <$> pop <*> pop <*> pop <*> pop <*> pop <*> pop)
  where
    pop = do xs <- get
             case xs of [] -> pure def
                        y:ys -> put ys >> pure y

Potrei anche immaginare di usare un tipo di flusso infinito come

data S a = S a (S a)

perché allora si potrebbe costruire foofuori repeat :: a -> S a, prepend :: [a] -> S a -> S ae take6 :: S a -> (a,a,a,a,a,a), ognuno dei quali potrebbe essere totale. Probabilmente non ne vale la pena se non hai già un tale tipo a portata di mano.


3
Oh, mi piace molto l'idea del flusso. Con un costruttore infix come data S a = a :- S a; infixr 5 :-sembra abbastanza pulito; foo xs = case prepend xs (repeat def) of a:-b:-c:-d:-e:-f:-_ -> foobar a b c d e f.
Daniel Wagner,

4

Solo per divertimento (e non raccomandato, questo è per i divertimenti), ecco un altro modo:

import Data.Default

data Cons f a = a :- f a
infixr 5 :-

data Nil a = Nil -- or use Proxy

class TakeDef f where takeDef :: Default a => [a] -> f a
instance TakeDef Nil where takeDef _ = Nil
instance TakeDef f => TakeDef (Cons f) where
    takeDef (x:xs) = x :- takeDef xs
    takeDef xs = def :- takeDef xs

foo xs = case takeDef xs of
    a:-b:-c:-d:-e:-f:-Nil -> foobar a b c d e f

Il tipo che usi nella corrispondenza del modello equivale a passare un livello naturale di tipo a takeDefdire quanti elementi guardare.


1
Questo è il mio approccio preferito finora. Probabilmente userei un modello di vista per completarlo. (Perché il "non raccomandato"? Quali sono i contro?)
chi

3
Incarna esattamente ciò che inizia ad andare storto quando investi pesantemente nella programmazione a livello di tipo: quello che era un programma a una riga, immediatamente comprensibile, si articola in dieci righe che richiedono al lettore di impegnarsi seriamente nel loro motore di inferenza del tipo mentale.
Daniel Wagner,

1
Vedo il tuo punto. Conto foo (takeDef -> a:-b:-c:-d:-e:-f:-Nil) -> foobar a b c d e fcome una riga. Non conto il resto poiché è il codice che dovrebbe essere in alcune librerie, per essere riutilizzato. Se deve essere scritto solo per questo caso, è chiaramente eccessivo come dici tu.
chi,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.