I’ve entered an issue about this a month ago
and because the reaction has been numb,
I wanted to ask if you saw it as a problem,
that Elm has no consistent or noticeable way
of telling, if a division by zero has occurred in a
calculation.
I have identified some functions,
that implemented some kind of division
and got radically different results.
Once Elm even crashed!
What do you think of this?
I have proposed a possible solution on GitHub,
but because I didn’t get any answer and it is
an extremely breaking change, I thought to ask
here, if you had an opinion on the matter.
The issue in question is: https://github.com/elm/core/issues/1072
I would handle this in a library: one that returns a Maybe type. For most cases having the deal with Maybe is just not needed and becomes extremely annoying code.
So I suggest you create an Elm library that implements your proposal.
That would be alright, if the function
modBy
wouldn’t crash!
I have no idea how to fix that, but it needs to be fixed and
not in a library.
An option would be to implement what you describe:
modBy returns (Maybe number)
but add functions like riskyModBy or crashableModBy that returns number. so that one does not need to worry about handeling maybe’s if you don’t need to.
The update for packages would be pretty simple: replace modBy with riskyModBy
Doing the oposite and call the new function safeModBy would not break anything, but in my opinion elm should be safe by default and then opt out of safety if you know what you are doing.
(Just like Http.request / Http.riskyRequest )
I dislike that solution, because “Elm has no runtime errors” is
one of it’s core promises.
I have a different proposal, that might help and not cause a
crash:
We create a new Value as part of Int
and Float
called
NotANumber
that is returned if an invalid calculation (division by zero) is performed.
And if you make any operation using NotANumber
(+,-,*,/) NotANumber
is the result.
Thus, you can check for this result whenever you want
and if a lazy or bad program were to output NotANumber
to the user, they would also immediately understand
the problem, while crashes or NaN
or Infinity
are less
helpful.
Hmm isn’t that what NaN
already means?
Currently NaN
is a member of Float
but not Int
. Maybe it would make sense to have it in Int
as well, and have division and modBy 0 return NaN
? I agree that having modBy
crash is very unexpected.
As I know it, there is no NaN
value in Elm,
at least the following doesn’t compile:
NaN
The Error being:
I cannot find a `NaN` variant:
3| NaN
^^^
These names seem close though:
EQ
Err
GT
LT
Hint: Read <https://elm-lang.org/0.19.1/imports> to see how `import`
declarations work in Elm.
Thus I think it doesn’t exist or it doesn’t exist in
any useful manner, because you can’t match for it.
And also, NaN is a cryptic shortcut for Not a Number
and anybody who isn’t familiar with programming, will
have to look it up or be left wondering, what it means,
that an App in Elm outputted NaN, while any user,
who can understand English, will understand when
an app prints “NotANumber”, even if it is written in a weird way.
In this solution, I have thought mostly about, what
would happen if Elm introduced this feature today.
Many developers wouldn’t fix their programs fast
enough, so it would only be caught, once the results
of a calculation were in some way printed,
which is when the users will know what NotANumber
means, while they are going to be confused with NaN
or Infinity or any other value of this sort.
NaN does exist in Elm, and there is the isNaN
function to check for it (also isInfinite
)
https://ellie-app.com/7Qfvy4RHZdNa1
Maybe it could be renamed to NotANumber though
My Problem wit that is, that you can’t use case … of
to match
against NaN, or can you?
And if you can, do you have to do something like:
case x of
(0 / 0) ->
Which, to me, looks less like intention and more like an unreadable
hack, that nobody will understand, if they wouldn’t know what
0 / 0 computes to, but how should they know?
It isn’t just the total lack of documentation (I have a PR on GitHub
pending, that would have fixed, if you went back a few commits),
but also how all functions implementing division return something else on division by 0, so nobody could know that in general, all divisions by 0 return this or that value.
Where did you get the information, that NaN would only exist in
Float?
Because remainderBy returns NaN as a Float:
remainderBy 0 5
NaN : Int
I would really like to know that.
And also whether NaN
was a planned part of Elm’s Data types
or a legacy from compiling to JS.
Ah interesting, might have just remembered wrong
I’m guessing Elm follows JS and IEEE 754 regarding Float, for now anyway.
In my opinion, you pointed out two severe bugs which should be solved ASAP. After that, we can discuss if there is a handier way of doing division by zero. Edit: taking the dates of multiple, already, filled, issues into account, it’ll probably take some more time before this gets fixed…
First, that modBy 0
gives an error instead of 0
is bad because we don’t want runtime exceptions. Exactly this was the reason for Elm to decide that 1 // 0 == 0
instead of throwing an error. Edit: Apparently it is already on a fix-me list dating from 2015…
Second, that remainderBy 0
gives NaN
leaks that Elm under the hood uses JavaScript numbers to represent integers. NaN
is part of IEEE Floats, but not Ints. Edit: Harry shows this nicely in an issue comment.
So, both should either return 0
if their type stays Int -> Int -> Int
to be consistent with //
; or their types should be Float -> Float -> Float
so that NaN
can be a result of both calculations.
I have already discussed in my issue, why returning 0 is bad.
It makes it impossible to test later on, if the calculation is invalid,
after all x/0 == 0/x using this rule. ( the first term being mathematically invalid, while the second is valid.)
I have stated this clearly in the first line of my issue:
There is no answer to the equation x = a/0
where a ∈ R.
Not even x = +∞
or x = - ∞
are valid.
I have created a small bug fix to all the affected functions using a case of matching and returning NaN
if the divisor = 0.
It isn’t perfect, especially because I have no idea how the type checker will react.
The current state of things in Elm is kind of a mess:
Float
s are messy but at least they’re IEEE floats so their behavior is well-documented.
Int
s, on the other hand, are crazy. Part of this comes from the fact that they have to use JavaScript numbers internally which are floats; but a better-documented and saner solution is certainly desirable: For example, if Elm should compile to Web Assembly some day in the future.
The current state is this: Integer division rounds towards zero. x // 0
is defined to be 0
for any x
. The expression remainderBy 0 x
returns NaN
but modBy 0 x
gives a runtime exception. Further, you can get Infinity : Int
by using round
on Infinity : Float
. You cannot get Infinity : Int
by integer operations alone (I think). This combination of choices does not make any sense to me.
Here’s what I believe would be reasonable: Int
s are 32 bit or 64 bit integers and their only values are the numbers between −2^31 − 1 and 2^31 (or with 63 instead of 31). So no NaN
s or Infinity
s in Int
.
Integer division a // b
does not perfectly answer the question “Which number q solves a = q · b?” anyways (because there is no such a integer if b does not divide a), so there is some leeway. The most sensible definition seems to me to ask for two numbers q and r that satisfy
a = b · q + r.
To make this well-defined, you have to restrict r. The two common choices are
(a) 0 ≤ r < |b| or
(b) 0 ≤ |r| < |b| where r has the same sign as a.
If a is positive these are the same. But for negative numbers, (a) is much more useful in my experience while (b) is more common in programming languages (I think it’s easier to implement in hardware). In version (a), integer division rounds towards negative infinity; in version (b), integer division rounds towards zero.
In both cases, the restriction does not work for b = 0. However, in this case r is already determined by the equation above: It has to be a because b · q is always 0 when b is 0. For q you can choose whatever you want but q = 0 seems the most symmetric choice in this case.
So here is what I think would be best:
a // b
returns the result of (the ordinary division) a / b rounded down (i.e. towards negative infinity) if b ≠ 0 and it returns 0 if b = 0. modBy b a
returns remainder of this division which is always a number between 0 (inclusive) and |b| (exclusive) if b ≠ 0 and it returns a if b = 0. (This implements version (a) above.) This definition makes it so that
(a // b) + modBy b a == a
always holds.
You can get rid of remainderBy
or use it to implement version (b) above. In this case it should be accompanied by a second division function that rounds towards zero. If this function is call quotBy
(for example) we would have
quotBy b a + remainderBy b a == a
for all numbers again.
I don’t have a good solution for rounding NaN : Float
and Infinity : Float
. Either change the type signature of these functions to round : Float -> Maybe Int
or have them return 0
or maxInt
. Defining them to be NaN : Int
or Infinity : Int
seems unwise as those definitions will almost certainly make it impossible to use Web Assembly ints in the future.
And how do you propose to solve the following problem?
There is an unknown function f
which takes some arguments and may or may not do some division.
How do you check, if the results of f
are invalid?
If the result is somehow 0
or a
(to use your terminology), you can’t tell and have to lookup the function and check every calculation.
This situation might seem odd, but anyone using
a library will not want to inspect every function in
the same.
In conclusion, I think we have no choice but to use Maybe
,
if you’re right and want to one day be able to compile to WebAsm.
I find this argument unconvincing because it applies to every unknown function. An integer division by zero is just one way in which there might be a subtle mistake in the definition of f
. Another might be that f
does an integer division but the default rounding of a // b
leads to wrong results – I cannot check for that either, so maybe a // b
should only be allowed if a
actually divides b
and return NaN
otherwise?
There are essentially four solutions to a // 0
:
- Return an arbitrary result – choosing
0
seems the most natural to me. The advantages are that you stay withinInt
and that//
is a total function. The disadvantage is thata // 0
is probably a mistake that you are now silently ignoring. - Return
NaN
orInfinity
. The advantages are that you might be able to detect a division by zero (depending on the further computation). The disadvantages are that these are not usually values ofInt
so that you have to do yourInt
computations using floating point math.NaN
as a value ofInt
is not something many people expect. (Not even the developers of Elm: Note that theisNaN
function has typeFloat -> Bool
.) - Crash. The advantage is that you notice the (probably) mistaken calculation and don’t accidentally use its result in further computations, corrupting your data. The disadvantage is that you crash.
- Change the result type to
Maybe Int
. The advantage is that you cannot forget to handle the case of a division by zero. The disadvantage is that your computation now involves Elm constructs (which might make it harder to optimize) and that you always have to handle this case even though there are many situations where you know that division by zero cannot occur: For example, if the divisor is a fixed number.
None of these is perfect. The usual solution (in other languages) is 3 but it is not very Elm like. The costs of 2 and 4 seem very high for a relatively minor problem to me. That leaves option 1.
(In any case, the current state of affairs appears to be basically random with no unifying principle behind it.)
So, the perfect solution would be one, that:
- Doesn’t break the definition of
Int
- Does signal to the program if division by zero has occurred
- Doesn’t force the handling of the division by zero case, if not necessary.
I think in the already existing functions in Basics.elm,
this is impossible to achieve.
I thus have this proposal:
- We define a value inside the Integer realm, that is used whenever
/
,//
,modBy
orremainderBy
have to divide by 0. (This value would probably be 0) - We define a set of functions, doing the exact same thing as
/
,//
,modBy
andremainderBy
and return aMaybe number
. These functions I would put into Basics.elm and refer to them in the documentation of the first set of functions.
With this solution, anyone who doesn’t want to, doesn’t
have to bother with division by 0, but can if they want to or need to.
And all informed developers know about this slightly inaccurate
behavior of /
, //
, modBy
and remainderBy
.
This proposal is probably the only thing we can do without major breaks.
It solves most of my problems, for whom I raised this issue
in the first place:
- The inconsistency of division by 0
- The crashing of
modBy
- The undocumented nature of the problem
Although it doesn’t solve the problem of lazy developers using the wrong functions where they really shouldn’t.
@Lucas_Payr proposed adding refinement types a while back Elm-Refine: Proposal. I’m guessing nothing came of it but the idea would be that, for example, modBy’s type signature would be changed from
modBy: Int -> Int -> Int
to modBy: IntWithoutZero -> Int -> Int
and IntWithoutZero would be defined with a special syntax (there’s more discussion in the thread about what that could look like)
{-| @refine \int -> int /= 0
-}
type alias IntWithoutZero =
Int
The compiler would then verify that this condition holds everywhere modBy is called. I guess in cases where it can’t be proven, the user would need to write if value == 0 then ... else modBy value n
or something.