Functional Pizza - A delicious Haskell tutorial

#programming 2015-11-07

Welcome! I will help you create a pizzeria - one that works entirely in Haskell - a functional programming language. After completing this tutorial, you will know the basics of the language and have practical experience of using for problem solving. Let's get started!

Heavily work in progress, bear with me. TODO:

0. Installation

This tutorial assumes a Linux / Unix environment. If you have a *nix operating system, great! If not, please consider using a virtual machine with a distribution of your choice - setting up programming environments is considerably easier there.

Let's start by installing the basics.

You should install these packages to get started. On most Debian-based systems, this should work:

sudo apt-get install ghc haskell-stack

Some parts of this tutorial refer to Haskell packages that are not installed by default. When you need to install a package, fire up a shell and run:

stack update                # updates repository information
stack install package       # installs package

1. GHCi and basics

Let's found a pizzeria start-up!

First, we need to make sure it could be a successful business, right? Open up a shell of your choice, and start GHCi by typing ghci and enter. You are greeted with a couple of lines of information - hello, GHCi! o/

GHCi, version 7.8.4: http://www.haskell.org/ghc/  :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
Prelude>

Prelude is a bundle of included-by-default libraries and so on. Let's not let it confuse us - just know that whenever there is Prelude> in the beginning of a line in my code examples, I'm showing you something to try within the GHCi prompt. Later on, when we import more modules, the prompt will show them as well - it's rather handy, actually!

Math

Now we have a fun little interactive environment to use Haskell in. Here's some examples of basic math:

Prelude> 1+1
2
Prelude> 1/2
0.5
Prelude> 2*3
6
Prelude> 2**3
8.0

Great! Typing in an expression and pressing enter causes GHC to evaluate the expression, and you get the result back.

Exercise: Use GHCi as a calculator. Assuming making a pizza costs you 3€, how many euros profit would you get if you sell 42 pizzas for the price of 5.70€ each?

Answer:

Prelude> 42*(5.7-3)
113.4

Now a bit about the boolean operators - notice especially how in Haskell, /= is used instead of !=:

Prelude> not True
False
Prelude> not False
True
Prelude> 1 == 1
True
Prelude> 1 /= 1
False
Prelude> 1 < 2
True
Prelude> 1 > 2
False

Naming

Now that basic math shows that you can make a profit, let's get going! The starting year is 2015, alright. Your pizzeria is also going to need a name. We could try and combine some words:

Prelude> let startyear = 2015
Prelude> startyear
2015
Prelude> let name = "Functional" ++ "Pizza"
Prelude> name
"FunctionalPizza"

Exercise: If we manage to run the pizzeria for seven years, what year will it be?

Answer:

Prelude> startyear + 7
2022

Numbers aren't words and words aren't numbers - if we want to combine a number into a string, we need to make a string representation of it. This happens with show:

Prelude> name ++ " - since " ++ show startyear
"FunctionalPizza - since 2015"

Hmm. Now that we are actually doing things, maybe we should start putting some notes down and use comments.

Prelude> 1+1 -- this is a comment
2
Prelude> "foo" ++ "bar" {- this is also a comment -}
"foobar"

Lists

Manipulating lists is the bread and butter of most programming. Knowing that, the great developers have made it easy and pretty in Haskell. Here's some examples for your pizzeria's needs:

Generating lists in a puff of flour:

Prelude> [1,2,3] -- declaring manually
[1,2,3]
Prelude> [1..5]  -- declaring range
[1,2,3,4,5]

Operating the dough:

Prelude> 1 : [2,3]       -- adding to beginning of list
[1,2,3]
Prelude> [1] ++ [2,3]    -- concatenating lists
[1,2,3]
Prelude> [1..10] !! 3    -- nth element, indexed from zero
4
Prelude> head [1..3]     -- first element
1
Prelude> take 3 [1..10]  -- n first
[1,2,3]
Prelude> drop 3 [1..10]  -- elements from n forward
[4,5,6,7,8,9,10]
Prelude> init [1..3]     -- all but last
[1,2]
Prelude> tail [1..3]     -- all but first
[2,3]
Prelude> last [1..3]     -- last
3
Prelude> reverse [1..10] -- reverse
[10,9,8,7,6,5,4,3,2,1]

Strings are lists of chars:

