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.
Consider the expression
(7^7^7`mod`5`mod`2)
And I want to know whether or not this expression is 1. For my safety, I will do perform this twice:
( (7^7^7`mod`5`mod`2)==1, [False,True]!!(7^7^7`mod`5`mod`2) )
which now gives (True,False)
with GHCi 7.4.1.
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?
(Apart from the only somewhat related question I found on SO there must be something else on this)
Probably another type-inference and referential-transparency related thing is the „dreaded“ Monomorphism restriction (its absence, to be exact). A direct quote:
Notice that in this case types of both expressions are the same. Results are too, but the substitution isn't always possible.
I do not think there's any guarantee that evaluating a polymorphically typed expression such as
5
at different types will produce "compatible" results, for any reasonable definition of "compatible".GHCi session:
This looks as it's breaking referential transparency since
length []
and0
should be equal, but under the hood it'snum
that's being used at different types.Also,
where one could have expected
False
in the last line.So, I think referential transparency only holds when the exact types are specified to resolve polymorphism. An explicit type parameter application à la System F would make it possible to always substitute a variable with its definition without altering the semantics: as far as I understand, GHC internally does exactly this during optimization to ensure that semantics is unaffected. Indeed, GHC Core has explicit type arguments which are passed around.
A another type has been chosen, because
!!
requires anInt
. The full computation now usesInt
instead ofInteger
.Haskell does not provide a precise promise or guarantee. There exist many functions like
unsafePerformIO
ortraceShow
which are not referentially transparent. The extension called Safe Haskell however provides the following promise: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.
As others have said, the problem emerges from this behavior:
This happens because
7^7^7
is a huge number (about 700,000 decimal digits) which easily overflows a 64-bitInt
type, but the problem will not be reproducible on 32-bit systems: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 forIntegral
classes; the platform-specificInt
type is used on the right due to the type of(!!) :: [a] -> Int -> a
. If you use the appropriate indexing operator forIntegral a
you instead get something consistent: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.The problem is overloading, which does indeed sort of violate referential transparency. You have no idea what something like
(+)
does in Haskell; it depends on the type.When a numeric type is unconstrained in a Haskell program the compiler uses type defaulting to pick some suitable type. This is for convenience, and usually doesn't lead to any surprises. But in this case it did lead to a surprise. In ghc you can use
-fwarn-type-defaults
to 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.I thought of something which might help clarify things...
The expression
mod (7^7^7) 5
has typeIntegral a
so there are two common ways to convert it to anInt
:Integer
operations and types and then convert the result to anInt
.Int
operations.If the expression is used in an
Int
context Haskell will perform method #2. If you want to force Haskell to use #1 you have to write:This will ensure that all of the arithmetic operations will be performed using
Integer
operations and types.When you enter he expression at the ghci REPL, defaulting rules typed the expression as an
Integer
, so method #1 was used. When you use the expression with the!!
operator it was typed as anInt
, so it was computed via method #2.My original answer:
In Haskell the evaluation of an expression like
depends entirely on which
Integral
instance is being used, and this is something that every Haskell programmer learns to accept.The second thing that every programmer (in any language) has to be aware of is that numeric operations are subject to overflow, underflow, loss of precision, etc. and thereby the laws for arithmetic may not always hold. For instance,
x+1 > x
is not always true; addition and multiple of real numbers is not always associative; the distributive law does not always hold; etc. When you create an overflowing expression you enter the realm of undefined behavior.Also, in this particular case there are better ways to go about evaluating this expression which preserves more of our expectation of what the result should be. In particular, if you want to efficiently and accurately compute a^b mod c you should be using the "power mod" algorithm.
Update: Run the following program to see how the choice of
Integral
instance affects the what an expression evaluates to:and the output (compiled with GHC 7.8.3 (64-bit):