Introduction

Install Haskell Platform 8.2.1. Make sure you're using the right version. Should be available on lab machines. Installing locally is also a good idea.

REPL (Read-Eval-Print Loop)

Launch the interactive Haskell shell (ghci) from the UNIX command prompt.

# ghci
GHCi, version 8.2.1: http://www.haskell.org/ghc/  :? for help
Prelude>

Numbers, Booleans, Strings, Characters

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

Types

We'll talk about types in much more detail later. For now, let's experiment a little.

> :type True
> :t True

Numeric Types

> :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 :: 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 Ints or two Floats or two Doubles 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)

Strings as Lists of Characters

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

Tuples

> :t ('a', True)
> :t ('a', True, 1)

History and Tab Completion

> <UP>
> :<TAB>               -- lots of commands
> :set <TAB>           -- lots of options and flags
> :set prompt "> "

Let-bindings

> let x = 1
> x + 1
> let y = 2
> x + y

If you're familar with variables in other languages, this is not the same thing. These 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;             -- last semi-colon is optional

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

Simple Function Definitions

> 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
> (add 4) 5
> add (4 5)

Functions can return functions! Hence, "multi-argument" functions are just nested single-argument functions.

        e1 e2 e3 e4 e5  ===  ((((e1 e2) e3) e4) e5)

(e1 (e2 (e3 (e4 e5))))  ===  e1 $ e2 $ e3 $ e4 e5

We'll talk more about ($) later. For now, just note that it's a way to write right-associative nested function calls without so many parentheses.

Source Files

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

Booleans and Guarded Definitions

> :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

Comments

-- single line comment

{- multi-line comment

-}

Loading Files

Load a source file from the shell.

> :load Introduction.hs
> :l Introduction.hs
> :l Introduction
> :reload
> :r

Library Documentation

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.

Hello, world!

Okay, we've said hello to the Haskell world. But where was printing "Hello, world!" to the output console? Patience...

Source Files

results matching ""

    No results matching ""