На самом деле это обычный конструктор данных, который определяется в 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
только один раз, когда вы закончите.
Может быть, как монада
Я еще не уверен, насколько вы знакомы с концепцией a Monad
, но вы, по крайней мере, использовали его IO a
раньше, и подпись типа IO a
выглядит удивительно похожей на Maybe a
. Несмотря IO
на то, что он особенный в том, что он не предоставляет вам свои конструкторы и, таким образом, может «запускаться» только системой времени выполнения Haskell, он по-прежнему является не Functor
только файлом Monad
. На самом деле, есть важное значение, в котором a Monad
- это просто особый видFunctor
с некоторыми дополнительными функциями, но здесь не место для этого.
В любом случае, монады любят IO
сопоставлять типы с новыми типами, которые представляют «вычисления, которые приводят к значениям», и вы можете преобразовывать функции в Monad
типы с помощью очень fmap
похожей функции, называемой liftM
которая превращает обычную функцию в «вычисление, которое приводит к значению, полученному путем оценки функция «.
Вы, наверное, догадались (если дочитали до этого места), что Maybe
это тоже Monad
. Он представляет собой «вычисления, которые могут не вернуть значение». Как и в fmap
примере, это позволяет вам выполнять целый ряд вычислений без необходимости явно проверять наличие ошибок после каждого шага. Фактически, при построении Monad
экземпляра вычисление Maybe
значений останавливается, как только Nothing
встречается a , так что это похоже на немедленное прерывание или возврат без значения в середине вычисления.
Вы могли бы написать "Может быть"
Как я уже говорил ранее, Maybe
типу не присуще ничего, что встроено в синтаксис языка или систему времени выполнения. Если бы Haskell не предоставлял его по умолчанию, вы могли бы предоставить все его функции самостоятельно! Фактически, вы все равно можете написать его снова, с другими именами, и получить ту же функциональность.
Надеюсь, Maybe
теперь вы понимаете этот тип и его конструкторы, но если все еще что-то неясно, дайте мне знать!
Maybe
там, где другие языки используютnull
ornil
(с неприятнымиNullPointerException
словами, скрывающимися в каждом углу). Теперь другие языки также начинают использовать эту конструкцию: Scala asOption
, и даже Java 8 будет иметь этотOptional
тип.