Comment puis-je utiliser une liste de longueur minimale fixe de manière totale et élégante?

10

Je traite actuellement une fonction qui va comme ceci:

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

En d'autres termes, étant donné une liste, il utilise les six premiers éléments pour quelque chose, et si la liste est inférieure à six éléments, il utilise defcomme remplaçant les éléments manquants. C'est total, mais les morceaux ne le sont pas (tout comme map fromJust . filter isJust), donc je n'aime pas ça. J'ai essayé de réécrire ceci afin qu'il n'ait pas besoin d'utiliser de partialité, et j'ai obtenu ceci:

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

Techniquement, j'ai fait ce que je voulais, mais maintenant c'est un gigantesque bordel. Comment puis-je le faire de manière plus élégante et moins répétitive?

Joseph Sible-Reinstate Monica
la source
2
Peut-être, écrivez un par uncons :: Default a => [a] -> (a,[a])défaut def. Ou un défaut takeWithDef. Et / ou un motif de vue / synonyme de motif. Cela nécessite cependant d'écrire du code auxiliaire.
chi
@chi je pense que c'est ce que j'irai avec. Si vous en faites une réponse, je l'accepterai.
Joseph Sible-Reinstate Monica
2
Pour ce que ça vaut, je pense que l'argument de la totalité case xs ++ repeat def of a:b:c:d:e:f:_ -> ...est suffisamment local pour que je ne pense pas à deux fois à simplement l'utiliser et à sauter toutes les machines supplémentaires que les réponses existantes introduisent. Ce sont généralement les arguments de totalité plus globaux (qui impliquent des invariants maintenus sur plusieurs appels de fonction, par exemple) qui me rendent nerveux.
Daniel Wagner
En fait, il takeWithDefn'est pas utilisable s'il renvoie une liste régulière, car nous devons faire correspondre ce modèle: - / La bonne solution est ce que Daniel a écrit ci-dessous dans sa deuxième réponse. unconsobtient seulement le premier élément, donc ce n'est pas très utile.
chi

Réponses:

8

En utilisant le package sécurisé , vous pouvez écrire, par exemple:

(!) = atDef def
foo xs = foobar (xs ! 0) (xs ! 1) (xs ! 2) (xs ! 3) (xs ! 4) (xs ! 5)
Daniel Wagner
la source
6

C'est au moins plus court:

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

Vous pouvez facilement voir que les modèles sont exhaustifs, mais maintenant vous devez réfléchir un peu pour voir qu'il se termine toujours. Je ne sais donc pas si vous pouvez considérer cela comme une amélioration.

Sinon, nous pouvons le faire avec la monade d'État, bien que ce soit un peu lourd:

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

Je pourrais aussi imaginer utiliser un type de flux infini comme

data S a = S a (S a)

parce que vous pouvez construire foosur repeat :: a -> S a, prepend :: [a] -> S a -> S aet take6 :: S a -> (a,a,a,a,a,a), tous pourrait être totale. Probablement pas la peine si vous n'avez pas déjà un tel type à portée de main.

David Fletcher
la source
3
Oh, j'aime beaucoup l'idée de diffusion. Avec un constructeur infixé, data S a = a :- S a; infixr 5 :-il semble assez propre; foo xs = case prepend xs (repeat def) of a:-b:-c:-d:-e:-f:-_ -> foobar a b c d e f.
Daniel Wagner
4

Juste pour le plaisir (et non recommandé, c'est pour les funsies), voici une autre façon:

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

Le type que vous utilisez dans la correspondance de motif revient à passer un naturel de type à takeDefdire combien d'éléments à regarder.

Daniel Wagner
la source
1
C'est mon approche préférée jusqu'à présent. J'utiliserais probablement un modèle de vue pour le compléter. (Pourquoi le "non recommandé"? Quels sont les inconvénients?)
chi
3
Il incarne exactement ce qui commence à mal tourner lorsque vous investissez massivement dans la programmation au niveau du type: ce qui était un programme d'une ligne, instantanément compréhensible, se décompose en dix lignes qui nécessitent que le lecteur engage sérieusement son moteur d'inférence de type mental.
Daniel Wagner
1
Je vois ce que tu veux dire. Je compte foo (takeDef -> a:-b:-c:-d:-e:-f:-Nil) -> foobar a b c d e fcomme une ligne. Je ne compte pas le reste car c'est du code qui devrait être dans une bibliothèque, pour être réutilisé. S'il doit être écrit uniquement pour ce cas, c'est clairement exagéré comme vous le dites.
chi