First steps with Haskell
Pinei
Posted on August 19, 2020
This is my first article at Dev.to. I'm not used to writing in English, but I am thinking of get some practice while learning pure functional programming with Haskell.
Haskell is a 90's programming language characterized by:
- Multi OS (Windows, Mac, Linux)
- General purpose (like C#, Java, C, C++)
- Purely functional (like Elm)
- Strong static typing (like Java, C, C++)
- Type inference (like OCaml, Haskell, Scala, Kotlin)
- Lazy evaluation
- Concise programs
Haskell has a diverse range of use commercially, from aerospace and defense, to finance, to web startups, hardware design firms and lawnmower manufacturers (Haskell Wiki)
The language is widely known as a functional programming language which is great to write pure code and immutable data structures. It has a great support for a wide variety of concurrency models too.
Functional programming is a style of programming in which the basic method of computation is the application of functions to arguments, encouraging us to build software by composing functions in a declarative way (rather than imperative), avoiding shared state and mutable data.
In a pure functional language, you can't do anything that has a side effect. As a benefit, the language compiler can be a set of pure mathematical transformations. This results in much better high-level optimization facilities.
A side effect would mean that evaluating an expression changes some internal state that would later cause evaluating the same expression to have a different result. In a pure functional language we can evaluate the same expression as often as we want with the same arguments, and it would always return the same value, because there is no state to change. (What does “pure” mean in “pure functional language”?)
In Haskell, even functions based on system inputs and outputs (like print
, getChar
and getSystemTime
) are pure. They handle IO
actions (where IO is a monad, like Javascript Promises
is) which can be combined and performed by runtime. We can say that getSystemTime
is a constant which represents the action of getting the current time. This action is the same every time no matter when it is used. (IO Inside)
A language is statically typed if the type of a variable is known at compile time. For some languages this means that you as the programmer must specify what type each variable is (e.g.: Java, C, C++); other languages offer some form of type inference, the capability of the type system to deduce the type of a variable.
The main advantage here is that all kinds of checking can be done by the compiler, and therefore a lot of trivial bugs and a very large class of program errors are caught at a very early stage. (What is the difference between statically typed and dynamically typed languages?)
Lazy evaluation means that expressions are not evaluated when they are bound to variables, but their evaluation is deferred until their results are needed. When it is evaluated, the result is saved so repeated evaluation is not needed.
Lazy evaluation is a technique that can make some algorithms easier to express compactly and much more efficiently. It encourages programming in a modular style using intermediate data structures, and even allows data structures with an infinite number of elements. (Programming in Haskell)
Imagine an infinite data structure like a Fibonacci list. We don't actually figure out what the next element is until you actually ask for it. (Fibonacci, Using Lazy Evaluation)
let fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
take 10 fibs -- [0,1,1,2,3,5,8,13,21,34]
fibs!!10 -- 55
We have to understand some functions and operators to interpret the code above.
- the
:
operator prepends an element to a list -
tail
function returns a list without its first element -
zipWith
uses a specified function (in this caseaddition
) to combine corresponding elements of two lists (in this casefibs
andtail fibs
) to produce a third -
take
returns a sized prefix of a list -
!!
is the list index (subscript) operator, starting from 0
Another nice example is a definition for an infinite list of integers (starting at 2) where each number is not divisible by any previous number in the list.
:{
primes = filterPrime [2..]
where filterPrime (p:xs) =
p : filterPrime [x | x <- xs, x `mod` p /= 0]
:}
take 10 primes -- [2,3,5,7,11,13,17,19,23,29]
Due to high-level nature of the functional style, programs written in Haskell are often much more concise than in others languages. The language syntax was designed with concise programs in mind.
These are the main concepts for now. To start testing and learning, let’s get our hands dirty.
The Haskell platform
A Haskell distribution may includes tools like:
- the Glasgow Haskell Compiler
- the Cabal build system
- the Stack tool
- core & widely-used packages
Glasgow Haskell Compiler (GHC) is an open source compiler and interactive environment (aka GHCi) for the language Haskell.
Let's see some concepts from the Cabal build system.
CABAL
(the spec) is the Common Architecture for Building Applications & Libraries. It’s a specification for defining how Haskell applications and libraries should be built, defining dependencies, etc.
.cabal
(the file format) is used to write down the definitions for a specific package.
library
exposed-modules: HelloWorld
-- other-modules:
-- other-extensions:
build-depends: base >= 4.11 && <4.12
hs-source-dirs: src
default-language: Haskell2010
The Cabal library
implements the above specification and file format.
The cabal executable (cabal-install
), is a command-line tool that uses Cabal (the library) to resolve dependencies and build packages.
What about Stack?
Stack is a replacement for cabal-install
, i.e. the cabal executable. It is a build tool that works on top of the Cabal build system, with a focus on reproducible build plans and multi-package projects.
We can also count on online package repositories to complement the build system:
The Hackage package repository, providing more than ten thousand open source libraries and applications to help you get your work done
The Stackage package collection, a curated set of packages from Hackage which are regularly tested for compatibility. Stack defaults to using Stackage package sets to avoid dependency problems.
Starting with Stack
In my computer I have the brew
tool installed (a package manager for macOS and Linux)
With a simple command I was able to download and install the mentioned tools to start with Haskell.
$ brew install haskell-stack
...
🍺 /usr/local/Cellar/haskell-stack/2.3.3: 6 files, 55.4MB
A stack
command starts our Haskell Interpreter.
$ stack ghci
...
Configuring GHCi with the following packages:
GHCi, version 8.8.3: https://www.haskell.org/ghc/ :? for help
Prelude>
Prelude
is a module that contains a small set of standard definitions and is included automatically into all Haskell modules.
Let's do a simple test and quit.
Prelude> let x = 12 in x / 4
3.0
Prelude> :quit
Leaving GHCi.
The first project
With Stack and a template we can generate a start project inside our current directory, but it suggest us to config some properties in advance. We can set these properties in the ~/.stack/config.yaml
file.
templates:
params:
author-name: Aldinei
author-email: aldinei@gmail.com
copyright: none
github-username: pinei
category: Development
We can use stack new
to create our first Haskell project called haskell-kitchen
.
You can access stack-templates to search for another template. Here the default "new-template" will be used.
$ stack new haskell-kitchen
Downloading template "new-template" to create project "haskell-kitchen" in haskell-kitchen/ ...
Looking for .cabal or package.yaml files to use to init the project.
Using cabal packages:
- haskell-kitchen/
...
Initialising configuration using resolver: lts-16.10
Total number of user packages considered: 1
Writing configuration to file: haskell-kitchen/stack.yaml
All done.
The stack new
command should have created the following files:
├── app
│ └── Main.hs
├── ChangeLog.md
├── LICENSE
├── haskell-kitchen.cabal
├── package.yaml
├── README.md
├── Setup.hs
├── src
│ └── Lib.hs
├── stack.yaml
└── test
└── Spec.hs
We can see some text files and source folders (app
, src
and test
) containing .hs
files.
The package.yaml
is the preferred package format provided by Stack. The default behaviour is to generate the .cabal
file from this package.yaml
, so you should not modify the .cabal
file. It is updated automatically as part of the stack build process.
The Setup.hs
file is another component of the Cabal build system. It's technically not needed by Stack, but it is still considered good practice in the Haskell world to include it.
The project descriptor named stack.yaml
gives our project-level settings.
In the src\Lib.hs
source file, let's type a text in the function someFunc
of the Lib
module to see it printed to the output later.
module Lib
( someFunc
) where
someFunc :: IO ()
someFunc = putStrLn "Welcome to Haskell Kitchen"
Let's see the app\Main.hs
file:
module Main where
import Lib
main :: IO ()
main = someFunc
The function named main
in the module Main
is special. It is defined to be the entry point of a Haskell program (similar to the main function in C), and must have an IO type
, usually IO ()
.
Our module Lib
is imported here and the function someFunc
will be invoked.
Now let's see the test\Spec.hs
file.
main :: IO ()
main = putStrLn "Test suite not yet implemented"
It would be interesting to write some tests to learn and document some codes, but with this sample we haven't any clue about how to write the test cases. It's a subject for another article.
Let's use Stack to run our hello-world
application. Type stack build
to generate the binary ...
$ stack build
haskell-kitchen> configure (lib + exe)
Configuring haskell-kitchen-0.1.0.0...
haskell-kitchen> build (lib + exe)
Preprocessing library for haskell-kitchen-0.1.0.0..
Building library for haskell-kitchen-0.1.0.0..
Preprocessing executable 'haskell-kitchen-exe' for haskell-kitchen-0.1.0.0..
Building executable 'haskell-kitchen-exe' for haskell-kitchen-0.1.0.0..
[1 of 2] Compiling Main [Lib changed]
Linking .../haskell-kitchen-exe ...
haskell-kitchen> copy/register
Registering library for haskell-kitchen-0.1.0.0..
The binaries for the local platform (in my case x86_64-osx) are generated somewhere in the folder .stack-work
under the project folder.
The command stack exec
helps us to find the executable and run it ...
$ stack exec haskell-kitchen-exe
Welcome to Haskell Kitchen
The output is showing as expected. In a near future we can build a functional application.
For now let's test some code writing a new module in src\Calculator.hs
for simple recursive functions.
module Calculator
( double, sum, factorial, product
) where
double :: Num a => a -> a
double x = x + x
sum_list :: Num a => [a] -> a
sum_list[] = 0
sum_list(x:xs) = x + sum_list xs
product_list :: Num a => [a] -> a
product_list[] = 1
product_list(x:xs) = x * product_list xs
factorial :: Integral a => a -> a
factorial x = product_list [1..x]
We can try the functions using the GHCi environment. Execute stack ghci
in the project folder.
$ stack ghci
Using main module: 1. Package `haskell-kitchen' component haskell-kitchen:exe:haskell-kitchen-exe with main-is file: ./haskell-kitchen/app/Main.hs
haskell-kitchen> configure (lib + exe)
Configuring haskell-kitchen-0.1.0.0...
haskell-kitchen> initial-build-steps (lib + exe)
Configuring GHCi with the following packages: haskell-kitchen
GHCi, version 8.8.3: https://www.haskell.org/ghc/ :? for help
[1 of 3] Compiling Calculator ( ./haskell-kitchen/src/Calculator.hs, interpreted )
[2 of 3] Compiling Lib ( ./haskell-kitchen/src/Lib.hs, interpreted )
[3 of 3] Compiling Main ( ./haskell-kitchen/app/Main.hs, interpreted )
Ok, three modules loaded.
*Main Calculator Lib>
We now have 3 modules compiled and loaded, and we are able to invoke the functions from the Calculator
module.
The Prelude
module in the prompt gave way to a list of our loaded modules Main Calculator Lib
. We can configure the prompt using :set prompt "> "
.
*Main Calculator Lib> :set prompt "> "
> Calculator.double 3
6
> Calculator.sum [1..5]
15
> Calculator.product [2, 3]
6
> Calculator.factorial 5
120
> :quit
Leaving GHCi.
It seems our functions are working well, but we can make our project better with unit tests. I have to search for options before writing a second article in a series.
Posted on August 19, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.