Functional Purity using 'let' in Haskell

前端 未结 2 934
遥遥无期
遥遥无期 2021-01-12 20:51

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

相关标签:
2条回答
  • 2021-01-12 20:59

    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.

    0 讨论(0)
  • 2021-01-12 21:25

    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> 
    
    0 讨论(0)
提交回复
热议问题