Chapter 2: Hello, Haskell!

2.1 Hello, Haskell

In order to run our code, we’ll need a compiler.

Install stack by following the directions on their website.

Stack manages the entire tool-chain that you’ll typically use for a project in an isolated way. Right now we’re only interested in it because it will provide a compiler to execute our code with.

2.2 Interacting with Haskell code

Although Haskell is an ahead-of-time compiled language, it provides both a compiler and an interpreter.

The compiler is known as GHC, short for Glasgow Haskell Compiler. You can read more on the homepage or peruse its documentation. The interpreter, GHCi, is also part of the GHC project.

When working with a compiler, the steps to run your code go roughly like this:

  • Edit and save your source code into a file,

  • compile that file by running stack ghc $file as a separate step,

  • and run the compiled artifact it produced from your shell like ./progname.

(This is of course the most naive possible workflow; it can be more automated.)

Working with an interpreter goes more like this:

  • Start the interpreter with stack ghci;

  • type code into it and watch it be executed immediately.

Now try bringing up your prompt with stack ghci and entering some arithmetic, to see if it’s working.

Prelude> 2 + 2
4
Prelude> 7 < 9
True
Prelude> 10 ^ 2
100

As an elaboration of the workflow above, ghci allows you to load existing files into your interactive session. To do so, type :load $filename inside the repl.

You can also load a module, like :load module and GHC will search for it in its search path. :load *module will not only load the declarations the module exports, but also things internal to it.

Special commands that only GHCi understands start with the : character. Here are a few useful ones:

$ stack ghci
:load filename
:reload
:{
  multi-line
  expression
:}
-- evaluation order matters in the repl
let x = 4  -- "let" is needed to declare regular variables in older versions of GHCi
let x = 10 -- unlike in source files, you can reassign the same name to different values
-- newer versions of GHCi don't need the "let"
:info
:type
:doc -- only available in newer version of ghci
:browse
:show bindings
:quit

For more, check the GHC documentation.

2.2.2 What is Prelude?

Prelude is the standard module. It is imported into all Haskell files by default, unless there is an explicit import declaration hiding it, or the NoImplicitPrelude compiler extension is enabled, like stack ghci -XNoImplicitPrelude.

Prelude is part of the base package, which comes with GHC. The base package includes other modules, too. You can read more here.

Here is a function with a type signature for your inspection:

sayHello :: String -> IO ()
sayHello x = putStrLn ("Hello, "  ++ x ++ "!")

The double colon, ::, is read as “has the type”. The entire line would be read as “sayHello has the type String to IO unit”. (Unit is how you pronounce the empty tuple, ().)

2.3 Understanding expressions

Everything in Haskell is an expression or declaration.

Expressions are the building blocks of our programs, and programs themselves are one big expression made of smaller expressions.

Declarations are top-level bindings which allow us to name expressions.

2.3.1 Normal form

We say that expressions are in normal form when there are no more evaluation steps that can be taken, or put differently, when they’ve reached an irreducible form. Reducible expressions are also called redexes.

2.4 Functions

A function is an expression that is applied to an argument and always returns a result.

As in the lambda calculus, all functions in Haskell take one argument and return one result. When it seems like we’re passing multiple arguments to a function, we are actually applying a series of nested functions, each to one argument. This is called currying.

Functions are how we factor out patterns common to expressions into something we can reuse with different inputs.

Here’s one example of a simple function definition:

-- name    body
-- v       vvvvv
triple x = x * 3
--     ^
--   parameter

2.4.2 Capitalization matters!

Unlike Ada, Nim, and windows batch, identifiers are case sensitive.

camelCase is the current convention for variables and functions.

PascalCase is used for type constructors, data constructors, and type class names, things you’ll learn about later. This is enforced by the compiler.

You can also use underscores and single quotes in identifiers.

Adding a single quote after a variable name sometimes suggests a slightly altered version of it. In that circumstance, a single quote is read as “prime”.

Adding a _ after the name may suggest that the output is thrown out. There are a few loose conventions like this, you’ll learn them over time.

2.5 Evaluation

Evaluation is program execution. Haskell uses a non-strict evaluation strategy, which defers evaluation of terms until they’re forced by other terms requiring them. Simplifying a term is called reducing. Values are a terminal point of reduction.

Haskell doesn’t evaluate everything to normal form by default. Instead it only evaluates to weak head normal form.

Here’s a great video on it Haskell for Imperative Programmers #31 - Weak Head Normal Form.

2.6 Infix operators

