3 ms·
Do-notation is sequenced evaluation, where the result of each statement is passed as the argument to the next (a simplification, but good enough). In this case
by gh02t 10y ago
Do-notation is sequenced evaluation, where the result of each statement is passed as the argument to the next (a simplification, but good enough). In this case there are two sequential operations and if either of them fails then it automatically propagates and results in the whole sequence returning a value that indicates failure. In Python, an (approximate) analog would be like this:
from math import sqrt
def f(a):
if (a >= 0):
return (True, sqrt(a))
else:
return (False, None)
def do(a):
(ok, y) = f(a)
if ok:
(ok2, y2) = f(y)
if ok2:
return (True, y2)
else:
return (False, None)
else:
return (False, None)
# example
for x in [2, -2]:
(ok, y) = do(x)
if ok:
print("Result: {}".format(y))
else:
print("Calculation failed.")
# Output
>>> Result: 1.189207115002721
>>> Calculation failed.
This sort of thing is super awkward in Python, but is a common/natural pattern in Haskell (note: I realize this is not how you'd do this in Python, but I'm trying to make it equivalent to the Haskell). This does the exact same thing as the Python program above and is a more complete version of what the OP posted:
import Control.Monad (mapM_)
{-
Maybe Double represents a computation that either
returns a Double or fails. In this case, fail
when passed a negative number. Note: this is not
the same as throwing an exception.
-}
f :: Double -> Maybe Double
f x
| x >= 0 = Just (sqrt x)
| otherwise = Nothing
-- Fleshed out version of OP's snippet
eval :: Double -> Maybe Double
eval x = do
y <- f x
f y
{-
do notation desugars to monadic binding; in this
case eval can be desugared to the equivalent definition:
eval x = f x >>= f
-}
-- Here's why this is convenient, because you can pattern match
printIf :: Show a => Maybe a -> IO ()
printIf (Just x) = putStrLn $ "Result: " ++ show x
printIf Nothing = putStrLn "Calculation failed."
-- This is basically the equivalent of the Python for loop
main :: IO ()
main = mapM_ (printIf . eval) [2, -2]
I know Haskell syntax is strange looking if you're unfamiliar with it, but it's mostly geared towards making certain patterns (like this one) convenient.