Just started Haskell, it's said that everything in Haskell is "immutable" except IO package. So when I bind a name to something, it's always something immutable? Question, like below:
Prelude> let removeLower x=[c|c<-x, c `elem` ['A'..'Z']]
Prelude> removeLower "aseruiiUIUIdkf"
"UIUI"
So here:
1. “removeLower" is an immutable? Even it's a function object?
But I can still use "let" to assign something else to this name.
2. inside the function "c<-x" seems that "c" is a variable.
It is assigned by list x's values.
I'm using the word "variable" from C language, not sure how Haskell name all its names?
Thanks.
If you're familiar with C, think of the distinction between declaring a variable and assigning a value to it. For example, you can declare a variable on its own and later assign to it:
int i;
i = 7;
Or you can declare a variable and assign initial value at the same time:
int i = 7;
And in either case, you can mutate the value of a variable by assigning to it once more after the first initialization or assignment:
int i = 7; // Declaration and initial assignment
i = 5; // Mutation
Assignment in Haskell works exclusively like the second example—declaration with initialization:
I bolded and hyperlinked "scope" because it's the second critical component here. This goes one of your questions:
“removeLower" is an immutable? Even it's a function object? But I can still use "let" to assign something else to this name.
After you bind removeLower
to the function you define in your example, the name removeLower
will always refer to that function within the scope of that definition. This is easy to demonstrate in the interpreter. First, let's define a function foo
:
Prelude> let foo x = x + 2
Prelude> foo 4
6
Now we define an bar
that uses foo
:
Prelude> let bar x = foo (foo x)
Prelude> bar 4
8
And now we "redefine" foo
to something different:
Prelude> let foo x = x + 3
Prelude> foo 4
7
Now what do you think happens to bar
?
Prelude> bar 4
8
It remains the same! Because the "redefinition" of foo
doesn't mutate anything—it just says that, in the new scope created by the "redefinition", the name foo
stands for the function that adds three. The definition of bar
was made in the earlier scope where foo x = x + 2
, so that's the meaning that the name foo
has in that definition of bar
. The original value of foo
was not destroyed or mutated by the "redefinition."
In a Haskell program as much as in a C program, the same name can still refer to different values in different scopes of the program. This is what makes "variables" variable. The difference is that in Haskell you can never mutate the value of a variable within one scope. You can shadow a definition, however—uses of a variable will refer to the "nearest" definition of that name in some sense. (In the case of the interpreter, the most recent let
declaration for that variable.)
Now, with that out of the way, here are the syntaxes that exist in Haskell for variable binding ("assignment"). First, there's top-level declarations in a module:
module MyLibrary (addTwo) where
addTwo :: Int -> Int
addTwo x = x + 2
Here the name addTwo
is declared with the given function as its value. A top level declaration can have private, auxiliary declarations in a where
block:
addSquares :: Integer -> Integer
addSquares x y = squareOfX + squareOfY
where square z = z * z
squareOfX = square x
squareOfY = square y
Then there's the let ... in ...
expression, that allows you to declare a local variable for any expression:
addSquares :: Integer -> Integer
addSquares x y =
let square z = z * z
squareOfX = square x
squareOfY = square y
in squareOfX + squareOfY
Then there's the do
-notation that has its own syntax for declaring variables:
example :: IO ()
example = do
putStrLn "Enter your first name:"
firstName <- getLine
putStrLn "Enter your lasst name:"
lastName <- getLine
let fullName = firstName ++ " " ++ lastName
putStrLn ("Hello, " ++ fullName ++ "!")
The var <- action
assigns a value that is produced by an action (e.g., reading a line from standard input), while let var = expr
assigns a value that is produced by a function (e.g., concatenating some strings). Note that the let
in a do
block is not the same thing as the let ... in ...
from above!
And finally, in list comprehension you get the same assignment syntax as in do
-notation.