Functions default to prefix syntax, but you can also create infix functions, which are known as operators.

Here’s an example:

·∾ x |> f = f x
·∾ 3 |> (+ 12)
15

If the function name is alphanumeric, it is a prefix function by default. If the name is a symbol, it is prefix by default.

You can also use prefix functions as infix by surrounding them with backticks:

·∾ 10 `div` 4
2
·∾ div 10 4
2

…and you can use infix functions a prefix by surrounding them with parenthesis:

·∾ (+) 3 4
7

2.6.1 Associativity and precedence

You can query the fixity, associativity, and precedence of a function using :info:

·∾ :info (+)
class Num a where
  (+) :: a -> a -> a
--     v-- precedence out of the range 0..9, where 9 binds most tightly
infixl 6 +
--   ^-- the l means left associative

2.6.2 Exercises: Parentheses and Association

Read the pairs of expressions, and decide if the parenthesis change the result. Check your work in GHCi.

    1. 8 + 7 * 9

    2. (8 + 7) * 9

    They should differ, multiplication is usually performed first, but this has parenthesis:

    ·∾ 8 + 7 * 9
    71
    ·∾ (8 + 7) * 9
    135
    
    1. perimeter x y = (x * 2) + (y * 2)

    2. perimeter x y =  x * 2  +  y * 2

    These should be the same, since * already has a higher precedence than +:

    ·∾ perimeter x y = (x * 2) + (y * 2)
    ·∾ perimeter 12 8
    40
    ·∾ perimeter x y = x * 2 + y * 2
    ·∾ perimeter 12 8
    40
    
    1. f x = x / 2 + 9

    2. f x = x / (2 + 9)

    This should differ; (x / 2) vs (x / (2 + 9)):

    ·∾ f x = x / 2 + 9
    ·∾ f 3
    10.5
    ·∾ f x = x / (2 + 9)
    ·∾ f 8
    0.7272727272727273
    

2.7 Declaring values

The order of declaration in a source code file doesn’t matter because GHCi loads the entire file at once, so it known all the values that have been defined.

On the other hand, when you enter them one by one into the repl, the order does matter.

Module names must begin with an uppercase letter.

2.7.1 Troubleshooting

White-space is significant in Haskell, just like Python.

The basic rule is that subsequent lines belonging to an expression should be written under the beginning of that expression at the same level of indentation.

Correct

Incorrect

let
  x = 3
  y = 4

-- or

let x = 3
    y = 4
let x = 3
 y = 4

-- or

let
 x = 3
  y = 4

You can read about the particulars in the 2010 language report, section 2.7 Layout.

2.7.2 Heal the sick

The following code samples are broken and won’t compile. The first two are as you might enter into the REPL; the third is from a source file. Find the mistakes and fix them so that they will.

  1. area x = 3. 14 * (x * x) should be let area x = 3.14 * (x * x) (Note that since GHC 8.0.1 you no longer have to preface name bindings with let in ghci.)

  2. double x = b * 2 should be let double x = x * 2

  3. This:

    x = 7
     y = 10
    f = x + y
    

    Won’t compile, ghc will make a complaint similar to:

    ~/P/h/0/exercises ❯❯❯ ghc healthesick3.hs
    [1 of 1] Compiling Main ( healthesick3.hs, healthesick3.o )
    healthesick3.hs:2:4: error: parse error on input ‘=’
      |
    2 |  y = 10
      |    ^
    

    …so we should edit it to remove the extra indentation, like this:

    x = 7
    y = 10
    f = x + y
    

2.8 Arithmetic functions in Haskell

Operator

Name

Purpose/Application

Associativity

Precedence

Fixity

Arity

+

plus

addition

left

6

infix

2

-

minus

subtraction

left

6

infix

2

*

asterisk

multiplication

left

7

infix

2

^

caret

non-negative integral exponentiation

right

8

infix

2

/

slash

fractional division

left

7

infix

2

div

divide

integral division, round towards -inf

left

7

prefix

2

quot

quotient

integral division, round towards zero

left

7

prefix

2

rem

remainder

remainder after division

left

7

prefix

2

mod

modulo

like ‘rem’, but after modular division

left

7

prefix

2

The mod and rem functions keep on tripping me up. https://ebzzry.io/en/haskell-division/

2.8.1 Laws for quotients and remainders

·∾ --  (x `quot` y) * y  +  (x `rem` y) == x
·∾ --   (x `div` y) * y  +  (x `mod` y) == x

