Ответ 1
На самом деле это обычный конструктор данных, который определен в Prelude, которая является стандартной библиотекой, которая автоматически импортируется в каждый модуль.
Что может быть, Структурно
Определение выглядит примерно так:
data Maybe a = Just a
| Nothing
Это объявление определяет тип, Maybe a
, который параметризуется переменной типа a
, что означает, что вы можете использовать его с любым типом вместо a
.
Строительство и Разрушение
Тип имеет два конструктора, Just a
и Nothing
. Когда тип имеет несколько конструкторов, это означает, что значение типа должно быть создано только с одним из возможных конструкторов. Для этого типа значение было создано с помощью Just
или Nothing
, других (без ошибок) возможностей нет.
Так как Nothing
не имеет типа параметра, при использовании в качестве конструктора он называет постоянное значение, являющееся членом типа Maybe a
для всех типов a
. Но конструктор Just
имеет параметр типа, что означает, что при использовании в качестве конструктора он действует как функция из типа a
в Maybe a
, то есть имеет тип a → Maybe a
Итак, конструкторы типа создают значение этого типа; другая сторона - когда вы хотите использовать это значение, и именно здесь вступает в игру сопоставление с образцом. В отличие от функций, конструкторы могут использоваться в выражениях привязки к шаблону, и таким образом вы можете анализировать значения, принадлежащие типам с более чем одним конструктором.
Чтобы использовать значение Maybe a
в сопоставлении с образцом, необходимо предоставить шаблон для каждого конструктора, например, так:
case maybeVal of
Nothing -> "There is nothing!"
Just val -> "There is a value, and it is " ++ (show val)
В этом случае выражение будет соответствовать первому шаблону, если значением является Nothing
, а второй будет соответствовать, если значение было создано с помощью Just
. Если второй совпадает, он также связывает имя val
с параметром, который был передан конструктору Just
когда было создано значение, с которым вы сравниваете.
Что может быть значит
Может быть, вы уже были знакомы с тем, как это работает; на самом деле не существует никакого волшебства для значений Maybe
, это просто обычный алгебраический тип данных Haskell (ADT). Но он использовался совсем немного, потому что он эффективно "поднимает" или расширяет тип, такой как Integer
из вашего примера, в новый контекст, в котором у него есть дополнительное значение (Nothing
), которое представляет отсутствие значения! Затем система типов требует, чтобы вы проверили это дополнительное значение, прежде чем оно позволит вам получить Integer
которое может быть там. Это предотвращает значительное количество ошибок.
Многие языки сегодня обрабатывают этот тип значения "без значения" через NULL-ссылки. Тони Хоар, выдающийся компьютерный ученый (он изобрел Quicksort и является победителем премии Тьюринга), признает это своей "ошибкой в миллиард долларов". Тип Maybe не единственный способ исправить это, но он оказался эффективным способом сделать это.
Может быть, как Функтор
Идея преобразования одного типа в другой с тем, чтобы операции над старым типом также могли быть преобразованы для работы с новым типом, является концепцией класса типов Haskell, называемого Functor
, который, Maybe a
имеет полезный экземпляр.
Functor
предоставляет метод, называемый fmap
, который отображает функции, которые варьируются по значениям от базового типа (например, Integer
), к функциям, которые располагаются по значениям из поднятого типа (например, Maybe Integer
). Функция, преобразованная с помощью fmap
для работы со значением Maybe
работает следующим образом:
case maybeVal of
Nothing -> Nothing -- there is nothing, so just return Nothing
Just val -> Just (f val) -- there is a value, so apply the function to it
Поэтому, если у вас есть значение Maybe Integer
m_x
и функция Int → Int
f
, вы можете выполнить fmap f m_x
чтобы применить функцию f
непосредственно к Maybe Integer
не беспокоясь о том, получило ли оно значение или нет. Фактически, вы можете применить целую цепочку поднятых функций Integer → Integer
к значениям Maybe Integer
и вам придется беспокоиться только о явной проверке Nothing
один раз, когда вы закончите.
Может быть, как монада
Я не уверен, насколько вы знакомы с концепцией Monad
, но вы по крайней мере уже использовали IO a
, и сигнатура типа IO a
выглядит удивительно похожей на Maybe a
. Хотя IO
является особенным в том IO
, что он не предоставляет вам своих конструкторов и, таким образом, может быть "запущен" только системой времени исполнения Haskell, он все еще также является Functor
в дополнение к Monad
. На самом деле, есть важный смысл, в котором Monad
- это просто особый вид Functor
с некоторыми дополнительными функциями, но это не то место, где можно в это разобраться.
В любом случае, монады, подобные IO
отображают типы на новые типы, которые представляют "вычисления, которые приводят к значениям", и вы можете поднимать функции в типы Monad
помощью самой функции fmap
-like, называемой liftM
которая превращает обычную функцию в "вычисление, которое приводит к значение, полученное путем оценки функции. "
Вы, наверное, догадались (если вы читали это далеко), что Maybe
это также Monad
. Он представляет "вычисления, которые могут не вернуть значение". Как и в примере с fmap
, это позволяет вам выполнять целую кучу вычислений без необходимости явно проверять наличие ошибок после каждого шага. И на самом деле, как Monad
экземпляр Monad
, вычисление значений Maybe
прекращается, как только встречается Nothing
, так что это вроде немедленного прерывания или бесполезного возврата в середине вычисления.
Вы могли бы написать, может быть,
Как я уже говорил, нет ничего присущего типу Maybe
который встроен в синтаксис языка или систему времени исполнения. Если Haskell не предоставил его по умолчанию, вы можете предоставить все его функции самостоятельно! На самом деле, вы могли бы все равно написать это сами, с разными именами, и получить ту же функциональность.
Надеюсь, теперь вы понимаете тип Maybe
и его конструкторы, но если что-то неясно, дайте мне знать!