Prelude> ['a'..'z']
"abcdefghijklmnopqrstuvwxyz"
Prelude> ['a'..'z'] !! 5
'f'
Prelude> take 6 ['a'..'z']
"abcdef"

Word of warning: what is head of an empty list? Or tail of it? Trying to make a pizza crust without dough is not going to compile into a pizza no matter how many toppings you add, you'll just make a mess of your table :( For the curious and advanced readers, Data.List.Safe might be useful.

In addition to the basic stuff, haskell allows for some fancier things. For example, if you happen to think in mathematical notation:

Prelude> let evenNumbers = [x | x <- [1..10], even x]
Prelude> evenNumbers
[2,4,6,8,10]

What happens here is we let evenNumbers be a list of x, where we take each x from a list of numbers from 1 to 10, with the additional condition that x is even.

Functions

Let's write our first functions! If a customer wants double cheese on their pizza:

Prelude> let cheese = 150 -- a regular pizza has 150g of delicious cheese mix
Prelude> let double x = 2*x
Prelude> double cheese
300

One of the fancy things in Haskell is that functions are first-class citizens. Take a look:

Prelude> let double x = 2*x
Prelude> let quad x = double (double x)
Prelude> quad 2
8

TODO: Exercise

2. Hello, world!

Main

Let's write our first standalone program - in an actual file instead of just poking at the REPL!

Open up a new file in your favourite editor, and name it as hello.hs. Paste this content in it:

-- hello.hs
main = do
  putStrLn "Hi! What's your name?"
  name <- getLine
  putStrLn ( "You look wonderful today, " ++ name ++ " =)" )

Save it, and in a terminal, say the following magic spell: runhaskell hello.hs. Watch the magic happen!

Hi! What's your name?
Reader
You look wonderful today, Reader =)

Let's take a look at what happened there.

main = do is the most common main function declaration - way shorter and prettier than in more verbose languages! Followed by lines at an increased indentation, it makes up the core part of the program.

putStrLn is a way to print text into the standard output. There is also a function named print, but it behaves a bit differently and might be slightly confusing - it ends up surrounding strings with extra quotes.

name <- getLine - here we bind a value to name, taking the value from a line of standard input. It could be tempting to call this "setting a variable", but in the Haskell world, they aren't variable but immutable. Set it once, and that is that. For the sake of understanding the tutorial, yeah, this is a bit like setting variables in other languages.

Helper functions

Now obviously, in many programs, you want other functions than just main. Let's write a program that asks for a number and returns a number twice the size. We have already done half of it when using GHCi, so this should be easy =)

A new file:

-- twice.hs
main = do
  putStrLn "Give me an offer, and I'll double it:"
  number <- readInt
  print (double number)

double x = 2*x

readInt :: IO Integer
readInt = readLn

