Подтвердить что ты не робот

Реализация нулевой функции

Мне нужно научиться Haskell для университета, и поэтому я использую learnyouahaskell.com для начала.
Я всегда использовал императивные языки, поэтому решил заняться Haskell, кодируя намного больше, чем для других языков. < ш > Я начал реализовывать несколько функций для работы со списками, такими как head, tail, init,...
В какой-то момент я просмотрел реализации этих функций для сравнения с моими, и я наткнулся на нулевую функцию, определенную в List.lhs.

null:

-- | Test whether a list is empty.
null                    :: [a] -> Bool
null []                 =  True
null (_:_)              =  False

моя реализация:

mNull :: [a] -> Bool
mNull []        = True
mNull _         = False

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

4b9b3361

Ответ 1

Вы не можете просто повернуть предложения с помощью своего решения:

mNull' :: [a] -> Bool
mNull' _  = False
mNull' [] = True

всегда будет давать False, даже если вы пройдете пустой список. Поскольку среда выполнения никогда не рассматривает предложение [], она сразу видит, что _ соответствует чему-либо. (GHC предупредит вас о таком перекрывающемся шаблоне.)

С другой стороны,

null' :: [a] -> Bool
null' (_:_) = False
null' []    = True

работает корректно, потому что (_:_) не соответствует конкретному случаю пустого списка.

Это само по себе не дает двух явных предложений преимущество. Однако в более сложном коде, выписывая все варианты взаимного исключения, есть одно преимущество: если вы забыли один из вариантов, компилятор также может предупредить вас об этом! В то время как _ может и будет обрабатывать любой случай, не рассматриваемый предыдущими предложениями, даже если это не совсем правильно.

Ответ 2

Потому что _ буквально означает что-либо отдельно от явно определенных шаблонов. Когда вы указываете (_:_), это означает все, что может быть представлено в виде списка, содержащего как минимум 1 элемент, не беспокоясь о том, что или даже сколько элементов содержит список. Поскольку случай с явным шаблоном для пустого списка уже присутствует, (_:_) также может быть заменен на _.

Однако, представляя его как (_:_), вы получаете гибкость, чтобы даже явно не пропускать пустой шаблон списка. На самом деле это будет работать:

-- | Test whether a list is empty.
null                    :: [a] -> Bool    
null (_:_)              =  False
null _                  =  True

Демо

Ответ 3

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

data FavoriteFood = Pizza
                  | SesameSpinachPancake
                  | ChanaMasala

а затем вы научитесь нравиться DrunkenNoodles и добавьте его в список, все ваши функции, соответствующие шаблону, будут расширены. Если вы использовали какие-либо шаблоны _, вам нужно будет найти их вручную; компилятор не может вам помочь. Если вы четко определили каждую вещь, вы можете включить -fwarn-incomplete-patterns, и GHC расскажет вам о каждом пропущенном вами месте.