core
core copied to clipboard
(^) can return 0.5 typed as an Int
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.
@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!
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
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