Map over the optional payload, preserving failure:
mapMaybe :: (a -> b) -> Maybe a -> Maybe b
mapMaybe _ Nothing = Nothing
mapMaybe f (Just x) = Just (f x)
This is the optional-value mapping operation, equivalent to fmap for Maybe. The question's local name is distinct from the library function Data.Maybe.mapMaybe, which filters and maps an entire list. The algebraic data type keeps absence separate from every ordinary payload value.
The most general type signature is foldMaybe :: (a -> b) -> b -> Maybe a -> b. The Nothing branch returns the default of type b; the Just branch applies the supplied function to an a, also producing a b.
Separate the maximal decimal prefix from the unconsumed suffix, rejecting an empty prefix:
import Data.Char (isDigit, digitToInt)

type Parse a = String -> Maybe (a,String)

parseInt :: Parse Int
parseInt input =
  case span isDigit input of
    ([],_)  -> Nothing
    (ds,ys) -> Just (foldl (\n d -> 10*n + digitToInt d) 0 ds, ys)
The left fold uses the positional notation recurrence . This parses unsigned decimal operands as illustrated, leaving the first nondigit and everything after it untouched. As the required result type is Int, correct mathematical values require the parsed integer to fit that bounded type; arbitrary-length exact values would instead need Integer or an explicit integer overflow check.
The parser recognizes exactly one leading operator character:
parseOp :: Parse Char
parseOp (c:cs)
  | c `elem` "+-*/" = Just (c,cs)
parseOp _ = Nothing
An empty input fails, and any later characters remain for the next parser. The last example printed in this part uses the name parseInt; its failure result does not change the requested operator parser.
Compose the three parsing stages using the supplied fold, and accept only when no suffix remains:
eval :: String -> Maybe Int
eval input = foldMaybe first Nothing (parseInt input)
  where
    first (a,rest) = foldMaybe (second a) Nothing (parseOp rest)
    second a (op,rest) = foldMaybe (finish a op) Nothing (parseInt rest)
    finish a op (b,rest)
      | null rest = Just (apply op a b)
      | otherwise = Nothing
    apply '+' a b = a+b
    apply '-' a b = a-b
    apply '*' a b = a*b
    apply '/' a b = a `div` b
    apply _ _ _ = error "unreachable: parseOp rejected this operator"
Every failed intermediate parser is converted to Nothing by foldMaybe. Checking null rest rejects both a second operation and trailing letters. In particular, integer division makes eval "34/4" = Just 8. As permitted by the question, division by zero is outside the specified inputs; the bounded-Int proviso also applies to arithmetic results.

Articles by others on the same topic (0)

There are currently no matching articles.