I want to read a Ratio from a String, but I don't want my program to crash when the denominator is zero. How can I detect a zero denominator and avoid an error? Just using readMaybe
doesn't work:
Prelude Text.Read> readMaybe "1 % 0" :: Maybe Rational
Just *** Exception: Ratio has zero denominator
I created this far from perfect solution:
readMaybeRational :: String -> Maybe Rational
readMaybeRational s =
case ((readMaybe $ drop 1 $ dropWhile (/='%') s) :: Maybe Int)
of Just 0 -> Nothing
_ -> readMaybe s
But I don't know how to handle a nested Ratio nicely:
"Just (1 % 0)"
If I could override Ratio's Read instance, I could get readMaybe to return Nothing when the denominator is zero:
instance (Integral a, Read a) => Read (Ratio a) where
readPrec =
parens
( prec ratioPrec
( do x <- step readPrec
expectP (L.Symbol "%")
y <- step readPrec
-- is y 0? If so, do something here
return (x % y)
)
)
But I'm pretty sure I can't do that.
On a ratio scale, a zero means there's a total absence of the variable of interest.
if we ever have 0 in the denominator, all we can say is that the fraction is “undefined.”
Fractions consist of sets of numbers in which the top number (numerator) illustrates a part that is related to the whole unit, which is represented by the bottom number (denominator). A ratio is very similar to a fraction, in that it is composed of two numbers being compared to each other.
Consequently, a ratio may be considered as an ordered pair of numbers, a fraction with the first number in the numerator and the second in the denominator, or as the value denoted by this fraction. Ratios of counts, given by (non-zero) natural numbers, are rational numbers, and may sometimes be natural numbers.
I think your best solution is a newtype
wrapper around Ratio
, like this:
import Control.Monad
import GHC.Read
import GHC.Real
import qualified Text.Read.Lex as L
import Text.ParserCombinators.ReadPrec
newtype SaneReadRatio a = SaneReadRatio (Ratio a)
type SaneReadRational = SaneReadRatio Integer
instance (Integral a, Read a) => Read (SaneReadRatio a) where
readPrec =
parens
( prec ratioPrec
( do x <- step readPrec
expectP (L.Symbol "%")
y <- step readPrec
guard (y /= 0)
return (SaneReadRatio (x % y))
)
)
readListPrec = readListPrecDefault
readList = readListDefault
Use it by reading in your data with SaneReadRational
in place of Rational
, then using coerce
from Data.Coerce
on the result, which will change it back to the underlying Rational
no matter how deeply it's buried inside your type.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With