Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Functional Purity using 'let' in Haskell

Tags:

haskell

As I am working on learning Haskell, I understand it is a purely functional language. I am having trouble understanding why let-statements don't violate purity.

For example (in ghci):

Prelude> let e = exp 1
Prelude> e
2.718281828459045
Prelude> let e = 2
Prelude> e
2

isn't my second let statement producing a side effect? Or is the second let statement a new closure?

like image 807
anguyen Avatar asked Nov 24 '12 21:11

anguyen


2 Answers

Your second let creates a new binding for e that shadows the existing variable. It does not modify e. You can easily check this with the following:

Prelude> let e = 1
Prelude> let f () = "e is now " ++ show e
Prelude> f ()
"e is now 1"
Prelude> let e = 2
Prelude> e
2
Prelude> f ()
"e is now 1"
Prelude> 
like image 68
melpomene Avatar answered Dec 30 '22 11:12

melpomene


let introduces a new local variable with a single unalterable value, and it has more local scope than any surrounding definitions, so for example:

*Main> (let length = 2 in show length) ++ ' ':show (length "Hello")
"2 5"

Here the first length has the value 2, but its scope local to the brackets. Outside the brackets, length means what it has always meant. Nothing has been edited, just a more local variable has been introduced that happens to have the same name as another one in a different scope. Let's make ghci mad by omitting the brackets and making it try to make length a number and a function:

*Main> let length = 2 in show length ++ ' ':show (length "Hello")

<interactive>:1:14:
    No instance for (Num ([Char] -> a0))
      arising from the literal `2'
    Possible fix: add an instance declaration for (Num ([Char] -> a0))
    In the expression: 2
    In an equation for `length': length = 2
    In the expression:
      let length = 2 in show length ++ ' ' : show (length "Hello")

<interactive>:1:19:
    No instance for (Show ([Char] -> a0))

      arising from a use of `show'
    Possible fix: add an instance declaration for (Show ([Char] -> a0))
    In the first argument of `(++)', namely `show length'
    In the expression: show length ++ ' ' : show (length "Hello")
    In the expression:
      let length = 2 in show length ++ ' ' : show (length "Hello")

And here's your example:

*Main> let e = exp 1 in show e ++ " " ++ let e = 2 in show e
"2.718281828459045 2"

I'll add brackets to emphasise the scope:

*Main> let e = exp 1 in (show e ++ " " ++ (let e = 2 in (show e)))
"2.718281828459045 2"

The first e is hidden rather than edited. Referential transparency is preserved, but it's definitely bad practice because it's hard to follow.


Now secretly the interactive prompt is a bit like one big do block in the IO monad, so let's look at that:

testdo = do
  let e = exp 1
  print e
  let e = 2
  print e

Now I have to admit that looks an awful lot like breaking referential transparency, but bear in mind that this looks like it does too:

testWrite = do
   writeFile "test.txt" "Hello Mum"
   xs <- readFile "test.txt"
   print xs
   writeFile "test.txt" "Yo all"
   xs <- readFile "test.txt"
   print xs

Now in what sense have we got referential transparency? xs clearly refers to two different strings. Well, what does this do notation actually mean? It's syntactic sugar for

testWrite = writeFile "test.txt" "Hello Mum"
         >> readFile "test.txt" 
         >>= (\xs -> print xs 
         >> writeFile "test.txt" "Yo all"
         >> readFile "test.txt"
         >>= (\xs -> print xs))

Now it's clearer that what looks like assignment is just local scope again. You presumably are happy to do

increment :: [Int] -> [Int]
increment = \x -> map (\x -> x+1) x

Which is doing the same thing.


Summary
What appeared to be assignment is just introduction of a new local scope. Phew. If you use this a lot, you make it very unclear what your code means.

like image 33
AndrewC Avatar answered Dec 30 '22 11:12

AndrewC