·∾ :{
·∾ let x = 10; y = (-4) in
·∾     (x `quot` y) * y    +  (x `rem` y)    == x
·∾ --
·∾ --  (10 `quot` (-4)) * (-4) + (10 `rem` (-4)) == 10
·∾ --              (-2) * (-4) +  2              == 10
·∾ --                       8  +  2              == 10
·∾ --                            10              == 10
·∾ --
·∾ :}
True

·∾ :{
·∾ let x = 10; y = (-4) in
·∾         (x `div` y) *  y   +  (x `mod` y)    == x
·∾ --
·∾ --  (10 `div` (-4)) * (-4) + (10 `mod` (-4)) == 10
·∾ --             (-3) * (-4) + (-2)            == 10
·∾ --                     12  + (-2)            == 10
·∾ --                         10                == 10
·∾ --
·∾ :}
True

2.8.2 Using mod

If you’re unfamiliar with modular division, you may not understand the useful difference between mod and rem.

Modular arithmetic is a system of arithmetic for integers where numbers “wrap around” upon reaching a certain value, called the modulus (the second argument to mod).

2.8.3 Negative numbers

Negative numbers need to be surrounded in parenthesis, like this (-9). Otherwise the compiler may confuse the negative sign with the infix subtraction operator. When - is used infix, it’s a synonym for subtract.

Using - to make a number negative is syntactic sugar; You can instead write it like this (negate 9). This is a bit of a special case.

2.9 Parenthesization

If you want to inspect an infix operator with ghci using the :info command, you usually have to surround it with parenthesis, like :info (^), for example.

The $ operator can be used to avoid parenthesis, sometimes. It will allow everything to the right of it to be evaluated first and can be used to delay function application.

·∾ (2^) $ (+2) $ 3 * 2
256

2.9.1 Parenthesizing infix operators

You can also use parenthesis to apply only some arguments to a function, and leave the other parameters available for binding. Applying only some arguments is known as partial application.

For example (2^) is equivalent to (^) 2, or more verbosely \x -> 2 ^ x.

When you do this by surrounding the function with parenthesis, this is sometimes known as sectioning.

Subtraction is a special case; (-2) 1 won’t work, because - has a special case that it’s treated as negate within parenthesis and prefacing a numeric literal. (subtract 2) 1 will give the desired effect.

When sectioning operators, pay special attention to the associativity, it will change the result.

2.10 Let and where

let introduces an expression, so it can be used wherever you can have an expression, but where is a declaration, and is bound to the surrounding syntactic construct.

2.10.1 Exercises: A Head Code

Now for some exercises. First, determine in your head what the following expressions will return, then validate in the REPL:

  1. These examples are prefixed with let because they are not declarations, they are expressions.

    let x = 5 in x

    This should return 5. Let’s see:

    ·∾ let x = 5 in x
    5
    
  2. let x = 5 in x * x should return 25:

    ·∾ let x = 5 in x * x
    25
    
  3. let x = 5; y = 6 in x * y should return 30:

    ·∾ let x = 5; y = 6 in x * y
    30
    
  4. let x = 3; y = 1000 in x + 3 should return 6:

    ·∾ let x = 3; y = 1000 in x + 3
    6
    

Above, you entered some let expressions into your REPL to evaluate them. Now, we’re going to open a file and rewrite some let expressions using where declarations. You will have to give the value you’re binding a name, although the name can be a single letter if you like. For example:

-- this should work in GHCi
let x = 5; y = 6 in x * y

…could be rewritten as:

-- practice.hs
module Mult1 where
-- put this in a file
mult1     = x * y
  where x = 5
        y = 6

Making the equal signs line up is a stylistic choice. As long as things are nested in that way, the equals signs do not have to line up. But notice we use a name that we will use to refer to this value in the REPL:

Prelude> :l practice.hs
[1 of 1] Compiling Main

The prompt changes to *Main instead of Prelude to indicate that you have a module called Main loaded.

Rewrite with where clauses:

  1. let x = 3; y = 1000 in x * 3 + y

    becomes

    one = x * 3 + y
      where
        x = 3
        y = 1000
  2. let y = 10; x = 10 * 5 + y in x * 5

    becomes

    two = x * 5
      where
        y = 10
        x = 10 * 5 + y
  3. let x = 7
        y = negate x
        z = y * 10
    in z / x + y
    

    becomes

    three = z / x + y
      where
        x =7
        y = negate x
        z = y * 10

Note: the filename you choose is unimportant except for the .hs extension.

