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

10

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

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

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

Джозеф Сибл-Восстановить Монику
источник
2
Может быть, напишите, uncons :: Default a => [a] -> (a,[a])который по умолчанию def. Или дефолт takeWithDef. И / или шаблон просмотра / синоним шаблона. Это требует написания некоторого вспомогательного вспомогательного кода.
Чи
@ Чи я думаю, что это то, что я пойду с. Если вы сделаете ответ, я приму это.
Джозеф Сибл-Восстановить Монику
2
Что бы это ни стоило, я думаю, что аргумент совокупности case xs ++ repeat def of a:b:c:d:e:f:_ -> ...достаточно локальный, чтобы я не задумывался о том, чтобы просто использовать его и пропустить все дополнительные механизмы, которые вводят существующие ответы. Обычно меня раздражают более глобальные аргументы совокупности (которые включают в себя инварианты, поддерживаемые, например, в нескольких вызовах функций).
Даниэль Вагнер
На самом деле takeWithDefне может использоваться, если он возвращает обычный список, так как нам нужно сопоставить с шаблоном, что: - / Правильное решение - то, что Даниэль написал ниже во втором ответе. unconsтолько получает первый элемент, так что это не так полезно.
Чи

Ответы:

8

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

(!) = atDef def
foo xs = foobar (xs ! 0) (xs ! 1) (xs ! 2) (xs ! 3) (xs ! 4) (xs ! 5)
Даниэль Вагнер
источник
6

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

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), все из которых может быть полным. Вероятно, не стоит, если у вас нет такого типа под рукой.

Дэвид Флетчер
источник
3
О, мне очень нравится идея стрима. С инфиксным конструктором data S a = a :- S a; infixr 5 :-это выглядит довольно чисто; foo xs = case prepend xs (repeat def) of a:-b:-c:-d:-e:-f:-_ -> foobar a b c d e f,
Даниэль Вагнер
4

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

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количества элементов, на которые нужно посмотреть.

Даниэль Вагнер
источник
1
Пока это мой предпочтительный подход. Я бы, вероятно, использовал шаблон представления, чтобы дополнить его. (Почему «не рекомендуется»? Какие минусы?)
Чи
3
Он воплощает в себе именно то, что начинает идти не так, когда вы вкладываете значительные средства в программирование на уровне типов: то, что представляло собой однострочную, мгновенно понятную программу, разветвляется на десять строк, которые требуют от читателя всерьез задействовать свой механизм умственного вывода типа.
Даниэль Вагнер
1
Я понимаю вашу точку зрения. Я считаю foo (takeDef -> a:-b:-c:-d:-e:-f:-Nil) -> foobar a b c d e fодной строкой. Остальное я не считаю, так как это код, который должен быть в какой-то библиотеке для повторного использования. Если он должен быть написан только для этого случая, это явно излишне, как вы говорите.
Чи