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)
Referential transparency, a term commonly used in functional programming, means that given a function and an input value, you will always receive the same output. That is to say there is no external state used in the function.
Contrast to imperative programmingIf the substitution of an expression with its value is valid only at a certain point in the execution of the program, then the expression is not referentially transparent.
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:
> class C a where num :: a
> instance C Int where num = 0
> instance C Double where num = 1
> num + length [] -- length returns an Int
0
> num + 0 -- GHCi defaults to Double for some reason
1.0
This looks as it's breaking referential transparency since length []
and 0
should be equal, but under the hood it's num
that's being used at different types.
Also,
> "" == []
True
> [] == [1]
False
> "" == [1]
*** Type error
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.
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 line default ()
to your module to stop all defaulting.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With