Haskell
The problems
It is a bit strange to start with “problems” but lets make everything clear before we go.
There are a few serious problems with the current manifestation of the Haskell ecosystem (the language itself is fine).
- Tremendous amount of low-effort written bullshit by unqualified narcissistic idiots.
- Unnecessary and redundant poor abstractions by unqualified narcissistic idiots.
- Lots and lots of stupid attention seeking uneducated Chuds trying to show off.
- Absolutely horrific, barely usable tools like
hls
as the direct consequence of the facts listed above.
This gives Haskell
as an ecosystem, not as a language, rather bad reputation and
this is, unfortunately, justified.
The good news is that we do not have to use these redundant abstractions, over-“designed” libraries, and bloated, low-effort tools.
Well, we have to use some necessary tools (cabal
, stack
, and even hls
), just
turning away and not really looking at how bad and crappy they actually are.
The language itself is beautiful and nearly perfect.
Books
Because Haskell
is so subtle and requires at least some mathematical maturity,
there are lots of “very bad” books, “just bad” books and a very few good books about it.
The “very bad” books are by the guys who came from imperative languages. They are simply inadequate. The “just bad” books are low-effort crap by the “I know it all” Chuds, who never studied mathematics but have an opinion about everything.
The “very few” good books are by the real math guys and the co-authors of (or contributors to) the language specification. Read everything by Simon Peyton Jones, Graham Hutton, Paul Hudak and Richard Bird.
Principles
When you write Haskell
expressions (everything in Haskell is an expression) you are writing mathematics or terms of an advanced system of logic. Everything is declarative and pure.
The key to understand what is the fundamental difference is to realize what declarative means.
Suppose you are writing a very detailed instructions for your younger sister how to cook a noodle soup.
The principle is that writing what to do (what has to be done) and actually doing it (cooking on a stove) are very different processes. Actually, they have nothing in common.
When you write what has to be done eventually, you never see or touch the actual ingredients, like chicken meat, do not measure or change the temperature or add more salt.
What you do is only describing with words how some one else, maybe you but later, should do it. This means you declare what shall be done eventually, some day, by someone else.
The ultimate difference is that instead of using plain English, you have to use this particular programming language, which restricts what can you say and has its own rules of grammar.
Also you are speaking as a mathematician - always precise and always with a well-defined terms, using mathematical and logical expressions instead of imperative statements.
Telling to a robot
Imagine that you are telling what has to be done to a robot, to a machine (which is what an interpreter actually is).
Unlike we communicate to other people in real life, here we do not shout out commands or issue statements. We use expressions and “literals” which denote some value - a function (which is a value) or any other value of some data-type.
There are, for convinience, literals which denote numbers, individual chacters, whole strings of them and functions (the lambda syntax). These evaluate to themselves (to what they denote).
Again, everything in Haskell
is an expression, which denotes a value of some type. There are differnt kinds of expressions, and “literals” are of a special kind. No commands. No statements. Nothing.
Because this is a robot you have to be absolutely precise about every single detail. The absolutely precise languages are mathematics and logic, so you will write both mathematics and logic using Haskell
.
The declarative, descriptive approach, however, is still applicable to all Haskell
code. This, it turns out, is the only way to wirite instructions for a robot, which has no notion of time.
You declare, using pure Haskell
expressions, what has to be done eventually with values, but never actually “see” or “touch” any “actual” value. They even aren’t out there.
This is exactly the same as when you write some mathematics. You might say, there is a function
\[x \mapsto 3x + 6\] or \[x \mapsto 3(x + 2)\] which are tow different expressions denoting the same function. Here you’re telling what to do to a computer (a person) – multiply by 3 first and add 6 to it.
Here you never “see” or “touch” what the actual /value of \[x\] may or will be (only its type – to which Set of numbers it belongs). You don’t have to. You don’t need to. It not even relevant to the definition of these expressions.
Mathematical expressions denote either functions or numbers or describe properties of such abstract mathematical objects. Similarly, Haskell
expressions denote values, functions and data types.
This expression, for example, denotes an infix function composition operator, which itself is just a pure function:
(g . f) = \x -> g (f x)
It says that the (.)
operator is defined as follows. No more, no less. A declaration of the fact.
Eventually
Ok, now how do we define what to be done with values, which we never actually observe, eventually?
It is easy. We tell to the language rutime (which will eventually run our program) that we want to scrutinize the value (we still can’t and never will observe it) and we declare what has to be done for each potential outcome, for every possibility.
Again, we do not actually do, that would be an imperative crap, we are desctibing what has to be done for each possible outcome of a scrutiny. Beautiful, isnt it?
Multiple clauses
Case analysis
Pattern-matching
Functions
We define functions using equations – two expressions on each side of the equal sign. They both denote the same value and can be substututed for each other. This is the principle – substutution of an equal for an equal.
So, the expression above declares that whenever we see (g . f)
we can /substitute it with \x -> g (f x)
.
We evaluate these functions (and other expressions) using the substitution model. We could do it with a pen and paper, just like arithmetic and simple algebra, and this is another principle.