2.11 Chapter Exercises

2.11.1 Parenthesization

Given what we know about the precedence of (*), (+), and (^), how can we parenthesize the following expressions more explicitly without changing their results? Put together an answer you think is correct, then test in the GHCi REPL.

For example, we want to make this more explicit:

2 + 2 * 3 - 3

This will produce the same result:

2 + (2 * 3) - 3

Attempt the above on the following expressions:

  1. 2 + 2 * 3 - 1 becomes 2 + (2 * 3) - 1

  2. (^) 10 $ 1 + 1 becomes (^) 10 (1 + 1)

  3. 2 ^ 2 * 4 ^ 5 + 1 becomes ((2 ^ 2) * (4 ^ 5)) + 1

2.11.2 Equivalent expressions

Which of the following pairs of expressions will return the same result when evaluated? Try to reason them out by reading the code and then enter them into the REPL to check your work:

  1. equivalent

    1 + 1
    2
    
  2. equivalent

    10 ^ 2
    10 + 9 * 10
    
  3. 400 - 37
    (-) 37 400
    
  4. 100 `div` 3
    62
    100 / 3
    
  5. 2 * 5 + 18
    2 * (5 + 18)
    

2.11.3 More fun with functions

Here is a bit of code as it might be entered into a source file. Remember that when you write code in a source file, the order is unimportant, but when writing code directly into the REPL the order does matter. Given that, look at this code and rewrite it such that it could be evaluated in the REPL (remember: you may need let when entering it directly into the REPL). Be sure to enter your code into the REPL to make sure it evaluates correctly.:

z = 7
x = y ^ 2
waxOn = x * 5
y = z + 8

-- becomes
z = 7
y = z + 8
x = y ^ 2
waxOn = x * 5

All the answers in one screen cast, the answer to 4 has a correction below.

  1. Now you have a value called waxOn in your REPL. What do you think will happen if you enter:

    10 + waxOn
    -- or
    (+10) waxOn
    -- or
    (-) 15 waxOn
    -- or
    (-) waxOn 15
    

    This:

    Prelude> z = 7; y = z + 8; x = y ^ 2; waxOn = x * 5
    Prelude> waxOn
    1125
    Prelude> 10 + waxOn
    1135
    Prelude> (+10) waxOn
    1135
    Prelude> (-) 15 waxOn
    -1110
    Prelude> (-) waxOn 15
    1110
    
  2. Earlier we looked at a function called triple. While your REPL has waxOn in session, re-enter the triple function at the prompt:

    triple x = x * 3
    
  3. Now, what will happen if we enter this at our GHCi prompt? What do you think will happen first, considering what role waxOn is playing in this function call? Then enter it, see what does happen, and check your understanding:

    triple waxOn
    

    Sure, here:

    Prelude> triple x = x * 3
    Prelude> x
    225
    Prelude> triple waxOn
    3375
    
  4. Rewrite waxOn as an expression with a where clause in your source file. Load it into your REPL and make sure it still works as expected.

    You mean a decalartion, right?:

    ·∾ :{
     ⋮ waxOn = x * 5
     ⋮   where z = 7
     ⋮         y = z + 8
     ⋮         x = y ^ 2
     ⋮ :}
    ·∾ waxOn
    1125
    

    No, wait, I got that wrong:

    ·∾ :{
     ⋮ let waxOn = x * 5
     ⋮       where z = 7; y = z + 8; x = y ^ 2
     ⋮ in  waxOn
     ⋮ :}
    1125
    ·∾
    
  5. To the same source file where you have waxOn, add the triple function. Remember: You don’t need let and the function name should be at the left margin (that is, not nested as one of the waxOn expressions). Make sure it works by loading it into your REPL and then entering triple waxOn again at the REPL prompt. You should have the same answer as you did above.

  6. Now, without changing what you’ve done so far in that file, add a new function called waxOff that looks like this:

    waxOff x = triple x
    
  7. Load the source file into your REPL and enter waxOff waxOn at the prompt. You now have a function, waxOff that can be applied to a variety of arguments — not just waxOn but any (numeric) value you want to put in for x. Play with that a bit. What is the result of waxOff 10 or waxOff (-50)?

    *WaxOn> waxOff 10
    30
    *WaxOn> waxOff (-50)
    -150
    *WaxOn>
    

    Try modifying your waxOff function to do something new — perhaps you want to first triple the x value and then square it or divide it by 10. Spend some time getting comfortable with modifying the source file code, reloading it, and checking your modification in the REPL.