One year of Haskell celebration!
Hello reader, October 2021 I completed one year of haskell programming, and to celebrate it, I'm going to go through six(one for each two-month interval of the year) features of this wonderful language.
Haskell has a fascinating history it all began in a committee during the 90s, where many researchers in the functional programming field got together to create a common ground language to help on their research process instead of everyone creating its own language for specific purposes and this committee counted with people from many universities, especially the Glasgow university.
Haskell nowadays is considered the state of the art in functional programming and aged very well, adding features that I never saw in any other programming languages before(haskell was my first functional language).
Type inference
Haskell in my opinion group the best of two worlds into creating a strong, immutable, and statically typed language but also without the necessities of depending on the programmer's effort to specify the types correctly, when I saw it for the first time I fell in love completely.
mySum x y = x + ymySum 3 3 -- outputs 6
The code above compiles because Haskell's compiler can use type inference to figure out that I'm trying to perform an arithmetic expression, so I need two numerical values in a language like JavaScript I would be able to pass any type of value to the function and in a language like java I would need to write the types more like: “x :: Integer” depending only on the capacity of the programmer to inform the correct types.
I thought I hated static types before haskell, but using types as a way of expression is so delightful that I risk saying that haskell was the only language I coded that got type systems the right way.
Recently I saw a talk by Bruce Tate (7 languages in seven weeks) where he shared the same opinion about haskell: “I thought I was allergic to static types until I met haskell” and it was wonderful to see that I'm not the only one who thinks that way.
Algebraic Data Types
Normally programming languages have their primitive types(int, string, bool) that we can use to compose more complex data structures, but this is certainly much more increased in haskell where I can build my own type from the mathematic foundations of a type and also use recursion inside my type definition.
data BTree = Empty | Node Int BTree BTree
In the previous example, we created a binary tree type that will hold either the value of a Node(an integer value) plus a recursive tree type for the left and the right side of the tree or an Empty like an empty node of the tree
let tr = Node 3 (Node 2 Empty Empty)(Empty)
If we type:
print tr
We will get an error because haskell datatypes let us customize types with instances and to print a custom datatype we need to derive this type from the ‘Show’ type class like this:
data BTree = Empty | Node Int BTree BTree deriving(Show)
Now our print statement should work properly, haskell contains many more type derivations like for example to compare
Empty == Empty
Node 3 == Node 3
Node 0 == Empty
It will result in another error, and we’ll need to specify a derivation:
data BTree = Empty | Node Int BTree BTree deriving(Show, Eq)
And if we want to customize we just do like this:
instance Eq BTree where(==) Empty Empty = True(==) Empty (Node 0) = True(==) (Node 0) Empty = True(==) (Node x) (Node y) | x Prelude.== y = True(==) _ _ = False
Expressivity
At first, a language without ‘{’, ‘}’ symbols and focused on composing functions, may look a bit alien, but you get adapted to haskell syntax fast and get that sensation of expressivity and readability, the sensation of doing more with less boilerplate code.
sumF v1 v2 = v1 + v2
mult v1 v2 = v1 * v2main = do
print $ sumF 2 $ mult 3 2
As the example shows, we can compose function calls(sumF and mult) without any ‘(’, ‘)’ characters in a more imperative language we would have to pollute our code into something more like this:
const sumF = (v1, v2) => v1 + v2
const mult = (v1, v2) => v1 * v2const main = () => {
console.log(sumF(2, mult(3, 2)))
}
Haskell code looks more like a text in a book, without all the boilerplate code that is required to do proper syntactic analysis in more imperative languages.
Lazy evaluation
A wonderful and fascinating feature that I saw for the first time in haskell, normally imperative languages compute everything sequentially, it helps them to get previsibility and put everything that is needed, but haskell evaluation method is different, being lazy means that haskell will not evaluate expressions sequentially, it means that instead haskell will compute expressions and load data into memory only when it is needed and if it is exclusively necessary.
In the following example in JavaScript we can notice that the example will generate an error because we called a function that throws an error in the middle of its execution because of sequentially evaluation
const fnError = () => {
throw new Error('error')
}const main = () => {
let err = fnError();
console.log('end')
}
While in haskell the error never happens because we are adding the value of the call of ‘fnError’ to the ‘err’ variable, but haskell compiler will check if err is necessary for the execution of the program before evaluating the error and then the compiler realizes that the ‘err’ variable is not being used anywhere, so the compiler just discards it without evaluating any result or error and goes straight to evaluate the ‘print “end”’ statement.
fnError = error "any error"main = do
let err = fnError
print "end"
Auto Currying
Currying functions are a very common practice in functional programming, and it basically consists in returning a function as a return value guaranteeing the scopes of each action, and haskell gives it to us for free by automatically currying all our functions
Let's look at our previous function “sumF”:
sumF v1 v2 = v1 + v2
It looks like a function that gets two numerical arguments and returns the numerical sum of the two values, but what haskell do under the hood is:
sumF v1 = \v2 -> v1 + v2sumF 3 4
7
In a more imperative language like javascript the code would look a little like this:
const sumF = v1 => v2 => v1+v2
Purity
The promise of no side effects may be a little far from our sight as real-life programmers, but this is not the case of haskell programmers in most part of the time
From a simple hello world, to a web server and a database query, those are all actions that depend highly on IO and change of state in our programs, haskell was able to solve that problem by adding the concept of monads as boundaries to these uncertain, evil and chaotic IO actions.
By introducing monads the purity is achieved by wrapping an IO action inside an IO monad, an inescapable cage for a side effect that makes an IO action only able to modify itself and receive manipulations inside this IO cage allowing pure functions to remain pure by not allowing the interaction between pure, secure and predictable functions with uncertain and dangerous IO actions
ioAction = do print "hello world"
The do keyword means that we are opening an IO monad and that we can perform actions with side effects freely inside this monad.
But if I try to do the following computation:
ioAction = do 3sumF v1 v2 = v1 + v2sumF ioAction 3
I'll get an error because ioAction function is not returning the numeric value three, the ioAction function is returning a numeric value wrapped inside a monadic cage(IO Int) from where I cannot free the pure value because it may contain side effects that can corrupt my program, and when I try to call sumF with an IO Int value I get an error of unexpected types for trying to sum a monadic value in a pure numerical function.
It shouldn't mean that I cannot manipulate my side effects anymore because I can do it in another monadic cage like:
ioAction = do 3ioAc = do ioAction + 3ioAc
6
Because I'm in a cage context, I can manipulate all the side effects I find necessary without the risk of corrupting my pure functions.
And I also can call pure functions from an IO cage using this technique:
ioAction = do 3sumF v1 v2 = v1 + v2main = do sumF ioAction 3
main
6
I'm still in a cage context so haskell will unwrap the IO Int monad from ioAction in main IO cage and will call sumF with the arguments 3(from previous monadic context) and 3(pure numerical value) and with the successful result of 6 main will wrap 6 in an IO Int monadic cage.
Looks like we get to the end of this article, and it is always a pleasure for me to write about haskell, I'm a very skeptical person and when I learned haskell it looked like the holiest experience I had in years and the fact that it was all immersed in mathematics and lambda calculus made it all so beautiful and delightful.
After this brief journey on haskell I can only hope you consider haskell on your learning path, it may certainly surprise you and show you things that you don't see much without the lens of functional programming.
Hope to see you in the next posts, goodbye reader.