Maybe: Making Absence Explicit
data Maybe a = Nothing | Just a is Haskell's answer to the 'billion-dollar mistake' of null references: instead of every reference type silently being allowed to hold a special null value, a function that might not have a result returns Maybe a, and the type signature itself -- lookup :: Eq k => k -> [(k, v)] -> Maybe v -- documents that absence is a real possibility the caller must handle. Because Nothing and Just x are different constructors of the same sum type, you cannot accidentally treat a Maybe Int as if it were a plain Int; the compiler forces you to pattern match or use a combinator before you can get at the underlying value, eliminating an entire category of null-pointer-style crashes at compile time.
Cricket analogy: A scorecard's 'not out' status is explicitly recorded rather than left blank and assumed, the same way Maybe Int explicitly forces you to consider the Nothing case rather than silently assuming a value is always there like an unchecked null.
Chaining Computations with Functor and Monad
Because Maybe has a Functor instance, fmap (+1) (Just 5) applies the function inside the Just and produces Just 6, while fmap (+1) Nothing stays Nothing without ever calling the function -- the wrapped context propagates automatically. Maybe's Monad instance takes this further: >>= (bind) lets you chain a sequence of operations that each might fail, short-circuiting to Nothing the moment any one of them does, and do-notation makes that chain read like ordinary sequential code, do { x <- safeDivide 10 2; y <- safeDivide x 0; return y } evaluating to Nothing as soon as the second division by zero occurs, without any explicit branching or exception handling written by hand.
Cricket analogy: A DRS review chain -- checking the no-ball, then the bat edge, then the pitching line -- stops the instant any check fails and returns 'not out', mirroring how >>= chains Maybe computations that short-circuit to Nothing the moment one step fails.
safeDivide :: Int -> Int -> Maybe Int
safeDivide _ 0 = Nothing
safeDivide x y = Just (x `div` y)
-- Chaining with do-notation: short-circuits on the first Nothing
compute :: Int -> Int -> Int -> Maybe Int
compute a b c = do
x <- safeDivide a b
y <- safeDivide x c
return (y + 1)
-- Either carries error information instead of just absence
data ParseError = EmptyInput | NotANumber String
deriving Show
parseAge :: String -> Either ParseError Int
parseAge "" = Left EmptyInput
parseAge str = case reads str of
[(n, "")] -> Right n
_ -> Left (NotANumber str)
main :: IO ()
main = do
print (compute 100 5 2) -- Just 11
print (compute 100 0 2) -- Nothing
print (parseAge "42") -- Right 42
print (parseAge "abc") -- Left (NotANumber "abc")
Either: Carrying Error Information
data Either a b = Left a | Right b looks structurally similar to Maybe, but where Maybe only tells you a computation failed, Either lets you say why: by strong convention, Left carries error information (like a ParseError or a String message) and Right carries the successful result, a mnemonic reinforced by 'Right' also meaning 'correct'. Either e has a Monad instance just like Maybe does, so a chain of Either-returning operations short-circuits on the first Left exactly the way a Maybe chain short-circuits on the first Nothing -- except now the value that comes out the other end tells you specifically which step failed and why, rather than just that something did.
Cricket analogy: A match report that records not just 'rain-affected' but specifically 'abandoned due to rain in the 34th over with no result achievable' carries far more information than a bare 'no result' flag, mirroring how Left (NotANumber "abc") carries a specific reason, unlike Maybe's bare Nothing.
Practical Helpers: maybe, either, fromMaybe
Rather than manually pattern matching every time, Data.Maybe and Data.Either provide small combinator functions that consume these types safely: maybe :: b -> (a -> b) -> Maybe a -> b takes a default value, a function to apply if there's a Just, and the Maybe itself, collapsing it to a plain value in one call; fromMaybe :: a -> Maybe a -> a is the common special case that just supplies a default; and either :: (a -> c) -> (b -> c) -> Either a b -> c does the analogous job for Either, taking one function for the Left case and one for the Right case. catMaybes :: [Maybe a] -> [a] is handy for filtering a list of Maybe values down to just the successes, discarding every Nothing in one pass.
Cricket analogy: A scorecard summary tool that takes 'if not out, show N/A; if out, show the dismissal type' is a single reusable rule rather than writing custom branching for every player, mirroring how maybe collapses a Maybe to a plain value with one reusable default-plus-function call.
Data.Maybe.mapMaybe :: (a -> Maybe b) -> [a] -> [b] combines mapping and filtering in one pass -- it's equivalent to catMaybes . map f but avoids building the intermediate list of Maybe values, making it the idiomatic choice whenever you need to both transform and filter a list based on a function that can fail.
Data.Maybe.fromJust :: Maybe a -> a and pattern matches that only handle the Just case (leaving Nothing to error "..." or an incomplete pattern) throw away the entire safety guarantee Maybe exists to provide -- reaching for fromJust is usually a sign the function should either return Maybe itself, or that the calling code should use maybe/fromMaybe/pattern matching to handle the Nothing case honestly instead of asserting it can't happen.
Maybe a = Nothing | Just areplaces null references, making the possibility of absence part of the type signature itself.Maybe's Functor instance (fmap) applies a function inside aJustand no-ops onNothing; its Monad instance (>>=, do-notation) chains fallible computations, short-circuiting on the firstNothing.Either a b = Left a | Right bis like Maybe but the failure case (Left) carries specific error information instead of a bare absence.- By convention,
Leftholds an error/failure value andRightholds a success value;Either's Monad instance short-circuits on the firstLeft. maybe,fromMaybe, andeitherare combinator functions that collapseMaybe/Eithervalues without manual pattern matching.catMaybesandmapMaybefilter/transform lists ofMaybevalues in one pass, discardingNothings.- Reaching for partial functions like
fromJustdefeats the safetyMaybeprovides and is usually a sign theNothingcase should be handled explicitly instead.
Practice what you learned
1. What problem does Maybe a solve compared to using null in other languages?
2. What happens when you fmap a function over Nothing?
3. By convention in Either a b, which constructor holds the successful result?
4. What is the main advantage of Either over Maybe when representing a failure?
5. Why is reaching for Data.Maybe.fromJust generally discouraged?
Was this page helpful?
You May Also Like
Algebraic Data Types
How Haskell builds custom types from sum types and product types using data declarations, pattern matching, records, and recursion.
Type Classes Explained
How Haskell's type classes define shared interfaces like Eq, Ord, and Show, and how instances and constraints let ad-hoc polymorphism work safely.
Error Handling in Haskell
Learn how Haskell represents failure explicitly using Maybe, Either, and exceptions, and when to reach for each approach.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics