Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I detect zero denominator when reading Ratios?

Tags:

haskell

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.

like image 389
bwroga Avatar asked Jul 25 '20 21:07

bwroga


People also ask

Can you have zero in a ratio?

On a ratio scale, a zero means there's a total absence of the variable of interest.

What if the denominator is 0?

if we ever have 0 in the denominator, all we can say is that the fraction is “undefined.”

Which number is the denominator in a ratio?

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.

Does a ratio have a denominator?

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.


1 Answers

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.

like image 127
Joseph Sible-Reinstate Monica Avatar answered Nov 02 '22 14:11

Joseph Sible-Reinstate Monica