#haskell #functional-programming #runtime-error
Вопрос:
У меня есть следующий класс стека:
newtype Stack my_stack = Stack [my_stack] deriving Show
getSize :: Stack my_stack -> Int
getSize (Stack []) = 0
getSize (Stack (x:xs)) = 1 getSize (Stack xs)
push :: my_stack -> Stack my_stack -> Stack my_stack
push x (Stack xs) = Stack (x:xs)
pop :: Stack my_stack -> Stack my_stack
pop (Stack (x:xs)) = Stack xs
getTop :: Stack my_stack -> my_stack
getTop (Stack (x:xs)) = x
однако, когда я пытаюсь использовать эту getTop
функцию, я получаю Non-exhaustive patterns in function getTop
ошибку. Как я могу объявить базовый регистр (который, как я полагаю, является пустым регистром?) для getTop
функции, чтобы я не получал неполных ошибок шаблона? Спасибо!
Комментарии:
1. голосование за закрытие, поскольку OP задает множество вопросов в потоках комментариев, и поэтому ухудшает будущую полезность.
2. @MichaelLitchard Если вы считаете комментарий неуместным, отметьте его. Это ни в малейшей степени не умаляет полезности самого вопроса.
3. @SilvioMayolo хорошая мысль, удаляющая голосование за закрытие.
Ответ №1:
Ваша функция сейчас небезопасна. Это предупреждение (правильно) уведомляет вас о том, что вы не обрабатываете случай с пустым стеком. Теперь вопрос в следующем: что вы хотите делать, если пользователь звонит getTop
с пустым стеком? У тебя есть несколько вариантов. Я отсортирую их примерно в порядке убывания моих предпочтений.
1. Возвращение Maybe
Если у нас есть значение, которое может существовать, а может и не существовать, идиоматический подход состоит в том, чтобы вернуться Maybe
. Maybe
это особый тип, который может содержать или не содержать значение, и вызывающий должен решить, что с ним делать. Они могут сами выбрать возврат a Maybe
, или они могут указать значение по умолчанию, или они могут просто показать пользователю ошибку. Но система типов будет обеспечивать, чтобы они справлялись с ситуацией.
getTop :: Stack my_stack -> Maybe my_stack
getTop (Stack []) = Nothing
getTop (Stack (x:xs)) = Just x
Это совершенно безопасно и дает абоненту полный контроль.
2. Заставьте пользователя указать значение по умолчанию
Вместо этого вы можете потребовать от пользователя указать нужное значение, если стек пуст.
getTop :: my_stack -> Stack my_stack -> my_stack
getTop orelse (Stack []) = orelse
getTop _ (Stack (x:xs)) = x
Это по-прежнему совершенно безопасно, как и предыдущее решение, но, на мой взгляд, это более неудобно, чем an Option
. Подпись типа Stack my_stack -> Maybe my_stack
очень четко указывает на то, что делает функция: она принимает стек и может возвращать из него значение. Подпись типа my_stack -> Stack my_stack -> my_stack
более туманна: добавляем ли мы значение в стек, возвращаем ли значение по умолчанию, что мы делаем? Кроме того, если пользователь действительно хочет a Maybe
, может не быть подходящего значения по умолчанию, которое отличалось бы от «в стеке ничего не было».
3. Выдайте ошибку
Это не идиоматический Хаскелл, и я не рекомендую делать это в производстве. Но если вы просто пишете короткий код для личной практики, он может быть полезен для взлома.
getTop :: Stack my_stack -> my_stack
getTop (Stack []) = error "Empty stack in getTop"
getTop (Stack (x:xs)) = x
error
это специальная функция, которая имеет подпись String -> a
(для любого a
). Вы предоставляете ему сообщение об ошибке, и оно… ну, это приводит к сбою вашей программы. Технически существуют способы поймать ан error
, но они неудобны и имеют множество сложных причуд, поэтому, как правило, вы должны рассматривать error
как жесткий сбой и использовать его только в том случае, если нет возможности для восстановления. Следовательно, я не рекомендую это в данном случае, так как «стек был пуст» — это очень легко восстанавливаемое состояние.
Комментарии:
1. И будет ли какое-либо простое решение, если я захочу вернуть пустую строку при получении верхней части пустого стека?
2. Поскольку ваш код написан прямо сейчас, стек может содержать любой тип данных (
my_stack
является переменной типа), а не только строки. Таким образом, нет никакой гарантии, что""
это допустимое возвращаемое значение. Однако, если вы выберете вариант 1 выше, вы можете назвать его какmaybe "" id (getTop stack)
, чтобы указать значение по умолчанию3. о, я понимаю, но теперь я уверен, что мой стек будет включать только строки, как я могу изменить его так, чтобы он был стеком строк, что облегчит гарантию «» возвращаемого значения
4. По одному вопросу за раз. Если у вас есть несколько вопросов, откройте другой вопрос.
Ответ №2:
Это заявление:
getTop (Stack (x:xs)) = x
Это означает: «когда getTop
параметром является a Stack
, который содержит список, состоящий из головы x
и хвоста xs
, результат будет x
«.
Но что, если параметр a Stack
содержит пустой список? Вы не определили, что должно произойти в этом случае, и поэтому возникает ошибка.
Так что вам нужно соответствие шаблону для этого случая:
getTop (Stack (x:xs)) = x
getTop (Stack []) = ???
Но вот вопрос: каким должен быть результат getTop
в этом случае? Только вы можете решить это, исходя из вашей конкретной ситуации. Как бы то ни было, распространенным шаблоном является возврат Maybe my_stack
вместо:
getTop :: Stack my_stack -> Maybe my_stack
getTop (Stack (x:xs)) = Just x
getTop (Stack []) = Nothing
Комментарии:
1. большое вам спасибо за ваш комментарий, ну, в моем случае я понял, что наиболее подходящим поведением было бы получить пустую строку, но
getStackPeek (Stack []) = ""
выдает ошибку, что я должен поместить справа, чтобы это не приводило к ошибке, и что я получу пустую строку2. Тип
my_stack
не обязательно является строкой. Если вы хотите вернуть строку, то типgetTop
должен бытьStack String -> String