Jak mogę korzystać z listy o stałej minimalnej długości w całkowity i elegancki sposób?

10

Obecnie mam do czynienia z funkcją, która wygląda następująco:

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

Innymi słowy, biorąc pod uwagę listę, używa pierwszych sześciu elementów do czegoś, a jeśli lista ma mniej niż sześć elementów, używa defjako stand-in dla brakujących. To jest suma, ale kawałki nie są (tak jak map fromJust . filter isJust), więc nie lubię tego. Próbowałem przepisać to, aby nie trzeba było używać żadnej stronniczości, i otrzymałem to:

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

Technicznie zrobiłem to, co chciałem, ale teraz jest to gigantyczny bałagan. Jak mogę to zrobić w bardziej elegancki i mniej powtarzalny sposób?

Joseph Sible-Reinstate Monica
źródło
2
Może napisz uncons :: Default a => [a] -> (a,[a])domyślną wartość def. Lub domyślnie takeWithDef. I / lub synonim wzorca widoku / wzorca. Wymaga to jednak napisania pomocniczego kodu pomocnika.
chi
@chi Myślę, że z tym właśnie pójdę. Jeśli udzielisz odpowiedzi, zaakceptuję ją.
Joseph Sible-Reinstate Monica
2
Jeśli chodzi o to, co warto, myślę, że argument całościowy case xs ++ repeat def of a:b:c:d:e:f:_ -> ...jest na tyle lokalny, że nie zastanawiałbym się dwa razy po prostu o używaniu go i pomijaniu wszystkich dodatkowych mechanizmów, które wprowadzają istniejące odpowiedzi. Na ogół denerwują mnie bardziej globalne argumenty totalności (które obejmują niezmienniki utrzymywane w wielu wywołaniach funkcji, np.).
Daniel Wagner
W rzeczywistości takeWithDefnie nadaje się do użycia, jeśli zwraca regularną listę, ponieważ musimy dopasować wzór, że: - / Właściwym rozwiązaniem jest to, co Daniel napisał poniżej w swojej drugiej odpowiedzi. unconsdostaje tylko pierwszy element, więc nie jest to tak przydatne.
chi

Odpowiedzi:

8

Korzystając z bezpiecznego pakietu, możesz na przykład napisać:

(!) = atDef def
foo xs = foobar (xs ! 0) (xs ! 1) (xs ! 2) (xs ! 3) (xs ! 4) (xs ! 5)
Daniel Wagner
źródło
6

Jest to co najmniej krótszy:

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

Możesz łatwo zobaczyć, że wzorce są wyczerpujące, ale teraz musisz pomyśleć trochę, aby zobaczyć, że zawsze się kończy. Więc nie wiem, czy możesz to uznać za poprawę.

W przeciwnym razie możemy to zrobić za pomocą monady państwowej, chociaż jest to nieco ciężkie:

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

Mógłbym również wyobrazić sobie użycie nieskończonego typu strumienia jak

data S a = S a (S a)

bo wtedy można skonstruować fooz repeat :: a -> S a, prepend :: [a] -> S a -> S ai take6 :: S a -> (a,a,a,a,a,a), z których każdy może być całkowite. Prawdopodobnie nie warto, jeśli nie masz już takiego typu pod ręką.

David Fletcher
źródło
3
Och, bardzo podoba mi się pomysł na stream. Dzięki konstruktorowi poprawek data S a = a :- S a; infixr 5 :-wygląda to całkiem czysto; foo xs = case prepend xs (repeat def) of a:-b:-c:-d:-e:-f:-_ -> foobar a b c d e f.
Daniel Wagner,
4

Dla zabawy (i niezalecane, to dla miłośników gier), oto inny sposób:

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

Typ użyty w dopasowaniu wzorca sprowadza się do przekazania naturalnego poziomu typu do takeDefpowiedzenia, ile elementów należy obejrzeć.

Daniel Wagner
źródło
1
To moje dotychczasowe preferowane podejście. Prawdopodobnie użyłbym wzorca widoku, aby go uzupełnić. (Dlaczego „niezalecane”? Jakie są wady?)
chi
3
Uosabia dokładnie to, co zaczyna się nie tak, gdy inwestujesz dużo w programowanie na poziomie typu: co to jest jednowierszowy, natychmiast zrozumiały program, który rozchodzi się w dziesięciu liniach, które wymagają od czytelnika poważnego włączenia silnika mentalnego wnioskowania o typie.
Daniel Wagner
1
Rozumiem co masz na myśli. Liczę foo (takeDef -> a:-b:-c:-d:-e:-f:-Nil) -> foobar a b c d e fjako jedną linię. Reszty nie liczę, ponieważ jest to kod, który powinien znajdować się w bibliotece do ponownego użycia. Jeśli trzeba to napisać tylko w tym przypadku, to, jak mówisz, wyraźnie przesada.
chi