You do not need previous programming skill to learn Haskell. What you need is school math skill, and only part of it—it is not like you will be solving quadratic equations, but you will be doing:
If you are given b*c = d^2 then when you see d + b*c, you are able to deduce d + b*c = d + d^2
Equality is symmetric. Now that you know d + b*c = d + d^2 you are able to deduce d + d^2 = d + b*c
Perhaps you don't know or care how to simplify d + d^2. Perhaps you say, you are interested in XML processing rather than Project Euler number crunching. That's fair. Then d may be an XML tree, d^2 may mean a larger tree built from duplicating d, and d + d^2 may mean an even larger tree built from d and d^2, which is the same as building from d and b*c. The same skill of following equalities applies.
When you see f (d^2) and you are given f(x) = x+x then you know that the “x” there is a placeholder for plugging in: you are able to deduce f (d^2) = d^2 + d^2 When you are also given g(x) = x * 3 then you are able to deduce g (f (d^2)) = f (d^2) * 3 = (d^2 + d^2) * 3
Equal. Not “return”. Equal. Sameness. f (d^2) does not “return” d^2 + d^2. f (d^2) equals d^2 + d^2, they are the same.
You will see polymorphic types in Haskell. Here is one:
t -> [t]
The “t” there is a placeholder for plugging in types. You can plug in Int to get
Int -> [Int]
You can plug in Bool to get
Bool -> [Bool]
You can plug in a type that contains placeholders, for example [s], to get
[s] -> [[s]]
But you will not get
Int -> [Bool]
Just like with f(x) = x+x, you can get
f (d^2) = d^2 + d^2
f (5) = 5 + 5
but you will not get
f (?) = d^2 + 5
It is the same principle. You must be able to plug in, and you must plug in
coherently.
Long before there was Liskov's substitutability principle, there had already been Leibniz's substitutability principle. (It's Leibniz's law, but “substitutability principle” is more catchy.) Customized to programming, it requires:
If x equals y, then replacing x by y in a program preserves program behaviour (except possibly for computational cost).
For example, given f(x) = x+x as above, you are able to deduce
f(rand(6)) = rand(6) + rand(6)
rand(6) + rand(6) = f(rand(6))
Now add Leibniz's subtitutability principle: replacing f(rand(6)) by rand(6) +
rand(6), or the other way round, preserves program behaviour.
You can use this to help predict program
behaviour: when you see f(rand(6)) during code review, you can think of it as
rand(6) + rand(6) and continue reading (or maybe you prefer the other way
round). You can use this to refactor your program: when you see rand(6) + rand(6)
during code cleanup, you can refactor it to f(rand(6)) (or maybe you prefer the
other way round).
This forbids rand(6) from being different numbers at different times (e.g., random number) or performing I/O. If rand(6) is 3 the first time and 4 the second time, then f(rand(6)) is 3+3 but rand(6) + rand(6) is 3+4. If rand(6) says hello, then f(rand(6)) says hello once but rand(6) + rand(6) says hello twice. As it happens, most programmers prefer rand(6) to give random numbers and perform I/O; as a result, they have to say “return” because they don't have “equal”, they lose the predictive power of Leibniz's simple principle, and they lose an important refactoring.
Haskell upholds Leibniz's substitutability principle by a tamed story for effects like random number generation and I/O. I won't describe it here; you will have to learn Haskell for it. Also, Haskell just shows one approach, there are many others.
So far you have been told what to plug in (e.g., you were told to plug d^2 into x); but you are not always told. Unification refers to figuring out what to plug in, without being told.
In school math, you are given this law: x + x = 2 * x When you apply this law to d^2 + d^2, you are able to deduce d^2 + d^2 = 2 * d^2 There are two steps in this deduction. The second step is plugging d^2 into x, and we have gone over that. The first step is deciding that it is d^2, not d, not 2, not d^2 + d^2, not something else, that you plug into x. This step is unification: to compare “x + x” with “d^2 + d^2” and find that they match up (“unify”) if you plug d^2 into x.
The skill of unification is used in Haskell programming in three ways. The first way is exactly like the example above: when writing programs and predicting program behaviour, you will apply some laws (a few from school math, but most from Haskell itself), and so you will have to figure out what to plug in to use those laws.
The second way concerns functions defined like this:
h (x:xs) = x + h (xs)
h ([]) = 0
When you see h (3:(5:[])), you are able to compare “x:xs”
with “3:(5:[])” and find that you should plug 3 into x, 5:[] into xs.
Then you can deduce
h (3:(5:[])) = 3 + h (5:[])
(If you like, you may continue expanding h (5:[]), etc.)
The third way concerns type inference, and this is when unification can get really hard—exponential time for computers, so nevermind humans. For an easy example though, let us infer the type of this function: ext(x) = if b then ('c',x) else (x,'d') Assume that b is defined elsewhere and has the correct type, Bool. The interesting point is inferring the type of x; call it t for now and solve for it. Then ('c',x) has type (Char,t), and (x,'d') has type (t,Char). Haskell's if-then-else requires the two branches to have the same type. Comparing (Char,t) with (t,Char), we must plug Char into t to make them match. Therefore, x's type is Char, and ext's type is Char -> (Char,Char).
For an example of type errors: err(x) = if b then ('c',x) else (x,True) This leads to trying to match up (Char,t) and (t,Bool), and trying to plug both Char and Bool into t. Since Char and Bool are different types (as opposed to aliases for the same type), this is a type error.
You may ask, the computer already infers types for you, why do you need to do it yourself? My answer: you don't need to, if you never make type errors. The day you make type errors, you will have to do it yourself to see the details.
I promised at the beginning that you will not be solving quadratic equations. But you will be solving type equations.
If you find the prerequisite skill trivial, good for you. Don't laugh, a lot of other programmers do not possess that skill, and they are proud of it.
I once saw an advertisement from a community college. It presented a student who flunked school math as a role model for programmers. It quoted the student: “my highschool teacher said that I would need math for programming; all the same, I was already writing assembly code in my basement”.
I am sure he is a proficient assembly programmer. All the same, he will not be able to learn Haskell.
I have more Haskell Notes and Examples