bwroga
bwroga

Reputation: 5459

How can I detect zero denominator when reading Ratios?

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.

Upvotes: 10

Views: 251

Answers (1)

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.

Upvotes: 4

Related Questions