Як я можу використовувати список фіксованої мінімальної довжини в повний та елегантний спосіб?


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. І / або синонім візерунка / схеми перегляду. Для цього потрібно написати деякий допоміжний код помічника.
чі

@chi Я думаю, що саме з цим я піду. Якщо ви зробите це на відповідь, я прийму це.
Джозеф Сібл-

2
Оскільки це варте, я вважаю, що аргумент сукупності case xs ++ repeat def of a:b:c:d:e:f:_ -> ...достатньо локальний, що я б не замислювався над тим, щоб просто використовувати його та пропустити всі додаткові механізми, які наявні відповіді. Як правило, більш загальні аргументи загальної сукупності (які включають інваріантів, що підтримуються у кількох функціональних викликах, наприклад) викликають у мене нервозність.
Даніель Вагнер

Насправді takeWithDefвін не є корисним, якщо він повертає звичайний список, оскільки нам потрібно вирівняти відповідність тому: - / Правильне рішення - це те, що Даніел написав нижче у своїй другій відповіді. unconsотримує лише перший елемент, тож це не так корисно.
чі

Відповіді:



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
Це мій переважний підхід поки що. Я, ймовірно, використовував шаблон перегляду для доповнення. (Чому "не рекомендується"? Які мінуси?)
chi

3
Він втілює саме те, що починається не так, коли ви інвестуєте значні кошти в програмування на рівні типу: те, що було однолінійним, миттєво зрозумілим, програмними кулями на десять рядків, які вимагають від читача серйозно задіяти свій розумовий механізм виводу типу.
Даніель Вагнер

1
Я бачу вашу думку. Я рахую foo (takeDef -> a:-b:-c:-d:-e:-f:-Nil) -> foobar a b c d e fяк один рядок. Я не рахую решти, оскільки це код, який має бути в якійсь бібліотеці, для повторного використання. Якщо це потрібно писати лише для цього випадку, це, очевидно, непомірно.
чі
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.