patternMinor
Fraction or integer parser
Viewed 0 times
parserintegerfraction
Problem
Question
How can the following implementation of
less ugly? (Preserving unreduced numerators and denominators is supposed to be a feature---as shown in the second example---so using
Things that irked me in the process of writing it, include:
Usage examples
Implementation
How can the following implementation of
fractionParse be madeless ugly? (Preserving unreduced numerators and denominators is supposed to be a feature---as shown in the second example---so using
readMaybe s :: Maybe (Ratio Int) doesn't really help.)Things that irked me in the process of writing it, include:
- Not being able to find a way to use do-notation in conjunction with
Maybein a way that would return as soon asJustappears rather than when the firstNothingappears. In other words aMaybemonad instance that emulatesorrather thanand.
caseand pattern matching colliding in unhelpful ways, forcing me to write nestedif-then-elses.
Usage examples
λ> fractionParse "2/3"
Fraction 2 3
λ> fractionParse "12 / 6"
Fraction 12 6
λ> fractionParse "23"
Whole 23
λ> fractionParse "a"
Rubbish
λ> fractionParse "2/3/4"
Rubbish
λ> fractionParse "2%3"
RubbishImplementation
import Text.Read (readMaybe)
import Data.String.Utils (split)
import Data.Maybe (isJust, fromJust)
data FractionParse = Fraction Int Int
| Whole Int
| Rubbish deriving (Show, Eq)
fractionParse :: String -> FractionParse
fractionParse s = let i = maybeInt s
r = maybeNumDenom s in
if isJust r
then let (Just (n,d)) = r in Fraction n d
else if isJust i
then Whole (fromJust i)
else Rubbish
maybeInt :: String -> Maybe Int
maybeInt s = readMaybe s
maybeNumDenom :: String -> Maybe (Int, Int)
maybeNumDenom s = do
if containsExactlyOneSlash s then Just () else Nothing
let [ns,ds] = split "/" s
n <- readMaybe ns
d <- readMaybe ds
return (n,d)
containsExactlyOneSlash s = (length $ filter (=='/') s) == 1Solution
You're looking for the
It doesn't make sense to encode your own failure values in your datatypes, so I'd remove the
Use
Take advantage of incremental parsing to implement
The above function operates in the list monad.
First Monoid instance for Maybe. Used like—import Data.Foldable (foldMap)
import Data.Monoid (First(..))
fractionParse :: String -> FractionParse
fractionParse s = fromMaybe Rubbish . getFirst
$ foldMap First [ maybeFraction s
, maybeWhole s
]It doesn't make sense to encode your own failure values in your datatypes, so I'd remove the
Rubbish Constructor.data FractionParse = Fraction Int Int | Whole Int
deriving (Show, Eq)
fractionParse :: String -> Maybe FractionParseUse
Control.Monad.guard instead of manually sending up your own sentinel values (as in maybeNumDenom).maybeNumDenom s = do
guard $ containsExactlyOneSlash s
-- ...Take advantage of incremental parsing to implement
maybeNumDenom, then you won't have to do so much extraneous filtering, counting, and finger crossing. And by removing the Rubbish constructor, at the point you have a correct parse you know you can return a Fraction, so—import Data.Maybe (listToMaybe)
maybeFraction :: String -> Maybe FractionParse
maybeFraction s = listToMaybe $ do
(n, '/':s') <- reads s
(d, "") <- reads s'
return (Fraction n d)The above function operates in the list monad.
listToMaybe converts a list to a Maybe value by returning Just the first element of the list, or Nothing in the case of an empty list. reads :: Read a => String -> [(a, String)] produces possible parses from a given string, returning the remainder of the unparsed string as the second element of each tuple. Binding to (d, "") ensures that only parses that consume the whole string will be returned.Code Snippets
import Data.Foldable (foldMap)
import Data.Monoid (First(..))
fractionParse :: String -> FractionParse
fractionParse s = fromMaybe Rubbish . getFirst
$ foldMap First [ maybeFraction s
, maybeWhole s
]data FractionParse = Fraction Int Int | Whole Int
deriving (Show, Eq)
fractionParse :: String -> Maybe FractionParsemaybeNumDenom s = do
guard $ containsExactlyOneSlash s
-- ...import Data.Maybe (listToMaybe)
maybeFraction :: String -> Maybe FractionParse
maybeFraction s = listToMaybe $ do
(n, '/':s') <- reads s
(d, "") <- reads s'
return (Fraction n d)Context
StackExchange Code Review Q#133313, answer score: 2
Revisions (0)
No revisions yet.