Ok I'll take a stab at a super high level crash course:
A note! Haskell has weird syntax. But it actually uses this syntax in cool ways, as opposed to some languages that have weird syntax in an effort to be different.
Let's consider the basic unit of Haskell programming -- the function!
Here is a function which returns its input:
f x = x
Notice that there are no parens or types or anything. Since functions are used so heavily, polluting them with parens would make your code look uglier than lisp.
Now notice that I didn't include a type signature. In haskell, you can omit them if you want. The compiler will figure out the most general type. Think of this as C++11 `auto` keyword on steroids.
If I were to include the type signature, it would look like this:
f :: a -> a
f x = x
Let's break down those symbols.
`::` just means "is of the type". So `f :: ...` is "function f is of the type..."
`a` is a type variable. Think of this like C++ templates! In C++, an equivalent function is
template <typename T>
T f(T x) { return x; }
Where `T` is the same as our `a` in Haskell. In Haskell, it's common to use the first few letters of the alphabet for type variables.
The `->` syntax is a bit funny, but it makes sense when you learn about function currying, which I will not explain now because it can be intimidating to some. For now, just think of the type signature as
arg1Type -> arg2Type -> ... -> argNType -> ResultType
So for our function `f` of type
f :: a -> a
That means "The function f takes some argument of type `a`, and returns a value of the same type `a`"
Now let's consider a more complex function.
f x = 2 * x
As you can see, this is a doubling function. It takes a value and doubles it. We can imagine that the value it takes can't be any arbitrary type, because not all types can be doubled! So it doesn't make much sense to pass in a string.
In Java style, we might say that the type must implement the interface `Multipliable` or something. It turns out, Haskell has a system that is very roughly similar to Java interfaces or in C++, a class with a bunch of virtual methods.
Haskell calls these Typeclasses. So there is a typeclass that specifies that types in the typeclass must implement some basic numeric methods, such as +, -, etc. This typeclass is called `Num`.
So we can imagine, our doubling function only takes types that implement `Num`. Our type signature reads:
f :: Num a => a -> a
f x = x * 2
So everything between the :: and the => is typeclass specifications. So in this one, I just specify that type `a` must implement `Num`. Let's say I also required `a` to have the equivalent of a Java `toString` method, I would say:
f :: (Show a, Num a) => a -> a
So now `a` must have a `toString` style method and must have numeric methods.
Now we are ready to approach the `primes` bit.
The first foreign looking thing is [2..]. This is an infinite list from 2 to infinity. How can one have an infinite list? The answer is laziness.
Whenever I go and ask for the 10000th value, the list must extend itself to that value if it hasn't yet. So the list only grows for as much as you ask for, but in theory it will grow until you run out of memory.
The [m..n] syntax is just nice syntactic sugar because it's used so much. [1..10] == [1,2,3,4,5,6,7,8,9,10]
[1..] == [1,2,3,4,5,....infinity]
So this is what we know so far:
primes = sieve [2..]
Which means "primes is equal to the function sieve, called on an infinite list from 2 to infinity"
Now you ask, well what the hell is the function `sieve`?
Haskell has a nice sugar for defining scoped functions within other functions with the `where` keyword.
Consider the doubling function. I could instead do:
f x = x * (three - one)
where
three = 3
one = 1
So it just let's you define values that get used in the function body in a convenient place. This is akin to math jargon where people say "blah blah x something blah where x is blah".
So `sieve` is a scoped function (cannot be accessed from outside of `primes` that takes a list as an argument and apparently produces the primes that are in that list. How does it do it?
We see some weird syntax in the definition of `sieve`:
sieve (p:xs) = ...
What is this? It's pattern matching. Let me use fake C++ as an example. Consider the recursive factorial function:
int fact(int n) {
if(n == 0) return 1;
return n * fact(n-1);
}
Now imagine if C++ let you do this instead:
int fact(0) { return 1; }
int fact(int n) { return n * fact(n-1); }
That is, you make a special case function body when the argument is 0. At runtime, if the arg is 0, it uses the special case body, otherwise it falls through to the general body.
So in Haskell, we could implement factorial like
fact :: Num a => a -> a
fact 0 = 1
fact n = n * fact (n-1)
Very clean and sexy! But now you say "Yeah, but I could just use an if statement or a switch..."
Now we go into the funny syntax and power of that funny syntax! Not only can pattern matching match on values, but it can decompose those values. What do I mean? Again, let me use some fake C++ as an example.
Consider the C++ function
int f(pair<int, int> my_pair) {
int x = my_pair.first;
int y = my_pair.second;
return x*y;
}
Ignore that there is no real reason to pull the values out into `x` and `y`. In a more complex function, I'm sure you can understand why it would be tedious to type `my_pair.first` over and over again and we would want to pull it into `x` or something.
Now imagine if C++ let us do this:
int f(pair<x, y>) {
return x*y;
}
That is, f takes a pair, and
in the type declaration we decompose the pair into its first and second parts, assigning them to the variables `x` and `y`. In Haskell, this is trivial.
Consider a direct translation of the C++:
f :: (Int, Int) -> Int
f my_pair = fst my_pair * snd my_pair
Now the idiomatic Haskell, with pattern matching decomposition:
f :: (Int, Int) -> Int
f (x,y) = x*y
Slick, right?
So what is `sieve (p:xs)` decomposing?
In Haskell, the default [] list is a linked list (there are random access arrays in various libraries). That means we can stick a value onto the front of the list in O(1) time. The way we stick a value onto the front of the list is with the `:` operator.
So
x = [1,2,3]
y = 0:x
`y` is now [0,1,2,3]
Just like we can compose lists with the `:` operator, we can decompose them via pattern matching with it.
So consider the function (I'll omit the type signature for brevity)
f (p:xs) = print (p, xs)
Let's say we give `f` the argument [1,2,3,4]. What call to `:` would we have to make to get this list?
[1,2,3,4] == 1:[2,3,4]
So when we send [1,2,3,4] into `f`, it gets decomposed into ` 1:[2,3,4] ` where 1 gets put into `p` and [2,3,4] gets put into `xs`.
This is a very common idiom in Haskell. Let's say I wanted to make a function that added 1 to every value in a list. Ignoring that there are much cooler/efficient ways to do this in Haskell, let's make a specialized function for it.
addOne (x:xs) = x+1 : addOne xs
So let's read through that. In our pattern matching decomposition, we pull off the first value of the list and store it in `x`, and we take the rest of the list and store it in `xs`.
Now in our function body, we recompose a list with different values. Those values are `x+1` for the first value, and `addOne xs` for the second value. So we add one to the first value, and stick it into the front of the rest of the list after the rest has had 1 added to its values. You might notice now, that there is no bottom-out case for this recursion. So let's add one.
addOne [] = []
addOne (x:xs) = x+1 : addOne xs
So we pattern match to catch the special case of the empty list, which returns the empty list, and then we decompose the general case as previously explained.
So `sieve` is pulling off the first value, storing it in `p`, and taking the rest of the list and storing it in `xs`.
It is returning...
p : sieve [x | x <- xs, x `mod` p /= 0]
So, that is `p` stuck onto the front of...
sieve [x | x <- xs, x `mod` p /= 0]
And we know that sieve returns all of the primes within its argument, so in English...
p stuck onto the front of all the primes in [x | x <- xs, x `mod` p /= 0]
So now what the hell is [x | x <- xs, x `mod` p /= 0]? It is a list comprehension.
Hackernews said my comment was too long. Read the rest here: http://pastebin.com/UesYMjjJ