There are two primary ways of working with code:
REPL is an acronym for read-eval-print loop.
REPLs provide an environment for inputting code, evaluating it, and seeing the result.
REPLs first originated with the Lisp Programming Language but are now common to modern programming languages.
The REPL in Haskell is called GHCi.
Install Haskell by going to: https://www.haskell.org/platform/
Once you have Haskell installed, open your terminal and type ghci
or stack ghci
.
stack ghci
includes a lot of modules that will be convenient to use later. It is not necessary to use stack ghci
at this point.
You will see something like the following:
GHCi, version 8.8.4: https://www.haskell.org/ghc/ :? for help
Prelude>
Let's try some basic arithmetic:
Prelude> 10 + 10
20
Prelude> 1 < 50
True
Prelude> 5 ^ 2
25
Congrats, you are now a functional programmer. It may not seem like much, but this is just the beginning of Haskell's capabilities.
To exit GHCi, type in the command :quit
or :q
.
What is Prelude?
Prelude is a library of standard functions that Haskell provides. Opening GHCi or Stack GHCi automatically uses Prelude.
There are alternative preludes and you can turn the default Prelude off.
You can find more information about Prelude in Haskell's base package at https://www.stackage.org/package/base.
The base package contains basic libraries.
Almost all programming will involve editing libraries or applications comprised of directories containing files with Haskell code in them.
The basic workflow is to have the Haskell code in a file, load it into the REPL, and then interact with it there.
Create a file called hello.hs
. Then, enter the following code into it:
helloHaskell :: String -> IO ()
helloHaskell name =
putStrLn ("Hello, " ++ name ++ "!")
::
is a way to write the type signature. You read it as "this has the type". In this instance, helloHaskell
has the type String -> IO ()
.
I want to focus primarily on syntax in this article. I will talk about types in a later article.
In the directory where you created the hello.hs
file, open up ghci and type the following:
Prelude> :load hello.hs
[1 of 1] Compiling Main ( hello.hs, interpreted )
Ok, one module loaded.
:Main> helloHaskell "Haskell"
Hello, Haskell!
:Main>
You can use :l
as a shorthand for :load
.
After you load hello.hs
, the helloHaskell
function is now visible in ghci.
You can pass it a string argument like "Haskell" and see the output.
If you load the code from the source file and your GHCi prompt is no longer Prelude>
, you can return to it by using the :m
command.
*Main> :m
Prelude>
The :m
command is shorthand for :module
.
This will unload the file from GHCi so that it will no longer be in scope.
You can set your prompt to whatever you would like by using :set prompt "λ> "
for example.
Everything in Haskell is either an expression or declaration.
An expression can be a value, a combination of values, and/or functions applied to values.
Expressions evaluate to a result.
Expressions act as the building blocks of programs.
The following are all expressions:
10
10 + 10
"Aegis"
Each of these can be entered into GHCi and will return a result. The number 10
, has no further reduction steps, so it represents itself.
In the case of 10 + 10
, GHCi reduces the expression to 20
, then outputs the number 20
.
Numbers can be nested:
Prelude> (5 + 5) * 2
20
Prelude> ((5 * 3) + 2) - 5
12
Try expanding on this yourself. You can nest as many expressions as you would like.
Expressions are said to be in normal form when they are irreducible.
The normal form of 2 + 2
is 4
. This is because it can no longer be reduced.
Reducible expressions are also known as redexes.
A function is a specific type of expression.
Functions in Haskell are related to functions in mathematics in that they map an input or set of inputs to an output.
Because functions are built out of expressions, they will always evaluate to the same result when given the same values.
In lambda calculus, all functions take one argument and return one result.
In Haskell, when it appears that we are passing multiple arguments into a function, we are actually applying a series of nested functions.
This is known as currying.
Functions allow us to abstract parts of code that we would like to reuse.
Imagine you need to take a handful of expressions and multiply them by 2.
You could do this manually like this:
Prelude> (2 + 2) * 2
8
Prelude> (5 + 1) * 2
12
Prelude> (3 + 8) * 2
22
That seems like a lot of work though. Imagine you had a ton of expressions that you needed to repeat that process for.
Functions are how we factor out the pattern into something we can reuse with different inputs.
The common pattern is the part where we multiply by 2.
The part that varies is the expression before it.
Let's build this function out in the next section.
Functions all have a name, parameters, an equal sign, and a body.
Defining functions in GHCi and in source code are a bit different.
In GHCi, you must use let
:
Prelude> let doubleIt num = num * 2
In source code, it is entered like this:
doubleIt num = num * 2
Let's examine each part:
doubleIt num = num * 2
[1] [2][3] [4]
=
is used to define values and functions.num * 2
is the body of the function. If you have an expression like doubleIt 5
, num
is bound to 5.Trying the doubleIt
function out:
Prelude> doubleIt 5
10
Try using different values for num
. You can use integer values or even mathematical expressions.
Try changing the function itself in the source code. Then, reload it using the :r
or :reload
command.
Evaluating an expression is reducing terms until the expression reaches its simplest form.
Haskell uses a non-strict evaluation strategy which defers evaluation of terms until they're forced by other terms referring to them.
Like in the lambda calculus, application is evaluation: applying a function to an argument allows evaluation or reduction.
Refer back to the doubleIt
function:
doubleIt 4
-- [doubleIt num = num * 2; num := 4]
4 * 2
8
Here, we apply doubleIt
to the value 4
and then reduce the expression to the final result 8
.
The expression doubleIt 4
is in normal form when it reaches 8
because it has no remaining reducible expressions.
Functions in Haskell default to prefix syntax meaning that the function being applied is at the beginning of the expression rather than the middle.
We saw prefix syntax with our doubleIt
function.
While this is the default syntax for functions, not all functions are prefix.
For example, the arithmetic operators we've been using, are functions but appear by default in an infix position.
All operators are functions; not all functions are operators.
You can use infix operators in a prefix fashion by wrapping them in parentheses:
Prelude> (+) 200 200
400
Prelude> (*) 2 5
10
If a function name is alphanumeric, it is a prefix function by default.
Not all prefix functions can be made infix.
If the name is a symbol, it is infix by default.
GHCi has a command called :info
which provides information about an operator or a function.
Let's use this command on operators to get a better idea of them:
Prelude> :info (*)
class Num a where
...
(*) :: a -> a -> a
...
-- Defined in ‘GHC.Num’
infixl 7 *
[1] [2][3]
infixl
means that it's an infix operator; the l
means that it's left associative.Left associativity:
This:
2 * 3 * 4
Evaluated as:
(2 * 3) * 4
Left takes precedence.
Here's a right-associative infix operator:
Prelude> :info (^)
(^) :: (Num a, Integral b) => a -> b -> a -- Defined in ‘GHC.Real’
infixr 8 ^
[1] [2][3]
means that it's an infix operator; the
r` means that it's right associativeRight associativity:
This:
2 ^ 3 ^ 4
Evaluated as:
2 ^ (3 ^ 4)
Let's make a new file called values.hs
module Values where
x = 23 * 7 + y
theResult = x * 3
y = 6
Declaring values is as easy as that.
Operator | Name | Purpose/application |
---|---|---|
+ | plus | addition |
- | minus | subtraction |
* | asterisk | multiplication |
/ | slash | fractional division |
div | divide | integral division, rounding down |
mod | modulo | like rem , but after modular division |
quot | quotient | integral division, rounds towards zero |
rem | remainder | remainder after division |
Integral division refers to division of integers.
You usually want to use div
for integral division, due to the way div
and quot
round.
To work with negative numbers, wrap them in parenthesis:
Prelude> 10000 + (-10)
9990
The $
operator is used frequently in Haskell.
The definition of $
:
f $ a = f a
This operator is a convenience for when you want to express something with fewer pairs of parenthesis:
This:
Prelude> (2^) (2 + 2)
16
Same as:
Prelude> (2^) $ 2 + 2
16
If you remove $
and the parenthesis:
Prelude> (2^) 2 + 2
6
Everything to the right of $
will be evaluated first and can be used to delay function application.
You can stack up multiple uses of $
in the same expression.
Let introduces an expression, so it can be used wherever you can have an expression.
Where is a declaration and is bound to a surrounding syntactic construct.
where
example:
module Learn where
printer n = print plusTwo
where
plusTwo = n + 2
*Learn> printer 1
3
let
example:
module Learn where
printer n =
let plusTwo = n + 2
in print plusTwo
*Learn> printer 1
3
Article on let vs. where: https://wiki.haskell.org/Let_vs._Where
Functions - https://www.haskell.org/tutorial/functions.html
Haskell Book - https://haskellbook.com/