me
Introduction to Haskell
Feb 2, 2017
6 minutes read

I came to discover Haskell is a wonderful programming language. The fact that it is statically typed and purely functional grabbed my attention (it is also described as a declarative language, regardless of how impossible it is to read).

This blog is an open record of how I came to create a simple CLI timer with Haskell and what I learned from it.

timer

GETTING STARTED

Ridiculously painless. First, read as much as you can of Learn You A Haskell. It is the best programming book I’ve ever read.

When you’re ready to code, go here and download the latest version of the Haskell Platform.

On top of installing Haskell, it also gives you GHC to compile the code. It comes with an interactive environment for the terminal. Type ghci to play around.

Lastly, the platform comes with Cabal. Cabal is not a package manager, even though it contains information about the packages. It is part of the ecosystem, along with HaskellDB and cabal-install to manage your packages.

All and all, I used Cabal to get the libraries and packages that i needed (like a package manager). I also used Stack. It is a wonderful program for developing Haskell apps. It scaffolds a beautiful app with tests. Install by running:

curl -sSL https://get.haskellstack.org/ | sh

Then use it with the following commands:

stack new my-project
cd my-project
stack setup
stack build
stack exec my-project-exe

DEVELOPMENT

Type stack new my-project to get started. When you generate a new project, Stack gives you the following:

.
├── LICENSE
├── Setup.hs
├── app
│   └── Main.hs
├── my-project.cabal
├── src
│   └── Lib.hs
├── stack.yaml
└── test
    └── Spec.hs

Like Java and other compiled languages, the main package contains the main function of the application. You will notice a Lib package being imported in Main.hs. This is an example of importing custom packages from the src directory. You can create a package by creating a file in the src directory and adding it to the .cabal file’s exposed-modules.

I found myself editing the *.cabal file quite often. This is where your project information lives and you specify the package imports.

For importing third party packages, install them using cabal and then add them to the build-depends. For example, I used hspec for tests, so I ran cabal install hspec and then added hspec == 2.* to the dependencies. Here you can find an example of my .cabal file and the tests.

To create your own packages, start by defining the module name, the functions to by exported surrounded by parenthesis, and the keyword where. This is the example from the Lib package:

module Lib
    ( someFunc
    ) where

someFunc :: IO ()
someFunc = putStrLn "someFunc"

Then, when the package is imported, all the exported functions will be available globally.

I only had a problem with certain packages being hidden by Cabal that Stack wasn’t able to access. If this ever happens, I worked around it by including a flag in the build command:

stack build --ghc-options="-package [container-name]"

SYNTAX

Again, I highly suggest reading Learn You A Haskell to explore the language. I do want to highlight a few areas that stood out to me:

Optionally Statically Typed

lpad :: Int -> [Int] -> [Int]
lpad m xs = replicate (m - length ys) 0 ++ ys
    where ys = take m xs

In the example above, the first line defining the function is lpad :: Int -> [Int] -> [Int]. This is the syntax for declaring the input and output of the function, first the name, then the :: and then the parameters separated by ->'s. The last type is the output. In this example, the function lpad takes an integer and a list of integers and returns another list of integers.

This is completely optional though. It is considered good practice to give functions type declarations but it is not required. The compiler will do just fine without it. Read this chapter for more information.

Extensive List of Operators

Haskell, and other functional languages, are mathematical and full of operators. Here is a list of them.

I find them unwelcoming when learning the language. They simplify the code to an extend that it looks beautiful but quite difficult to read. As a beginner, I found myself constantly looking up their definition.

My favorite ended up being the $ that is an abstraction of ()'s in the way of grouping the order of execution. sum (1 * 8) is the same as sum $ 1 * 8

The Importance of Pattern Matching

In the Syntax in Functions chapter, you can see Haskell’s ability to do pattern matching. There are so many ways to do if statements.

You can define a function to behave differently depending on the input with standard if statements, guards, or more interestingly, the way you define the function. Here is an example from the book:

sayMe :: (Integral a) => a -> String
sayMe 1 = "One!"
sayMe 2 = "Two!"
sayMe 3 = "Three!"
sayMe 4 = "Four!"
sayMe 5 = "Five!"
sayMe x = "Not between 1 and 5"

If the input is 1, the function will return “One!”, if it’s 2, “Two!, and so on. In the last case, it will handle any other input as a variable x.

Flexible Inputs and Outputs

In the code example above, you may have noticed the type a in the function declaration. This is Haskell’s Generics. They are great for allowing flexible inputs and outputs in a function. That means that the function sayMe could take any type of input.

Haskell also has a Maybe and Either type. This I found really interesting, where you define the possibility of the outcome. In the case of Maybe, you get Just the type you defined or Nothing back.

f::Int -> Maybe Int
f 0 = Nothing
f x = Just x

With Either, you Either get the Right or the Left type.

f1 :: Int -> Either String Int
f1 arg = if arg == 42
             then Left "can't work with 42"
             else Right (arg + 3)

Maybe and Either output flexibility is great for error handling.

CONCLUSION

Haskell is a blast. I must admit, I made rookie mistakes of trying to define an accumulator variable at global scope and trying to change the nth number of a list. In functional programming, there is no state or mutations.

The way of thinking is different. More recursion, math, modularity, and simplicity. I like the process of composing my code, brick by brick. Refactoring and modification was a peaceful process.

I encourage all to give it a shot.

gopher


Back to posts


comments powered by Disqus