What is the precise promise/guarantee the Haskell language provides with respect to referential transparency? At least the Haskell report does not mention this notion.
Haskell does not provide a precise promise or guarantee. There exist many functions like unsafePerformIO
or traceShow
which are not referentially transparent. The extension called Safe Haskell however provides the following promise:
Referential transparency — Functions in the safe language are deterministic, evaluating them will not cause any side effects. Functions in the IO monad are still allowed and behave as usual. Any pure function though, as according to its type, is guaranteed to indeed be pure. This property allows a user of the safe language to trust the types. This means, for example, that the unsafePerformIO :: IO a -> a function is disallowed in the safe language.
Haskell provides an informal promise outside of this: the Prelude and base libraries tend to be free of side effects and Haskell programmers tend to label things with side effects as such.
Evidently, this expression is now referentially opaque. How can I tell whether or not a program is subject to such behavior? I can inundate the program with :: all over but that does not make it very readable. Is there any other class of Haskell programs in between that I miss? That is between a fully annotated and an unannotated one?
As others have said, the problem emerges from this behavior:
Prelude> ( (7^7^7`mod`5`mod`2)==1, [False,True]!!(7^7^7`mod`5`mod`2) )
(True,False)
Prelude> 7^7^7`mod`5`mod`2 :: Integer
1
Prelude> 7^7^7`mod`5`mod`2 :: Int
0
This happens because 7^7^7
is a huge number (about 700,000 decimal digits) which easily overflows a 64-bit Int
type, but the problem will not be reproducible on 32-bit systems:
Prelude> :m + Data.Int
Prelude Data.Int> 7^7^7 :: Int64
-3568518334133427593
Prelude Data.Int> 7^7^7 :: Int32
1602364023
Prelude Data.Int> 7^7^7 :: Int16
8823
If using rem (7^7^7) 5
the remainder for Int64 will be reported as -3
but since -3 is equivalent to +2 modulo 5, mod
reports +2.
The Integer
answer is used on the left due to the defaulting rules for Integral
classes; the platform-specific Int
type is used on the right due to the type of (!!) :: [a] -> Int -> a
. If you use the appropriate indexing operator for Integral a
you instead get something consistent:
Prelude> :m + Data.List
Prelude Data.List> ((7^7^7`mod`5`mod`2) == 1, genericIndex [False,True] (7^7^7`mod`5`mod`2))
(True,True)
The problem here is not referential transparency because the functions that we're calling ^
are actually two different functions (as they have different types). What has tripped you up is typeclasses, which are an implementation of constrained ambiguity in Haskell; you have discovered that this ambiguity (unlike unconstrained ambiguity -- i.e. parametric types) can deliver counterintuitive results. This shouldn't be too surprising but it's definitely a little strange at times.
(True, False)
, with ghci 7.4.2. – Feebleminded(True, True)
. My guess to what is happening is that for some reason in GHC 7.4.2, the second one is getting parsed as((7^7^7) `mod` (5 `mod` 2))
, although I would think that it affect both sides. It's possible that there was some bug in the parser, but that's hard for me to determine without digging through the bug tracker or installing 7.4 – Sajovichlet f x = ((7^7^7 `mod` 5) `mod` x)
and then do( (f 2)==1, [False,True]!!(f 2) )
it still gives(True, False)
on ghci 7.4.2. I was thinking maybe somehow strictness of(!!)
caused something weird, but this suggests it is not parsing or strictness. – Feebleminded(!!)
infersInt
, and trying to experiment with that gives me some rather weird results:(-3568518334133427593 `mod` 5 `mod` 2) :: Int == -1
,(-3568518334133427593 `mod` 5 :: Int) `mod` 2 == 1
, and(-3568518334133427593 :: Int) `mod` 5 `mod` 2 == 0
(in ghci 7.6.3). – Heterotaxis-fwarn-type-defaults
so you can see when the compiler has used defaulting to pick a type for you. You can also add the linedefault ()
to your module to stop all defaulting. Then you will be forced to give the type of the first expression, and you'll see thatInt
vsInteger
makes a difference. – DietInt
isn't defined (except to demand at least 29 bits or some weird number like that). Try7^7^7 `mod` 5 `mod` 2 :: Int32
in ghci, and similarly forInt64
. – Eastman