Ответ 1
Возможно, вы ищете тип Maybe
:
lastButOne :: [a] -> Maybe a
lastButOne [x,_] = Just x
lastButOne (_:y:ys) = lastButOne (y:ys)
lastButOne _ = Nothing
Я следую "Real World Haskell", и я перехожу к главе 2, где упражнение - создать функцию "lastButOne", которая возвращает второе в последнее значение в списке. Мой исходный код:
lastButOne xs = if null (tail (tail xs))
then head xs
else lastButOne (tail xs)
Это работает отлично, если я не дам ему список только с 1 или 2 пунктами. Поэтому я изменил это на следующее:
lastButOne xs = if null xs || null (tail xs)
then head xs
else
if null (tail (tail xs))
then head xs
else lastButOne (tail xs)
Что проверяет, имеет ли он только 1 или 2 элемента, но затем выходит из строя, если у него только 1 элемент из-за вызова в голову. Моя проблема в том, что я не могу придумать ничего другого, кроме как "head xs", в идеале я хочу, чтобы он возвращал значение null или что-то в этом роде, но я не могу найти "нуль", который позволяет функции по-прежнему полиморфный.
Возможно, вы ищете тип Maybe
:
lastButOne :: [a] -> Maybe a
lastButOne [x,_] = Just x
lastButOne (_:y:ys) = lastButOne (y:ys)
lastButOne _ = Nothing
[I] честно, я хочу, чтобы он возвращал значение null или что-то в этом роде, но я не могу найти "null", который позволяет функции по-прежнему быть полиморфной.
Уже есть ряд ответов, посвященных тому, как это сделать, поэтому вместо этого позвольте мне ответить на более фундаментальный вопрос: "Почему нет" нулевого "?"
Это часть философии проверки типов в Haskell. большинство языков включают некоторую форму специального значения, которое сигнализирует об ошибке или отсутствии данных. Примеры включают 0
, -1
""
(пустая строка) JavaScripts null
или Ruby nil
. Проблема в том, что это создает целый класс потенциальных ошибок, в которых вызывающая функция имеет дело только с "хорошими" значениями, а сбой программы или ухудшает повреждение данных при возвращении специального значения. Существует также вопрос о том, что особое значение предназначено как регулярное значение, то есть 0
на самом деле является числом, а не состоянием отказа.
В вашей проблеме может быть задан вопрос: "Будет ли функция, вызывающая lastButOne
знать, что делать с null
?"
Проблема отсутствия правильного ответа для некоторых хорошо сформированных входов является очень распространенной в Haskell и имеет два типичных решения. Первое взято из Erlang: быстро и чисто. В рамках этого решения абоненту необходимо обеспечить, чтобы данные были "хорошими". Если это не программа, она умирает с ошибкой (или исключение поймано, но это сложнее в Haskell, затем на других языках). Это решение, используемое большинством функций списка, например. head
, tail
и т.д.
Другое решение состоит в том, чтобы явно указать, что функция может не создавать хорошие данные. Тип Maybe a
является самым простым из этих решений. Он позволяет вам вернуть значение Nothing
, которое работает так же, как null
, которое вы ищете. Но для этого есть затраты. Ваша подпись типа функции становится lastButOne :: [a] -> Maybe a
. Теперь каждая вызывающая функция должна принимать не a
, а Maybe a
, и прежде чем она сможет получить ответ, она должна сказать Haskell, что он собирается делать, если ответ Nothing
.
Ну, это зависит от того, сколько ошибок вы действительно хотите сделать.
Я подозреваю, что, учитывая это только в главе 2, вы, вероятно, должны игнорировать этот случай края. Сам Haskell выдает исключение:
Prelude> head []
*** Exception: Prelude.head: empty list
Альтернативой, если это случай, когда вам придется иметь дело с большим количеством, было бы изменить тип возврата lastButOne
на Maybe a
и вернуть Just (head xs)
, когда он будет работать, и Nothing
, когда он не работает. Но это означает, что вы всегда будете иметь дело с проживанием внутри Maybe
monad, что может быть не идеальным (так как вам придется "развернуть" Just
значение для использования).
Почему вы не используете сопоставление шаблонов для обработки случая, когда у вас есть только 1 или 2 элемента? Что-то вроде:
lastButOne :: [a] -> Maybe a
lastButOne [x,y] = Just x
lastButOne (x:y:t) = lastButOne (y:t)
lastButOne _ = Nothing
Примером "расширенной обработки ошибок", упомянутой @Andrew Jaffe, может быть:
{-# LANGUAGE FlexibleContexts #-} module Main where
import Control.Monad.Error
lastButOne :: MonadError String m => [a] -> m a
lastButOne (_ : y : []) = return y
lastButOne (_ : t) = lastButOne
lastButOne _ = throwError "Error in lastButOne"
В этом случае можно вернуть Either
тип: Right x
в случае успеха и Left "Error in lastButOne"
в случае сбоя, но возможны многие другие экземпляры MonadError String m
.
Также читайте 8 способов сообщить об ошибках в Haskell для получения дополнительных возможностей.