Wie kann ich eine Liste mit fester Mindestlänge vollständig und elegant verwenden?

10

Ich beschäftige mich derzeit mit einer Funktion, die so aussieht:

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

Mit anderen Worten, bei einer gegebenen Liste werden die ersten sechs Elemente für etwas verwendet, und wenn die Liste weniger als sechs Elemente lang ist, wird sie defals Ersatz für die fehlenden Elemente verwendet . Das ist total, aber die Teile davon sind nicht (genau wie map fromJust . filter isJust), also mag ich es nicht. Ich habe versucht, dies so umzuschreiben, dass keine Parteilichkeit erforderlich ist, und habe Folgendes erhalten:

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

Ich habe technisch getan, was ich will, aber jetzt ist das ein gigantisches Durcheinander. Wie kann ich dies eleganter und weniger repetitiv tun?

Joseph Sible-Reinstate Monica
quelle
2
Vielleicht schreiben Sie eine, uncons :: Default a => [a] -> (a,[a])die standardmäßig ist def. Oder eine Standardeinstellung takeWithDef. Und / oder ein Ansichtsmuster / Mustersynonym. Dies erfordert jedoch das Schreiben eines zusätzlichen Hilfecodes.
Chi
@chi Ich denke, das ist es, was ich machen werde. Wenn Sie eine Antwort geben, werde ich sie akzeptieren.
Joseph Sible-Reinstate Monica
2
Für das, was es wert ist, denke ich, dass das Gesamtargument für case xs ++ repeat def of a:b:c:d:e:f:_ -> ...lokal genug ist, dass ich nicht zweimal darüber nachdenken würde, es einfach zu verwenden und alle zusätzlichen Maschinen zu überspringen, die die vorhandenen Antworten einführen. Es sind im Allgemeinen die globaleren Totalitätsargumente (die Invarianten beinhalten, die über mehrere Funktionsaufrufe hinweg beibehalten werden, z. B.), die mich nervös machen.
Daniel Wagner
Eigentlich takeWithDefist es nicht verwendbar, wenn es eine reguläre Liste zurückgibt, da wir das Muster übereinstimmen müssen: - / Die richtige Lösung ist das, was Daniel unten in seiner zweiten Antwort geschrieben hat. unconsbekommt nur das erste Element, also ist es nicht so nützlich.
Chi

Antworten:

8

Mit dem sicheren Paket können Sie beispielsweise schreiben:

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

Das ist zumindest kürzer:

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

Sie können leicht erkennen, dass die Muster vollständig sind, aber jetzt müssen Sie ein wenig nachdenken, um zu sehen, dass sie immer enden. Ich weiß also nicht, ob Sie es als Verbesserung betrachten können.

Ansonsten können wir es mit der Staatsmonade machen, obwohl es ein bisschen schwer ist:

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

Ich könnte mir auch vorstellen, einen unendlichen Stream-Typ wie zu verwenden

data S a = S a (S a)

denn dann könnten Sie bauen foovon aus repeat :: a -> S a, prepend :: [a] -> S a -> S aund take6 :: S a -> (a,a,a,a,a,a)konnten alle davon insgesamt sein. Wahrscheinlich nicht wert, wenn Sie einen solchen Typ noch nicht zur Hand haben.

David Fletcher
quelle
3
Oh, ich mag die Stream-Idee sehr. Mit einem Infix-Konstruktor data S a = a :- S a; infixr 5 :-sieht es ziemlich sauber aus; foo xs = case prepend xs (repeat def) of a:-b:-c:-d:-e:-f:-_ -> foobar a b c d e f.
Daniel Wagner
4

Nur zum Spaß (und nicht empfohlen, dies ist zum Spaß), hier ist ein anderer Weg:

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

Der Typ, den Sie in der Musterübereinstimmung verwenden, entspricht der Übergabe einer natürlichen takeDefTextebene, um anzugeben, wie viele Elemente angezeigt werden sollen.

Daniel Wagner
quelle
1
Dies ist mein bisher bevorzugter Ansatz. Ich würde wahrscheinlich ein Ansichtsmuster verwenden, um es zu ergänzen. (Warum die "nicht empfohlen"? Was sind die Nachteile?)
Chi
3
Es verkörpert genau das, was schief geht, wenn Sie stark in die Programmierung auf Typebene investieren: Was ein einzeiliges, sofort verständliches Programm war, wird in zehn Zeilen aufgeteilt, in denen der Leser seine mentale Typ-Inferenz-Engine ernsthaft einsetzen muss.
Daniel Wagner
1
Ich weiß, worauf du hinauswillst. Ich zähle foo (takeDef -> a:-b:-c:-d:-e:-f:-Nil) -> foobar a b c d e fals eine Zeile. Ich zähle den Rest nicht, da es sich um Code handelt, der zur Wiederverwendung in einer Bibliothek vorhanden sein sollte. Wenn es nur für diesen Fall geschrieben werden muss, ist es eindeutig übertrieben, wie Sie sagen.
Chi