DEV Community

loading...
Cover image for First steps with Haskell

First steps with Haskell

Aldinei Lopes Bastos
Rio de Janeiro, Brazil
Updated on ・8 min read

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 case addition) to combine corresponding elements of two lists (in this case fibs and tail 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.

Discussion (4)

Collapse
sshine profile image
Simon Shine

Excellent walkthrough of creating one's first project!

Have you made some useful projects since?

Collapse
limitedeternity profile image
Marise Hayashi

You weren't meant to roast him like that :D

Collapse
sshine profile image
Simon Shine

It wasn't meant as a roast. :)

I think doing a write-up like this is immensely useful for one's own learning, and it may provide a shorter path for everyone else. So my question was: Now that you've carved a path, what are your second steps?

One suggestion is to solve one of the issues in majjoha's passphrase project that was recently featured in Haskell Weekly.

Collapse
wayneseymour profile image
Tre'

Thanks so much for this. I've has LYAH on my desk for a couple of months. Thanks for motivating me to open it! Great post!

Forem Open with the Forem app