Хаскелл: Неисчерпывающие шаблоны в стеке

#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