На самом деле это обычный конструктор данных, который определяется в 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
тип.Большинство текущих ответов - это технические объяснения того, как
Just
работают друзья; Я подумал, что могу попытаться объяснить, для чего это нужно.Многие языки имеют такое значение,
null
которое можно использовать вместо реального значения, по крайней мере, для некоторых типов. Это очень рассердило многих людей и считается плохим ходом. Тем не менее, иногда полезно иметь значение, например,null
чтобы указать на отсутствие вещи.Haskell решает эту проблему, заставляя вас явно отмечать места, где у вас может быть
Nothing
(его версияnull
). По сути, если ваша функция обычно возвращает типFoo
, она должна возвращать типMaybe Foo
. Если вы хотите указать, что значения нет, вернитесьNothing
. Если вы хотите вернуть значениеbar
, вам следует вернутьсяJust bar
.В общем, если вы не можете иметь
Nothing
, вам и не нужноJust
. Если можноNothing
, значит, нужноJust
.В этом нет ничего волшебного
Maybe
; он построен на системе типов Haskell. Это означает, что вы можете использовать с ним все обычные приемы сопоставления с образцом Haskell .источник
Для данного типа
t
значениеJust t
- это существующее значение типаt
, гдеNothing
представляет собой неспособность достичь значения или случай, когда наличие значения было бы бессмысленным.В вашем примере отрицательный баланс не имеет смысла, поэтому, если такое произойдет, он будет заменен на
Nothing
.В другом примере это можно использовать при делении, определяя функцию деления, которая принимает
a
иb
и возвращает,Just a/b
если неb
равно нулю, и вNothing
противном случае. Это часто используется в качестве удобной альтернативы исключениям или, как в предыдущем примере, для замены значений, которые не имеют смысла.источник
Just
, ваш код не будет проверять типы. Причина в томJust
, чтобы поддерживать правильные типы. Существует тип (на самом деле монада, но его легче представить как простой тип)Maybe t
, который состоит из элементов формыJust t
иNothing
. ПосколькуNothing
имеет типMaybe t
, выражение, которое может оценивать одноNothing
или какое-то значение типаt
, не типизировано должным образом. Если функция возвращаетNothing
в некоторых случаях, любое выражение, использующее эту функцию, должно иметь какой-то способ проверки этого (isJust
или оператор case), чтобы обрабатывать все возможные случаи.Maybe t
это просто типаж. Тот факт, что существуетMonad
экземпляр дляMaybe
, не превращает его во что-то, что не является типом.Полная функция a-> b может найти значение типа b для каждого возможного значения типа a.
В Haskell не все функции являются тотальными. В данном конкретном случае функция
lend
не является итоговой - она не определена для случая, когда баланс меньше резерва (хотя, на мой вкус, было бы разумнее не разрешать newBalance быть меньше резерва - как есть, вы можете взять 101 из баланс 100).Другие конструкции, которые имеют дело с неполными функциями:
lend
можно написать, чтобы вернуть старый баланс, если условие для кредитования не выполненоЭто необходимые ограничения дизайна в языках, которые не могут обеспечить выполнение всех функций (например, Agda может, но это приводит к другим осложнениям, например, к неполным по Тьюрингу).
Проблема с возвратом специального значения или выдачей исключений заключается в том, что вызывающий объект может легко по ошибке пропустить обработку такой возможности.
Проблема с тихим отбрасыванием сбоя также очевидна - вы ограничиваете то, что вызывающий может делать с функцией. Например, если был
lend
возвращен старый баланс, вызывающий абонент не может узнать, изменился ли баланс. Это может быть или не быть проблемой, в зависимости от предполагаемой цели.Решение Haskell заставляет вызывающего частичную функцию иметь дело с типом, подобным
Maybe a
илиEither error a
из-за типа возвращаемого функцией.Таким образом,
lend
как это определено, функция, которая не всегда вычисляет новый баланс - для некоторых обстоятельств новый баланс не определяется. Мы сигнализируем об этом обстоятельстве вызывающей стороне, либо возвращая специальное значение Nothing, либо заключая новый баланс в Just. Теперь у вызывающего абонента есть свобода выбора: либо обработать отказ по ссуде особым образом, либо игнорировать и использовать старый баланс - напримерmaybe oldBalance id $ lend amount oldBalance
,.источник
Функция
if (cond :: Bool) then (ifTrue :: a) else (ifFalse :: a)
должна иметь одинаковый типifTrue
иifFalse
.Итак, когда мы пишем
then Nothing
, мы должны использоватьMaybe a
вводelse f
источник
Nothing
иJust newBalance
явно.Just
.