Как я могу использовать список с фиксированной минимальной длиной полным и элегантным способом? - PullRequest
9 голосов
/ 21 октября 2019

В настоящее время я имею дело с функцией, которая выглядит следующим образом:

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

Другими словами, учитывая список, он использует первые шесть элементов для чего-то, и если список меньше шестиэлементы длиной, он использует def в качестве замены для отсутствующих. Это полная сумма, но ее части нет (как и map fromJust . filter isJust), поэтому мне это не нравится. Я попытался переписать это так, чтобы он не нуждался в какой-либо пристрастности, и получил это:

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

Я технически сделал то, что хотел, но теперь это гигантский беспорядок. Как я могу сделать это более изящным и менее повторяющимся способом?

Ответы [ 3 ]

8 голосов
/ 21 октября 2019

Используя пакет safe , вы можете написать, например:

(!) = atDef def
foo xs = foobar (xs ! 0) (xs ! 1) (xs ! 2) (xs ! 3) (xs ! 4) (xs ! 5)
6 голосов
/ 21 октября 2019

Это как минимум короче:

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

Вы можете легко увидеть, что шаблоны являются исчерпывающими, но теперь вам нужно немного подумать, чтобы увидеть, что они всегда заканчиваются. Поэтому я не знаю, можете ли вы считать это улучшением.

В противном случае мы можем сделать это с помощью монады состояний, хотя она немного тяжеловесна:

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

Я мог бы также представить, используятип бесконечного потока, такой как

data S a = S a (S a)

, потому что тогда вы можете построить foo из repeat :: a -> S a, prepend :: [a] -> S a -> S a и take6 :: S a -> (a,a,a,a,a,a), все из которых могут быть полными. Вероятно, не стоит, если у вас нет такого типа под рукой.

4 голосов
/ 21 октября 2019

Просто для забавы (и не рекомендуется, это для забав), вот другой способ:

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

Тип, который вы используете в сопоставлении с образцом, равносилен передаче естественного уровня типа takeDef говоря, на сколько элементов смотреть.

...