I confess that I am falling in love with Haskell, I am giving hard to the lists and the functions that can be applied:
Prelude> let list = [1,2,3,4,5]
Prelude> head list
1
Prelude> tail list
[2,3,4,5]
Prelude> init list
[1,2,3,4]
Prelude> last list
5
Prelude> take 2 list
[1,2]
Prelude> drop 3 list
[4,5]
Prelude> reverse list
[5,4,3,2,1]
Wonder, I understand that the functions that return a list (such as tail
, init
, reverse
, etc.) do not affect the list used as a parameter, that is, the original list list
has not been modified and a copy (?) of that list is returned:
Prelude> list
[1,2,3,4,5]
What I tried to do is something very simple:
Prelude> let list = reverse list
In my head I expected it to now list
contain [5,4,3,2,1]
, but trying to see what it contains just crashes the interpreter:
Prelude> list
(el cursor se queda acá y no hace nada)
Why is this happening?
Haskell is a pure functional language , so there are no side effects and no state : every function has a result, and always the same result if its parameters are identical.
To this we must add that in Haskell all variables are, in a way, functions: A constant value like the one you have defined can be understood as a function without arguments.
In this way, your code:
could be translated into an imperative language as:
With this hypothetical translation you should better understand what is happening: When trying to evaluate
list
, this translates to trying to evaluatereverse list
. This is clearly recursive, and without a base case to stop it, the result is an infinite loop.Darkhogg's answer, while it does point to a recursive definition as the cause, doesn't seem accurate to me. First of all, functional purity and statelessness are not the cause of this problem. Although it is true that we cannot modify the values in Haskell, we can locally obscure the names with definitions of a shorter scope ("shadow a binding with another one in a narrower scope"). This is easy to prove in the interpreter:
It is seen that:
lista
.ejemplo
was defined in a context wherelista = [1..5]
.ejemplo [4]
does not change after the redefinition oflista
—ejemplo
it remains the same function as it was at its point of definition.The redefinition did not modify the old value of
lista
, its only effect is on subsequent definitions. The above definitions maintain the same original meaning. Or more precisely:The second thing to note is that it's not a good idea to think that in Haskell "all variables are, in some way, functions." Thinking that way what you do is confuse the issue. In Haskell all functions are values, but not all values are functions.
To understand the difficulty of the example, what needs to be noted is that Haskell, unlike almost any other common language, allows recursive definitions not only of functions but also of values. For example, the following function builds a circular list:
In this definition, the variable
result
is used simultaneously on the left and right sides of its definition, both as the name we define, and as an argument to the function that produces the value we assign to the name. In Haskell this works, thanks to "lazy evaluation". We can illustrate this by an equational argument:Although this concept seems somewhat exotic, it turns out to be very useful, because it means that we can effortlessly construct graphs of immutable objects with mutual reference.
And now we can reexamine your example:
Your expectation is that on the right side of the
=
, the namelist
refers to its previous definition, in another scope ("scope"). In almost any other language that expectation would be correct, but it is not in Haskell—in this definition,list
on the right refers tolist
on the left.