In what cases does NegativeLiterals change behavior?
Asked Answered
V

3

7

While describing the NegativeLiterals School of Haskell shows an example of how using the language extension might change the performance of some code and then says

Other examples might actually change behavior rather than simply be less efficient

After fooling around with the extension a bit I was not able to find any of these instances of behavior changes. I was only able to find the performance changes they were talking about and a few programs that will error with and without the extension.

What are these programs that change their behavior when the NegativeLiterals extension is enabled?

Virgievirgil answered 15/1, 2018 at 18:27 Comment(0)
M
6

The difference of negative literals is the difference of if we negate the integer then call fromInteger or call fromInteger then negate the type of the codomain. That is, fromInteger . negate is not the same as negate . fromInteger. I would expect this to happen when you are on the bounds of some type - one might saturate while another wraps.

For example I have a pretty trivially bad type:

data Bad = Bad Integer
    deriving (Show)

instance Num Bad where
    negate (Bad a) = Bad (a + 1)
    fromInteger = Bad

And the result:

*Main> (-1) :: Bad
Bad 2
*Main> :set -XNegativeLiterals
*Main> (-1) :: Bad
Bad (-1)
*Main>
Magnien answered 15/1, 2018 at 18:34 Comment(0)
P
2

NegativeLiterals also changes how some expressions are parsed. Unary - is interpreted as an operator with the same precedence as binary - (i.e. 6), but with NegativeLiterals a - that is directly (without whitespace) followed by a number will be part of the literal. Here is an example of how this can make a difference:

>>> :set -XNoNegativeLiterals
>>> -1 `mod` 2
-1
>>> :set -XNegativeLiterals
>>> -1 `mod` 2
1
>>> - 1 `mod` 2
-1

I have got this from this StackOverflow question.

Planksheer answered 29/1, 2019 at 20:35 Comment(2)
Thanks! This is a really neat example. I would suggest adding the output for - 1 `mod` 2 with -XNoNegativeLiterals as well for clarity.Virgievirgil
I do not think it would be helpful. To me including it would seem to suggest that they are different expressions with the same value but they are actually the exact same expression. Only in the case of -XNegativeLiterals are they parsed differently.Planksheer
S
0

Consider a fixed-size type, such as Int8 from Data.Int. This is a signed integer stored in 8 bits. Its possible values range from -128 to 127. So, with the default syntax, the literal -128 will try to store 128 in an Int8 and then negate it, which results in overflow. With NegativeLiterals, it will directly construct -128 as an Int8, avoiding this possible error.


As it turns out, on ghci, the literal 128 procures a warning and then produces -128 as a result of the overflow, so as it happens -128 produces the correct value. However, I don't believe this behavior is standardized since Int8 is a signed type, so it's best not to rely on overflow behaving in this way.

Splendid answered 15/1, 2018 at 18:39 Comment(2)
This is exactly the case presented in the linked post so I would hope that the OP is aware of it.Kaden
Oops! I got it from the GHC docs, which is probably where SoH got it too.Splendid

© 2022 - 2024 — McMap. All rights reserved.