DEV Community

Cover image for A CRUD journey in Haskell, part I, introduction
Leandro Proença
Leandro Proença

Posted on • Originally published at leandronsp.com

A CRUD journey in Haskell, part I, introduction

In the past few days, I've been learning some Haskell after years of seeing people praising this programming language, be it about its expressiveness on writing programs or its pureness of being a "pure" functional programming language free of side-effects.

Expressiveness

After playing a bit with Haskell, I could realize how much I like such expressiveness in a programming language.

Additionally, I have backgound programming in Ruby since 2010 and Elixir since 2016. I always loved the way I can write declarative and readable code in those languages, which makes them my 2 preferred languages by far.

Then I met Haskell.

Types

In the late 2000's, I started my career programming in Java. Hence, I have some familiarity working with type declarations and how to use types to ensure some level of correctness.

However, when learning Haskell, I could realize there's much more I could learn on types. After more than a decade doing mainly duck type programming, I feel I have to do a "mental shift" in order to master types.

Thankfully, [Haskell does a great job on type inference](https://en.wikipedia.org/wiki/Haskell_(programming_language), which will ease my process on using types.

Hello World

In this blogpost series I'll assume you already have the Haskell environment installed. In case you want to save your time and your host storage utilization, you can use Docker.

A simple hello world using the ghc:

ghc -e 'putStrLn "Hello World in Haskell!"'
Enter fullscreen mode Exit fullscreen mode

This is the most possible simple Hello World in Haskell.

ghc on the -e mode will interpret the expression putStrLn "Hello World in Haskell", compile it to native code and execute it, all in a single command.

What's ghc?

It stands for "Glasgow Haskell Compiler". It's the most used Haskell compiler implementation, having its initial release in the early 90's.

When we write a Haskell program in a .hs source file, we use the ghc to compile our program into native code. For instance, let's make another "Hello World" but using the compilation process manually:

Hello.hs

main = putStrLn "Hello World in Haskell!"
Enter fullscreen mode Exit fullscreen mode

Then, compile the program using ghc:

ghc Hello.hs
Enter fullscreen mode Exit fullscreen mode

It will compile and generate the binary code in the output file ./Hello, which can be executed successfully:

./Hello
Enter fullscreen mode Exit fullscreen mode

"Hello World in Haskell!". YAY! 🎉

Skipping the manual compilation process

Remember our first Hello World example, skipping the manual compilation process? The -e option can be a valid Haskell expression, hence function calls are eligible for that:

ghc app/Hello.hs -e main
Enter fullscreen mode Exit fullscreen mode

The same as calling:

ghc -e 'putStrLn "Hello World in Haskell!"'
Enter fullscreen mode Exit fullscreen mode

A Quicksort example

Let's write a simple, not-optimized Quicksort algorithm:

Quicksort.hs

quicksort [] = []                                     
quicksort [pivot] = [pivot]                                

quicksort (pivot:tail) =                                   
  (quicksort [smaller | smaller <- tail, smaller <= pivot])
  ++ [pivot] ++                                            
  (quicksort [larger | larger <- tail, larger > pivot])    
Enter fullscreen mode Exit fullscreen mode

Now, in order to run the algorithm, it's only a matter of:

ghc Quicksort.hs -e "quicksort [5, 13, 8, 1, 2, 1, 3]"

# [1, 1, 2, 3, 5, 8, 13]
Enter fullscreen mode Exit fullscreen mode

I'll skip explaining the Quicksort solution, but in Haskell we basically used the following features:

Using the REPL

Haskell comes with a great REPL called GHCi. From the GHCi interpreter, we can load modules and execute their functions right from there:

Enter the REPL:

ghci
Enter fullscreen mode Exit fullscreen mode

Then, inside the REPL, do some math:

ghci> 1 + 1
2
Enter fullscreen mode Exit fullscreen mode

Load the Quicksort module:

ghci> :load Quicksort.hs
[1 of 1] Compiling Main       ( Quicksort.hs, interpreted )
Ok, one module loaded.

ghci> quicksort [2, 3, 1]
[1, 2, 3]
Enter fullscreen mode Exit fullscreen mode

And quit the REPL:

ghci> :quit
Enter fullscreen mode Exit fullscreen mode

Prelude

Inside the REPL, Haskell loads automatically the basic Prelude module and some standard libraries, such as IO and Complex. Because of that, any function belonging to the Prelude module will be acessible via REPL.

# implicit
ghci> putStrLn "hello from GHCi"

# explicit
ghci> Prelude.putStrLn  "hello from GHCi"
Enter fullscreen mode Exit fullscreen mode

Writing Unit tests

Testing is a good engineering practice agnostic to technology or tooling. Haskell is no different and the community created a great tool for Unit testing called HUnit, inspired by the Java JUnit.

Since it's an external package, we must download it and import the module Test.HUnit in order to run our first unit test in Haskell.

We will use Stack which comes with a good tooling for creating and managing complex projects in Haskell.

First of all, let's setup Stack so it will install a ghc in an isolated place and give us a buch of useful commands for using packages:

stack setup
Enter fullscreen mode Exit fullscreen mode

Now we are able to install the HUnit package:

stack install HUnit
Enter fullscreen mode Exit fullscreen mode

Following, the unit test:

Tests.hs

import Test.HUnit                                 

simpleTest = TestCase (assertEqual "1 equals 1" 1 1)
Enter fullscreen mode Exit fullscreen mode

And then, we can run our test:

stack ghc -- Tests.hs -e "runTestTT simpleTest"
Enter fullscreen mode Exit fullscreen mode

Which results in:

Cases: 1  Tried: 1  Errors: 0  Failures: 0
Counts {cases = 1, tried = 1, errors = 0, failures = 0}
Enter fullscreen mode Exit fullscreen mode

Notes:

  • calling stack is needed so Stack will load the HUnit module into the ghc
  • the Test.HUnit module defines a function called runTestTT which we use to run our test

We can even define multiple TestCases at once:

import Test.HUnit                                              

simpleTest = TestCase (assertEqual "1 == 1" 1 1)                
mathTest  = TestCase (assertEqual "10 / 5 equals 2" (10 / 5) 2)

tests = TestList [
    TestLabel "simple" simpleTest, 
    TestLabel "math" mathTest]                                   
Enter fullscreen mode Exit fullscreen mode
stack ghc -- Tests.hs -e "runTestTT tests"
Enter fullscreen mode Exit fullscreen mode

Or, if preferred, multiple assertions per TestCase:

import Test.HUnit                         

tests = TestCase $ do                     
  assertEqual "1 == 1" 1 1                
  assertEqual "10 / 5 equals 2" (10 / 5) 2
Enter fullscreen mode Exit fullscreen mode

Wrapping Up

In this post we've seen a gentle introduction to the Haskell programming language, writing simple programs using GHC, GHCi and Stack. We also covered how to setup and write unit tests, which are very important to every real-world project.

I didn't cover type declarations, but it will be covered in future posts.

Moreover, in the upcoming posts, we'll do some Socket programming and create a simple TCP server in Haskell, being the base ground of our simple CRUD application.

Top comments (0)