{- |
Description: Maybe monad
Copyright: Copyright (C) 2023 Yoo Chung
License: GPL-3.0-or-later
Maintainer: dev@chungyc.org

Part of Ninety-Nine Haskell "Problems".  Some solutions are in "Solutions.P75".
-}
module Problems.P75
  ( maybeGoldbach
  -- * Original function
  -- | The function below is the equivalent implementation of 'maybeGoldbach' without do notation.
  , maybeGoldbach') where

import           Solutions.P40 (goldbach)
import qualified Solutions.P75 as Solution
import           Text.Read     (readMaybe)

-- $setup
-- >>> import Problems.P74  (withFixedInput)
-- >>> import System.IO

{- |
In "Problems.P75", 'Problems.P75.askGoldbach' could not output an error if the input was not a number
or it was not an even number greater than 2.  We could implement a function which returned 'Nothing'
when the input is not valid, such as in the following.

@
maybeGoldbach :: String -> Maybe (Int, (Int,Int))
maybeGoldbach s =
  case readMaybe s of
    Nothing -> Nothing
    Just n -> if n < 2 then Nothing else
                if odd n then Nothing else
                  Just (n, goldbach n)
@

Then we could take advantage of this function to print out an error if the input is not valid.

>>> :{
let format (n, (a,b)) = (show n) ++ "=" ++ (show a) ++ "+" ++ (show b)
    maybeAskGoldbach hIn hOut = do
      s <- hGetLine hIn
      case maybeGoldbach s of
        Nothing -> hPutStrLn hOut "error"
        Just x  -> hPutStrLn hOut $ format x
in do
  withFixedInput "104" stdout maybeAskGoldbach
  withFixedInput "not a number" stdout maybeAskGoldbach
:}
104=3+101
error

However, the implementation of @maybeGoldbach@ above is a chain of conditional expressions.
It is not problematic in this particular case, but can make things awkward when there
are many conditions and successful operations that need to happen
for a function to return a 'Maybe' value.

Take advantage of the fact that 'Maybe' is a monad
and rewrite @maybeGoldbach@ more succintly using do notation.
The 'Control.Monad.guard' function, which in the 'Maybe' monad
returns @Just ()@ when its argument is true
and @Nothing@ when its argument is false,
would be useful for making it even more succinct.

=== Examples

>>> maybeGoldbach "104"
Just (104,(3,101))

>>> maybeGoldbach "not a number"
Nothing

>>> maybeGoldbach "1"
Nothing

>>> maybeGoldbach "101"
Nothing
-}
maybeGoldbach :: String -> Maybe (Integer, (Integer, Integer))
maybeGoldbach :: String -> Maybe (Integer, (Integer, Integer))
maybeGoldbach = String -> Maybe (Integer, (Integer, Integer))
Solution.maybeGoldbach

{- | Parses a string into a number and returns a tuple of the number and its Goldbach pair.
Returns 'Nothing' if the input is not valid.

This is an implementation of 'maybeGoldbach' without do notation.

=== Examples

>>> maybeGoldbach' "104"
Just (104,(3,101))

>>> maybeGoldbach' "not a number"
Nothing

>>> maybeGoldbach' "1"
Nothing

>>> maybeGoldbach' "101"
Nothing
-}
maybeGoldbach' :: String -> Maybe (Integer, (Integer, Integer))
maybeGoldbach' :: String -> Maybe (Integer, (Integer, Integer))
maybeGoldbach' String
s
  | Just Integer
n <- String -> Maybe Integer
forall a. Read a => String -> Maybe a
readMaybe String
s, Integer
n Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
> Integer
2, Integer -> Bool
forall a. Integral a => a -> Bool
even Integer
n = (Integer, (Integer, Integer))
-> Maybe (Integer, (Integer, Integer))
forall a. a -> Maybe a
Just (Integer
n, Integer -> (Integer, Integer)
forall a. Integral a => a -> (a, a)
goldbach Integer
n)
  | Bool
otherwise = Maybe (Integer, (Integer, Integer))
forall a. Maybe a
Nothing