core icon indicating copy to clipboard operation
core copied to clipboard

(^) can return 0.5 typed as an Int

Open rtfeldman opened this issue 7 years ago • 3 comments

SSCCE

> (floor 2) ^ -1
0.5 : Int

This happens because (^) currently has the type number -> number -> number, but if it receives a negative second argument, it returns a fraction even if both arguments have the type Int.

rtfeldman avatar May 15 '18 16:05 rtfeldman

@lukewestby suggested in Slack that (^) having a return type of Float would be better.

Thanks to @harfangk for finding the original issue and to @z5h for sharing it in Slack!

rtfeldman avatar May 15 '18 16:05 rtfeldman

Status quo:

(*) number number -> number
(*) number Float  -> Float
(*) number Int    -> Int
(*) Float  number -> Float
(*) Float  Float  -> Float
(*) Float  Int    -> **Error**
(*) Int    number -> Int
(*) Int    Float  -> **Error**
(*) Int    Int    -> Int

Conclusio: (*) has three different output types, depending on situation and the same goes for (-) and (+). (^) works the same as (*) above right now, but: some special cases (bugs) occur when the second parameter is a negative number or negative Int:

(^) number ^ -number -> number with decimals
(^) number ^ -Int    -> Int with decimals
(^) Int    ^ -number -> Int with decimals
(^) Int    ^ -Int    -> Int with decimals

In contrast: (/) always returns Float (never number) and doesn't accept Int as input noteworthy: Float / 0 returns Infinity:Float noteworthy: Float / -0 returns -Infinity:Float

(//) always always returns Int (never number) and doesn't accept Float as input. Noteworthy: Int // 0 returns 0:Int

To continue this pattern, one could propose: (^) should not accept Int and always return Float (^^) should not accept Float and always return Int

What value does the type number actually add to the language? Why not just get rid of this pseudo-type altogether? As a beginner, I wouldn't mind the following scenario: x = 3 is to be considered an Int, x = 3.0 is to be considered a Float.

But once there is no ambiguity anymore we are of course forced to decide what to do when we combine Int and Float, which throws plenty of errors right now, as soon as numbers reveal themselves as potential integers.

2 + 3/1 = 5     ? option A
2 + 3/1 = 5.0   ? option B
2 + 3/1 = Error ? option C

Sure it's not nice to get an error for such a basic operation, especially for beginners. I would prefer option B, which is the same as the current status quo actually. If I really want only integers I would use 3//1 instead of 3/1.

Proposal

My proposal would be to get rid of the number type and that (+) (-) and (*) would then work like this:

(*) Float  Float  -> Float
(*) Float  Int    -> Float
(*) Int    Float  -> Float
(*) Int    Int    -> Int

Rationale: An Int can always be converted to a Float, but not the other way round. (/), (^) and should then work like this:

(^) Float Float -> Float
(^) Int   Int   -> Float
(^) Float Int   -> Float
(^) Int   Float -> Float

The operators that guarantee Int output should not accept Float input:

(//) Int Int -> Int
(^^) Int Int -> Int

Just like there is the special case of 9//0 which evaluates to 0, we could treat the new (^^) operation which results in an Int with decimals forgivingly by rounding the result: returning either 0 or 1:

10   ^ -2.0 = 0.01
10  ^^ -2   = 0
2    ^ -1.0 = 0.5
2   ^^ -1   = 1
1    ^ -1.0 = 1.0
1   ^^ -1   = 1
42   ^ -0.0 = 1.0
42  ^^ -0   = 1

michi-zuri avatar Oct 31 '19 16:10 michi-zuri

What's the status quo of this? It is a shame that Elm's type system it is cataloged as "unsound" just because of this issue 😭 https://typing-is-hard.ch/#elm

kutyel avatar Sep 02 '20 11:09 kutyel