As a personal excercize in the process of learning Haskell, I'm trying to port this F# snippet for Random Art.
I've not embedded full source code for not bloating the question, but is available as gist.
An important part of the program is this Expr
type:
data Expr =
VariableX
| VariableY
| Constant
| Sum Expr Expr
| Product Expr Expr
| Mod Expr Expr
| Well Expr
| Tent Expr
| Sin Expr
| Level Expr Expr Expr
| Mix Expr Expr Expr
deriving Show
and two functions:
gen :: Int -> IO Expr
random generates a tree-like structure given a number of iterations
eval :: Expr -> IO (Point -> Rgb Double)
walks the tree and terminates producing a drawing function.
More high is the number passed to gen
than higher are the probability that the following exception is generated: Ratio has zero denominator
.
I'm new to Haskell so to solve the problem I've tried to compile it as above:
ghc RandomArt.hs -prof -auto-all -caf-all
Obtaining only this more (to me quite useless) info:
$ ./RandomArt +RTS -xc
*** Exception (reporting due to +RTS -xc): (THUNK_STATIC), stack trace:
GHC.Real.CAF
--> evaluated by: Main.eval.\,
called from Main.eval,
called from Main.tga.pxs',
called from Main.tga,
called from Main.save,
called from Main.main,
called from :Main.CAF:main
--> evaluated by: Main.eval.\.r,
called from Main.eval.\,
called from Main.eval,
called from Main.tga.pxs',
called from Main.tga,
called from Main.save,
called from Main.main,
called from :Main.CAF:main
*** Exception (reporting due to +RTS -xc): (THUNK_STATIC), stack trace:
Main.tga,
called from Main.save,
called from Main.main,
called from GHC.Real.CAF
RandomArt: Ratio has zero denominator
The code that persist the generated function to a TGA file works because it was my previous excercize (a port from OCaml).
I've tried executing various Expr
tree from GHCi, assembling data by hand or applying functions as in the program but I wasn't able to identify the bug.
Haskell docs talks about a package named loch
that should able to compile preserving source code line numbers, but I was not able to install it (while I normally install with cabal install
every package I need).
The question, to be honest are two:
where's is the bug (in this specific case)?
which tool do I need to master to find bugs like this (or bugs in general)?
Thanks in advance.
Let's focus on the exception first.
where's is the bug (in this specific case)?
In mod'
. We can check this easily if we provide an alternative version instead of the one by Data.Fixed
:
mod' :: RealFrac a => a -> a -> a
mod' _ 0 = error "Used mod' on 0"
mod' a b =
let k = floor $ a / b
in a - (fromInteger k) * b
We now get Used mod' on 0
.
which tool do I need to master to find bugs like this (or bugs in general)?
In this case, the necessary hint was already in the exception's message:
Ratio has zero denominator
This means that there's a place where you divide by zero in the context of a Ratio
. So you need to look after all places where you divide something. Since you use only (/)
and mod'
, it boils down to whether one of them actually can throw this exception:
(/)
usually returns ±Infinity
on division by zero if used on Double
,mod'
uses toRational
internally, which is a Ratio Integer
.So there's only one culprit left. Note that the other implementation yields the same results if b
isn't zero.
Using mod
or mod'
with b == 0
isn't well-defined. After all, a modulo operation should hold the following property:
prop_mod :: Integral n => n -> n -> Bool
prop_mod a b =
let m = a `mod` b
d = a `div` b
in a == b * d + m -- (1)
&& abs m < abs b -- (2)
If b == 0
, there doesn't exist any pair (d, m)
such that (1) and (2) hold. If we relax this law and throw (2) away, the result of mod
isn't necessarily unique anymore. This leads to the following definition:
mod' :: RealFrac a => a -> a -> a
mod' a 0 = a -- this is arbitrary
mod' a b =
let k = floor $ a / b
in a - (fromInteger k) * b
However, this is an arbitrary definition. You have to ask yourself, "What do I actually want to do if I cannot use mod
in a sane way". Since F# apparently didn't complain about a % 0
, have a look at their documentation.
Either way, you cannot use a library mod
function, since they aren't defined for a zero denominator.
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