Haskell for all

The above program reads one line of input, and then prints that line twice with an exclamation mark at the end, like this:

$ ./example HelloEnter> Hello! Hello!

Equality

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 output with 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.

Assignment

Once we understand equality we can better understand why Haskell uses a separate symbol for assignment:

main = do -- Assign result of `getLine` to `input` input  getLine . 

input and getLine are not equal in any sense of the word. They don't even have the same type!

The type of input is String :

input :: String

. whereas the type of getLine is IO String :

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 input with 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.

Also, getLine and 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".

Conclusion

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.