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.
8 + 7 * 9
(8 + 7) * 9
They should differ, multiplication is usually performed first, but this has parenthesis:
·∾ 8 + 7 * 9 71 ·∾ (8 + 7) * 9 135
perimeter x y = (x * 2) + (y * 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
f x = x / 2 + 9
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.
area x = 3. 14 * (x * x)
should belet area x = 3.14 * (x * x)
(Note that since GHC 8.0.1 you no longer have to preface name bindings withlet
inghci
.)double x = b * 2
should belet double x = x * 2
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 |
|
divide |
integral division, round towards -inf |
left |
7 |
prefix |
2 |
|
quotient |
integral division, round towards zero |
left |
7 |
prefix |
2 |
|
remainder |
remainder after division |
left |
7 |
prefix |
2 |
|
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:
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
let x = 5 in x * x
should return 25:·∾ let x = 5 in x * x 25
let x = 5; y = 6 in x * y
should return 30:·∾ let x = 5; y = 6 in x * y 30
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:
let x = 3; y = 1000 in x * 3 + y
becomes
one = x * 3 + y where x = 3 y = 1000
let y = 10; x = 10 * 5 + y in x * 5
becomes
two = x * 5 where y = 10 x = 10 * 5 + y
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:
2 + 2 * 3 - 1
becomes2 + (2 * 3) - 1
(^) 10 $ 1 + 1
becomes(^) 10 (1 + 1)
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:
equivalent
1 + 1 2
equivalent
10 ^ 2 10 + 9 * 10
400 - 37 (-) 37 400
100 `div` 3 62 100 / 3
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.
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
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
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
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 ·∾
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.
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
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.