Рекомендуется ли всегда иметь исчерпывающие совпадения с образцами в Haskell, даже для «невозможных» случаев? - PullRequest
17 голосов
/ 07 мая 2009

Рекомендуется ли всегда иметь исчерпывающие совпадения с образцами в Haskell, даже для "невозможных" случаев?

Например, в следующем коде я сопоставляю шаблон с «аккумулятором» фолдера. Я полностью контролирую содержимое аккумулятора, потому что я его создаю (он не передается мне как вход, а скорее встроен в мою функцию). Поэтому я знаю, что определенные шаблоны никогда не должны соответствовать этому. Если бы я старался никогда не получить ошибку «Сопоставление с образцом (-ями) не является исчерпывающим»), я бы поместил сопоставление с шаблоном для него, а это просто ошибка с сообщением «Этот шаблон никогда не должен происходить». Очень похоже на утверждение в C #. Я не могу думать ни о чем другом, чтобы сделать там.

Какую практику вы бы порекомендовали в этой ситуации и почему?

Вот код:

gb_groupBy p input = foldr step [] input
   where
      step item acc = case acc of
           []                           -> [[item]]
           ((x:xs):ys)                  -> if p x item
                                           then (item:x:xs):ys
                                           else [item]:acc

Шаблон не соответствует (как сообщает переводчик):

Предупреждение: сопоставление с образцом не является исчерпывающим В случае альтернативы: Шаблоны не совпадают: []: _

Ответы [ 6 ]

20 голосов
/ 07 мая 2009

Это, наверное, больше вопрос стиля, чем что-либо еще. Лично я бы положил в

_ -> error "Impossible! Empty list in step"

лишь бы заглушить предупреждение:)

11 голосов
/ 07 мая 2009

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

gb_groupBy p input = foldr step [] input
   where
     step item acc = case acc of
        []                           -> [[item]]
        (xs:xss)                      -> if p (head xs) item
                                         then  (item:xs):xss
                                         else [item]:acc

Тогда сопоставление с образцом будет завершено, и условие «невозможно» для пустого списка в начале аккумулятора вызовет ошибку времени выполнения, но не выдаст предупреждение.

Еще один способ взглянуть на более общую проблему неполных сопоставлений с образцами - это увидеть их как «запах кода», то есть указание на то, что мы пытаемся решить проблему неоптимальным, или не-Хаскельским способом и попробуйте переписать наши функции.

Реализация groupBy с помощью Foldr делает невозможным применение его к бесконечному списку, что является целью проектирования, которую функции Haskell List пытаются достичь везде, где это семантически целесообразно. Рассмотрим

take 5 $ groupBy (==) someFunctionDerivingAnInfiniteList

Если первые 5 групп ж.р. равенство конечно, ленивая оценка закончится. Это то, что вы не можете сделать на строго оцененном языке. Даже если вы не работаете с бесконечными списками, написание таких функций приведет к повышению производительности в длинных списках или позволит избежать переполнения стека, возникающего при оценке выражений типа

take 5 $ gb_groupBy (==) [1..1000000]

В List.hs groupBy реализован так:

groupBy         :: (a -> a -> Bool) -> [a] -> [[a]]
groupBy _  []       =  []
groupBy eq (x:xs)   =  (x:ys) : groupBy eq zs
                           where (ys,zs) = span (eq x) xs

Это позволяет интерпретатору / компилятору оценивать только те части вычислений, которые необходимы для результата. span дает пару списков, где первый состоит из (последовательных) элементов из заголовка списка, удовлетворяющих предикату, а второй - остальной части списка. Он также реализован для работы с бесконечными списками.

9 голосов
/ 08 мая 2009

Я считаю, что обязательна проверка исчерпывающих данных по шаблонам случаев. Я стараюсь никогда не использовать _ в случае на верхнем уровне, потому что _ соответствует всему, и используя его, вы теряете ценность проверки исчерпываемости. Это менее важно для списков, но критически важно для пользовательских алгебраических типов данных, потому что я хочу иметь возможность добавить новый конструктор и иметь запрет компилятора во всех пропущенных случаях. По этой причине я всегда компилирую с включенным -Werror, поэтому я не могу пропустить кейс.

Как уже отмечалось, ваш код может быть расширен в этом случае

[] : _ -> error "this can't happen"

Внутренне, GHC имеет функцию panic, которая в отличие от error будет давать исходные координаты, но я посмотрел на реализацию и не смог сделать ни голову, ни хвост.

4 голосов
/ 07 мая 2009

Чтобы прокомментировать мой предыдущий комментарий, я понял, что есть способ подтвердить пропущенный случай, но все же получить полезную ошибку с номером файла / строки. Однако он не идеален, так как будет появляться только в неоптимизированных сборках (см. здесь ).

...
[]:xs -> assert False (error "unreachable because I know everything")
2 голосов
/ 27 января 2010

Система типов - ваш друг, и предупреждение дает вам понять, что в вашей функции есть трещины. Самый лучший подход заключается в более чистом и элегантном сочетании между типами.

Рассмотрим определение ghc groupBy:

groupBy                 :: (a -> a -> Bool) -> [a] -> [[a]]
groupBy _  []           =  []
groupBy eq (x:xs)       =  (x:ys) : groupBy eq zs
                           where (ys,zs) = span (eq x) xs
1 голос
/ 22 апреля 2014

Моя точка зрения такова, что невозможный случай - undefined .
Если он не определен, у нас есть функция для него: хитро названный undefined.

Завершите сопоставление с такими:

_ -> undefined

И вот оно у вас!

...