This post clarifies the distinction between
= in Haskell, which sometimes mystifies newcomers to the language. For example, consider the following contrived code snippet:
main = do input <- getLine let output = input ++ "!" putStrLn output putStrLn (input ++ "!")
The above program reads one line of input, and then prints that line twice with an exclamation mark at the end, like this:
$ ./example Hello<Enter> Hello! Hello!
Why does the first line use the
<- symbol to assign a value to
input while the second line uses the
= symbol to define
output? Most languages use only one symbol to assign values (such as
:=), so why does Haskell use two?
Haskell bucks the trend because the
= symbol does not mean assignment and instead means something stronger than in most programming languages. Whenever you see an equality sign in a Haskell program that means that the two sides are truly equal. You can substitute either side of the equality for the other side and this substitution works in both directions.
For example, we define
output to be equal (i.e. synonymous) with the expression
input ++ "!" in our original program. This means that anywhere we see
output in our program we can replace
input ++ "!" instead, like this:
main = do input <- getLine let output = input ++ "!" putStrLn (input ++ "!") putStrLn (input ++ "!")
Vice versa, anywhere we see
input ++ "!" in our program we can reverse the substitution and replace the expression with
output instead, like this:
main = do input <- getLine let output = input ++ "!" putStrLn output putStrLn output
The language enforces that these sorts of substitutions do not change the behavior of our program (with caveats, but this is mostly true). All three of the above programs have the same behavior because we always replace one expression with another equal expression. In Haskell, the equality symbol denotes true mathematical equality.
Once we understand equality we can better understand why Haskell uses a separate symbol for assignment:
<-. For example, lets revisit this assignment in our original program:
main = do -- Assign result of `getLine` to `input` input <- getLine ...
getLine are not equal in any sense of the word. They don't even have the same type!
The type of
input :: String
... whereas the type of
getLine :: IO String
... which you can think of as "a subroutine whose return value is a
String". We can't substitute either one for the other because we would get a type error. For example, if we substitute all occurrences of
getLine we would get an invalid program which does not type check:
main = do let output = getLine ++ "!" -- Type error! putStrLn output putStrLn (getLine ++ "!") -- Type error!
However, suppose we gloss over the type error and accept values of type
IO String where the program expected just a
String. Even then this substitution would still be wrong because our new program appears to request user input twice:
main = do let output = getLine ++ "!" -- ← 1st request for input putStrLn output putStrLn (getLine ++ "!") -- ← 2nd request for input
Contrast this with our original program, which only asks for a single line of input and reuses the line twice:
main = do input <- getLine -- ← 1st and only request for input let output = input ++ "!" putStrLn output putStrLn (input ++ "!")
We cannot substitute the left-hand side of an assignment for the right-hand side of the assignment without changing the meaning of our program. This is why Haskell uses a separate symbol for assignment, because assignment does not denote equality.
input are not even morally equal.
getLine is a subroutine whose result may change every time, and to equate
getLine with the result of any particular run doesn't make intuitive sense. That would be like calling the Unix
ls command "a list of files".
Haskell has two separate symbols for
= because assignment and equality are not the same thing. Haskell just happens to be the first mainstream language that supports mathematical equality, which is why the language requires this symbolic distinction.
Language support for mathematical equality unlocks another useful language feature: equational reasoning. You can use more sophisticated equalities to formally reason about the behavior of larger programs, the same way you would reason about algebraic expressions in math.