Реализация нулевой функции
Мне нужно научиться 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
Я знаю, что нет глупых вопросов даже для таких простых вопросов:)
Поэтому мой вопрос заключается в том, почему в исходной реализации вместо _
используется (_:_)
?
Есть ли какое-либо преимущество в использовании (_:_)
или есть ли какие-либо краевые случаи, о которых я не знаю?
Я не могу себе представить никаких преимуществ, потому что _
ловит все.
Ответы
Ответ 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 расскажет вам о каждом пропущенном вами месте.