Install Haskell 8.6.5. Make sure you're using the right version. Should be available on lab machines. Installing locally is also a good idea.
Launch the interactive Haskell shell (ghci
) from the UNIX command prompt.
# ghci
GHCi, version 8.6.5: http://www.haskell.org/ghc/ :? for help
Prelude>
Try out integers, floating-point numbers, booleans strings, characters. Try some expressions that result in type errors.
Prelude> 4
Prelude> :set prompt "> "
> 4
> 4.0
> 4 + 4
> 4 + 4.0
> 'a'
> "a"
> "a" + "b"
> "a" ++ "b"
> "a" ++ 'b'
> True
> true
We'll talk about types in much more detail later. For now, let's experiment a little.
> :type True
> :t True
> :t 4
We'll dig into this in detail later. For now, read Num p => p
as "Int
and Float
and Double
and every other numeric type you can think of". We can "convert" 4
to one of these specific numeric types with an explicit type ascription, using the ::
syntax.
> :t (4 :: Int)
(4 :: Int) :: Int
> :t 4 :: Int
4 :: Int :: Int
> :t 4 :: Integer
4 :: Integer :: Integer
> :t 4 :: Float
4 :: Float :: Float
> :t 4 :: Double
4 :: Double :: Double
> :t 4.0 :: Double
4.0 :: Double :: Double
> :t 4.0 :: Int
Notice that operators like (+)
can work on many different kinds of numbers.
> 4 + 5
> (+) 4 5
> :t (+)
(+) :: Num a => a -> a -> a
You can read the type for (+)
as saying "(+)
takes two Int
s or two Float
s or two Double
s or two values of any other numeric type and returns another number of the same type as the arguments". Haskell automatically infers the specific types based on how the expressions are used. But we can also explicitly tell Haskell which types we want using type ascription.
> :t (4 :: Int) + (5 :: Int)
> :t (4 + 5) :: Int
> :t (4 + 5) :: Float
> :t (4 :: Float) + 5
> :t (4 :: Float) + (5 :: Double)
> :t (4 :: Float) + (5 :: Int)
Notice that strings are lists of characters, written [Char]
. We can define String
to be another way of writing the type [Char]
.
> :t 'a'
> :t "a"
> type String = [Char]
> :t "a" :: String
> :t (++) :: String -> String -> String
> :t ('a', True)
> :t ('a', True, 1)
> :t ('a', (True, 1))
> <UP>
> :<TAB> -- lots of commands
> :set <TAB> -- lots of options and flags
> :set prompt "> "
> let x = 1
> x + 1
> let y = 2
> x + y
If you're familar with mutable variables in other languages, this is not the same thing. These variable bindings never change...
> let z = 0
> let z = 1 -- "shadowing"
> let z = z + 1 -- recursive def (not shadowing)
> z -- "forces" evaluation (diverges)
> Ctrl-C -- to interrupt
> Ctrl-D -- to quit GHCI
Multiple bindings in a single let
.
> let a = 1; b = 2 -- multiple bindings
> let a = 1; b = 2; -- optional last semi-colon
> let {a = 1; b = 2;} -- optional braces
Top-level vs. local let-bindings.
let x = 1 -- "global" binding
let y = 2 in y + y -- "local" binding
y -- not in scope
let y = 6
let y = 5 in y + y -- previous binding "shadowed" (locally)
y
> let add3 x = x + 3
> add3 1
> add3 1.1
> add3 0xDEADBEEF
> let add x y = x + y
> add 3 4
> add 3
... No instance for Show ... -- more on this next time
> let also_add (x, y) = x + y
> also_add 1 2 -- error
> add (1, 2) -- error
The the add
function takes two arguments, whereas the also_add
function takes a single argument (a 2-element tuple, a.k.a. a pair).
All functions in Haskell take exactly one argument and produce exactly one value in return. But what about the "multi-argument" functions above?
> let add4 = add 4 -- "currying" or partial application
> add4 10
We'll talk much more about functions in due course.
So far, we've used the interactive shell. Now let's create a standalone Haskell source file called Introduction.hs
. Unlike in the shell, top-level definitions in Haskell source files do not start with let
. Instead, they are written like equations.
minutesPerDay = 60 * 24
Definitions can refer to subsequent definitions later in the file. Helper definitions can help improve readability and maintainability.
minutesPerDay = minutesPerHour * hoursPerDay
minutesPerHour = 60
hoursPerDay = 24
All definitions are at the "top-level" and are mutually recursive. These two features help make Haskell definitions read more like math.
Local variables can help improve readability and maintainability, by emphasizing the scope of the definitions. For example, instead of the previous three top-level definitions, we can use two locally defined bindings:
minutesPerDay =
let minutesPerHour = 60 in
let hoursPerDay = 24 in
minutesPerHour * hoursPerDay
Or better yet:
minutesPerDay =
let
minutesPerHour = 60
hoursPerDay = 24
in
minutesPerHour * hoursPerDay
Alternatively, where-clauses can be used instead of let-bindings.
minutesPerDay = minutesPerHour * hoursPerDay
where minutesPerHour = 60
hoursPerDay = 24
Like with let-bindings, minutesPerHour
and hoursPerDay
are not accessible outside this definition.
For both let-bindings and where-clauses, can choose indendation depth but must choose the same starting column for all variables being defined. So, there are many possible formatting styles. Here's another one.
minutesPerDay = minutesPerHour * hoursPerDay where
minutesPerHour = 60
hoursPerDay = 24
> :t True
> :t 1 == 2
> :t 1 < 2
If-then-else expressions.
absoluteValue n =
if n >= 0
then n
else -1 * n
Alternatively:
absoluteValue n
| n >= 0 = n
| n < 0 = -1 * n
We can use otherwise
as the last, "catch-all" guard. And we can negate n
more concisely.
absoluteValue n
| n >= 0 = n
| otherwise = -n
-- single line comment
{- multi-line comment
-}
Load a source file from the shell.
> :load Introduction.hs
> :l Introduction.hs
> :l Introduction
> :reload
> :r
The documentation for Haskell libraries will be very useful throughout the course. Prelude
is a good place to start browsing.
This documentation should also be installed locally somewhere, such as file:///Library/Haskell/doc/start.html
on a Mac; then click "Libraries".
Additional resources are listed at Haskell Documentation.
Okay, we've said hello to the Haskell world. But where was printing "Hello, world!" to the output console? Patience...