Just like before, we have a main loop that prints some text, binds a value to a name (yeah, almost like storing a variable, people just don't call it that way in Haskell world), and prints the number doubled. There's some helper magic going on though:

double is the helper function we wrote earlier. Notice how in actual .hs source code, we don't use let in the same way as when using GHCi

readInt is a helper function that simply reads a line from stdin, like it says in the function definition: readInt = readLn.

However, what is significant is that we declared a type for it: readInt :: IO Integer means that the function returns a value of the type IO Integer.

If you're familiar with C, this is a bit like defining a helper function scanf("%d").

However, IO Integer is something that in most cases should be an integer, but because it is an IO operation and hence not "mathematically pure", it might do something completely different, like cause a side effect. This concept of pure and impure functions and values is rather important in Haskell, and you should get comfortable with it from early on.

In most cases other than reading input, type declaration for functions is completely optional: the compiler infers them. However, it can be very useful to write the types - to catch any misunderstanding between you and the compiler.

Exercise: Write a program that goes through a short discussion with the user. The program could for example ask their name, age, favourite color, and make up a nickname for the user based on that information.

Answer:

-- nickname.hs
main = do
    putStrLn "Hi! What's your name?"
    name <- getLine
    putStrLn "That's a fun name. How old are you?"
    age <- readInt
    putStrLn "That is a fine age. What is your favourite colour?"
    colour <- getLine
    putStrLn "Hm, neat colour. You know what, I think I have a fun nickname for you:"
    putStrLn (colour ++ name ++ show age)

readInt :: IO Integer
readInt = readLn

There you go, you have your first actual working Haskell program! Congratulations!

If, else

Many times, a function needs to be able to make up its mind and make a decision -- for example, if a pizza can be split for kids.

-- splittable.hs
main = do
  putStrLn "Give me the size of a pizza, and I'll tell you if it can be split for kids"
  number <- readInt
  print (splittable number)

-- an even-sized pizza is divisible into two kids pizza slices
splittable x =
  if even x
    then True
    else False


readInt :: IO Integer
readInt = readLn

Quite often, if else if pattenrs can become long, difficult to follow, and in general unpleasant to the eye. Don't worry - in Haskell, if else if pattern has been squeezed down to a single pipe character: |. And easily enough, else is just another else if with the statement otherwise grated on top as the condition. In haskell, these pipe characters are called guards.

splittable x
  | even x      = True
  | otherwise   = False

Please be careful with the indentation levels.

3. Functional thinking

A family walks into your restaurant! Now that there are real customers in the table, let's make them happy.

Kids love drinking with straws, and as soon as you arrive at the table you give her a lovely straw with lots of loops. Smiling, she thanks you and starts playing with the straw. Soon enough she asks: "How many loops are there in this straw?"

What is a better way to deal with loops than recursion! To calculate the total number of loops on a squiggly straw, we look at one loop, say "one", and add the number of the loops on the rest of the straw. So we look at the next loop, add the "one", and the number of...

-- straw.hs

-- recursion end condition, nonexistent loop has zero length
strawLength [] = 0
-- pattern matching
strawLength (loop:loops) = 1 + strawLength loops

straw = ["squiggly", "round", "reverse", "figure-eight"]

main = do
  print (strawLength straw)

-- `runhaskell straw.hs` returns 4.

Handing the menus - a menu for each member of the family - is a job for map: it takes a function and a list, and applies the function to all elements on the list. Map is basically a for loop, but simpler:

Prelude> let family = ["Ma", "Pop", "Kiddo"]
Prelude> map (++ "menu") family
["Mamenu","Popmenu","Kiddomenu"]

Perhaps the kid is a bit picky: "I don't want any green circles". Fair enough, our chef (that's you!) will filter them out:

Prelude> let slice = ["cheese", "tomato sauce", "olive", "ham", "olive"]
Prelude> filter (/="olive") slice
["cheese","tomato sauce","ham"]

Orders are taken, let's look at the waiter's note - do some line parsing:

Prelude> let note = "mozza\nsalami\ngreek\njug of iced tea\n"
Prelude> -- lines - newline-separated values into a list
Prelude> lines note
["mozza", "salami", "greek", "jug of iced tea"]

Prelude> -- unlines - list into newline-separated data
Prelude> unlines ["mozza", "salami", "greek", "jug of iced tea"]
"mozza\nsalami\ngreek\njug of iced tea\n"

After the family has eaten, it is time to pay up. The amount to pay depends on the price of the individual items ordered, but the amount of tax imposed on restaurant food is the same. We can half-define a multiplication function that is basically "multiply by 1.14" - but doesn't yet know what to multiply. In Haskell lingo, this is called currying:

Prelude> let afterTaxes = (*1.14)
Prelude> afterTaxes 24
27.36

That last bit may have sounded a bit odd. Have some background here on Wikipedia - likely to have a stricter dress code than our cozy pizzeria. You have been warned!

4. Data

Let's define our own data type -- a pizza!

Let's declare that a pizza from our pizzeria is something that has a name, three toppings, and optional garlic and oregano.

data Pizza = Pizza {
    name          :: String,
    mainTopping   :: String,
    secondTopping :: String,
    thirdTopping  :: String,
    garlic        :: Bool,
    oregano       :: Bool
    } deriving Show

It really is as easy as that. You declare names for your fields and the data types of what they contain. deriving Show at the end practically means that our Pizza is printable. (Isn't it neat where 3d technology is getting us?)

Now let's define our first pizza - a delicious item on our menu called "Mozza".

-- setting data, in order
mozza = Pizza "Mozza" "Mozzarella" "Pesto" "Tomato" False False

Saved in variable mozza, we now have an instance of our mozzarella pizza. We declared it is a Pizza with all those parameters, in that order. That can get a bit difficult though -- trying to remember the order and all. Order all remember the and to trying. What?

Don't worry, we can also do this -- called record syntax:

-- setting data, named, order-independent
salami = Pizza {
    name          = "Salami",
    mainTopping   = "Salami",
    secondTopping = "Jalapeno",
    thirdTopping  = "Cheese",
    garlic        = True,
    oregano       = True
    }

Hmm. What if someone wants a pizza on our list, but wants to change only one ingredient in it? Do we need to hand-define all the ingredients? Not at all, you can create and use defaults and override only what you need:

mozzhroom = Pizza mozza { thirdTopping = "Mushroom" }

If you want to create explicit getters for your newly-created data type, here's the recipe:

-- getting data
getMainTopping :: Pizza -> String
getMainTopping a = mainTopping a -- getMainTopping mozza -> "Mozzarella"

Thank you!

Thank you for taking the time with my tutorial! Hope you enjoyed it and learned something new - and most importantly, had fun =)

There is more content below, it just hasn't been polished up to the shape of the rest of the tutorial. Feel free to continue reading or come back later to see how the tutorial has progressed.


Extra syntax tips with Haskell

Saving parentheses

Here are some great ways to save on parentheses and keep the code a bit more readable.

$ - the dollar symbol - is a fun way to defer evaluation order: basically, it means "printing is the last important thing we want to do on this line, evaluate everything else first". That way we can be sure we have the printable string built and ready before printing it. In many other languages, you would need to use parens - in Haskell, you can use either.

In practice, here's how you use deferred evaluation:

Prelude> putStrLn ( "foo" ++ "bar" )
foobar
Prelude> putStrLn $ "foo" ++ "bar"
foobar

Another way to reduce parentheses is . - known as function composition:

-- function composition with .
foo . bar x -- is the same thing as foo (bar x)

Editing parameter expectations

Sometimes you want to use a function a bit differently than originally intended - maybe give the parameters in a different order to keep the expression "sentence" sound "natural".

-- infixing a prefix function: `backticks`
mod 2 3 -- normally a prefix function
2 `mod` 3

-- prefixing an infix function: (parens)
2 + 3 -- normally an infix function
(+) 2 3

Multi-line definitions

Sometimes function definitions get a bit long, and it might be a good idea to split it up to a couple of lines.

circumference r = tau * r
      where tau = 2*pi

areaEstimate r =
  let p = 3.14
  in p * r**2

Practical Haskell Cheatsheet

Here are some short snippets of Haskell code that are close to the everyday programming needs - feel free to learn, use and adapt them.

IO

Basic IO operations like printing to stdout, getting from stdin, reading and writing a file.

TODO: fix, is broken :(

main = do
    putStrLn "Hi! What's your name?"
    name <- getLine
    putStrLn $ "You look wonderful today, " ++ name

    content <- readFile "file.txt"
    writeFile "file.txt" $ content ++ name ++ "was here!"

Regex

import Text.Regex.Posix -- Remember to `stack install regex-posix`
                        -- Docs https://hackage.haskell.org/package/regex-posix

-- basic matching, returns boolean
"mozzarella pizza with shitake-mushrooms" =~ "(shroom)" :: Bool -- True
-- basic matching, returns matched (grep-like behaviour)
"mozzarella pizza with shitake-mushrooms" =~ "TODO: FIX" :: String
-- "shitake-mushrooms"
-- basic manipulation / replace

HTTP

After a long day, we need to see what ingredients we have used up and need to restock. A quick look at the pantry and off you go to your trusty old computer, typing your order into your favourite wholesaler's website:

import Network.HTTP.Conduit -- Remember to `stack install http-conduit`
                            -- Docs https://hackage.haskell.org/package/http-conduit
-- simplest way to get message body, will throw exceptions on http response codes other than 2xx
getBody url = simpleHttp url

-- a hint on how to create custom requests
myLittleRequest <- parseUrl "https://example.com/"
-- will result in:
myLittleRequest =
    Request {
      host                 = "example.com"
      port                 = 443
      secure               = True
      requestHeaders       = []
      path                 = "/"
      queryString          = ""
      method               = "GET"
      proxy                = Nothing
      rawBody              = False
      redirectCount        = 10
      responseTimeout      = Just (-3425)
      requestVersion       = HTTP/1.1
    }

-- entire response as an Response object
response <- withManager $ httpLbs myLittleRequest
-- will result in
response = Response {   responseStatus = Status {statusCode = 200, statusMessage = "OK"},
                        responseVersion = HTTP/1.1,
                        responseHeaders = [("Accept-Ranges","bytes"),("Cache-Control","max-age=604800"),("Content-Type","text/html"),("Date","Thu, 24 Sep 2015 20:35:09 GMT"),("Etag","\"359670651\""),("Expires","Thu, 01 Oct 2015 20:35:09 GMT"),("Last-Modified","Fri, 09 Aug 2013 23:54:35 GMT"),("Server","ECS (ewr/1445)"),("X-Cache","HIT"),("x-ec-custom-error","1"),("Content-Length","1270")],
                        responseBody = "<!doctype html>\n<html>\n<head>\n    <title>Example Domain</title>\n\n    <meta charset=\"utf-8\" />\n    <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <style type=\"text/css\">\n    body {\n        background-color: #f0f0f2;\n        margin: 0;\n        padding: 0;\n        font-family: \"Open Sans\", \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n        \n    }\n    div {\n        width: 600px;\n        margin: 5em auto;\n        padding: 50px;\n        background-color: #fff;\n        border-radius: 1em;\n    }\n    a:link, a:visited {\n        color: #38488f;\n        text-decoration: none;\n    }\n    @media (max-width: 700px) {\n        body {\n            background-color: #fff;\n        }\n        div {\n            width: auto;\n            margin: 0 auto;\n            border-radius: 0;\n            padding: 1em;\n        }\n    }\n    </style>    \n</head>\n\n<body>\n<div>\n    <h1>Example Domain</h1>\n    <p>This domain is established to be used for illustrative examples in documents. You may use this\n    domain in examples without prior coordination or asking for permission.</p>\n    <p><a href=\"http://www.iana.org/domains/example\">More information...</a></p>\n</div>\n</body>\n</html>\n",
                        responseCookieJar = CJ {expose = []},
                        responseClose' = ResponseClose}



-- parsing things from the response
responseVersion response -- HTTP/1.1
responseStatus response -- Status {statusCode = 200, statusMessage = "OK"}
responseStatusCode $ responseStatus response -- 200

-- POST

-- TODO: Add HTTP POST examples

JSON

What is the shape of a pizza? A circle of course! For all-a-round JSON-handling, a package most useful is Aeson

{-# LANGUAGE DeriveGeneric #-}      -- Enable GHC to derive some generics to us
{-# LANGUAGE OverloadedStrings #-}  -- Enable some ByteString / [Char] conversion magic

import Data.Aeson (FromJSON, ToJSON, decode, encode)
-- Remember to `stack install aeson`

import GHC.Generics (Generic)
-- Docs https://wiki.haskell.org/GHC.Generics

Let's declare that a circle has its center at position x, position y, and has a radius r.

We also want to say that Circle is something we can encode and decode as JSON - and have the generics map haskell object value into a json object value

TODO: make ghci-testable version

data Circle = Circle {x :: Int, y :: Int, r :: Int} deriving (Show, Generic)
instance FromJSON Circle
instance ToJSON Circle


-- A standard pizza size:
-- unitCircle = Circle {x = 0, y = 0, r = 1}
unitCircle = Circle 0 0 1

-- encoding a Haskell object into JSON
encode unitCircle
-- "{\"r\":1,\"x\":0,\"y\":0}"

-- decoding from JSON to Haskell object.
-- "Maybe" is basically a try-catch - either you get a correct value, or nothing at all
decode "{\"r\":1,\"x\":0,\"y\":0}" :: Maybe Circle
-- Just (Circle {x = 0, y = 0, r = 1})

Graphics

import Graphics.Gloss -- Remember to `stack install gloss`
                      -- Might require freeglut3 and/or freeglut3-dev on your system

main
    = display
        (InWindow "Functional Pizza"    -- window title
        (512, 512)                      -- size
        (0, 0))                         -- position
        white                           -- background color
        draw

draw
    = Translate (-256) (-64)  -- move text to middle
    $ Scale 0.5 0.5           -- scale text size a bit
    $ Text "Functional Pizza"