Programming Languages: Appendix C: Introduction to Haskell



Key language concepts in Haskell

  • Haskell = ML + Lazy Evaluation - Side Effects.
  • Haskell = LISP - Homoiconicity - Side Effects + Strong Typing + Lazy Evaluation.
  • a statically-scoped, strongly-typed, purely functional language with a rich type system, built-in type inference algorithm, and lazy evaluation
  • named after Haskell B. Curry, the pioneer of the Y-combinator in λ-calculus.
  • developed at Yale University and University of Glasgow
  • descendant of Miranda
  • designed to be purely functional and thus brings programming closer to mathematics
  • intended for those who want to get work done quickly and, thus, designed to have a crisp, terse syntax; permeates even the language's writability (e.g., notice no define or lambda keywords, cons has even been reduced from cons in Scheme to :: in ML to : in Haskell; programmer nearly never needs to enter a ; (semicolon) in a Haskell program)
  • pattern-directed invocation (pattern matching, pattern-action rule oriented style of programming)
  • higher-order functions (map, ., foldl, foldr)
  • currying (curry, uncurry)
  • strong typing (Haskell is a strongly-typed programming language)
  • type inference (even functions have types!)
  • rich and expressive type system (ML and Haskell have arguably the most powerful and elegant type system in all of programming languages)
  • a useful general-purpose programming language, it incorporates
    • functional features from LISP,
    • rule-based programming from PROLOG,
    • terse syntax, and
    • data abstraction from Smalltalk and C++
  • Haskell is a functional language with declarative features (e.g., pattern-directed invocation, guards, list comprehensions, mathematical notation)
  • the language pH is a parallel dialect of Haskell


Core Haskell

  • simple expressions
  • primitive types: Bool, Char, String, Int (fixed precision), Integer (arbitrary precision), Float (single precision)
  • homogeneous lists
  • list operators: : (cons), ++ (append)
  • higher-order functions
    • functions that take functions as arguments or return a function as a value
    • for instance, map, ., foldl, foldr
    • `allow common programming patterns to be encapsulated as function' [PIH], p. 61
    • can be used to define domain-specific languages
  • powerful type system
  • :: operator associates a type with a value or expression (e.g., 1 :: Int)


Primitive types in Haskell

    :type expression (also :t expression) returns the type of expression
    Prelude> :t 3
    3 :: Num a => a
    Prelude> :t 3.3
    3.3 :: Fractional a => a
    Prelude> :t True
    True :: Bool
    Prelude> :t 'a'
    'a' :: Char
    Prelude> :t "hello world"
    "hello world" :: String
    Prelude>
    


Essentials

  • character conversions: ord and chr functions (in Data.Char module)
  • string concatenation: ++ operator (e.g., "hello " ++ "world")
  • basic arithmetic: +, -, *, / (for reals), div (prefix; for integers) and mod
  • comparison operators: ==, <, >, <=, >=, and /= to compare integers, reals, characters, or strings
  • boolean operators: ||, &&, and not
  • if-then-else expressions: there is no if without an else, why?
  • converting a prefix operator to infix (use backquotes or grave quotes):
    div 7 2 = 7 `div` 2
    mod 7 2 = 7 `mod` 2
  • converting an infix operator to prefix (enclose operator in parentheses):
    7 + 2 = (+) 7 2
    7 - 2 = (-) 7 2
  • comments:
    • single line: -- this is a comment until the end of the line
    • multi-line:
      {-
      a
      multi-line
      comment
      -}
      
    • multi-line comments can nest:
      {- nested
         {-
         comment
         -}
      -}
      
  • :? displays all available commands
  • running an Haskell program
    • $ ghci program.hs # from the command line
    • Prelude> :load program.hs from within the system (or :l)
    • :reload (or :r) reloads the program
  • : quit quits the interpreter (or use the EOF character; on UNIX systems <crtl-d>)


Lists

  • [] is the empty list
  • lists are homogeneous (e.g., [2,3,4])
  • tuples are heterogeneous (see below)
  • can have lists of tuples, but each tuple in the list must have the same type
  • : is the cons operator
    • takes a head (element) and a tail (list)
    • x:xs is a list of at least one element
    • x:[] is a list of exactly one element; same as [x]
    • x:y:excess is a list of at least two elements
    • x:y:nil is a list of exactly two elements
  • head and tail functions (analogs of car and cdr, respectively)
  • !! selection of the nth element from a list using zero-based indexing (e.g., [1,2,3,4,5]!!3)
  • ++ is the append operator
    • takes two lists
    • also inefficient as in Scheme


Tuples

  • can be thought of as a heterogeneous list or struct
  • idea from relational databases
  • a two-element tuple is a called a pair
  • a three-element tuple is a called a triple:
    Prelude> :t (1, "Lewis", 3.76)
    (1,"Lewis",3.76) :: (Fractional a, Num b) => (b,[Char],a)
    


Functions

  • have types
  • use of patterns in function arguments called pattern-directed invocation
  • Haskell prints the arguments to a function which takes >1 argument as a tuple:
    add (x,y) = x+y
    Main> :t add
    add :: Num a => (a,a) -> a
    
  • concept of a type variable which must begin with a lower case letter, typically a, b, and so on
  • polymorphism
  • concept of a class constraint, C a, where C is the name of a class and a is a type variable:
    Prelude> :t (+)
    (+) :: Num a => a -> a -> a
    Prelude>
    


Some user-defined functions

function, parameter, and value names must begin with a lower case letter
    {-
    simple functions
    pattern-directed invocation
    pattern matching
    like cases in Scheme
    -}
    square (x) = x*x
    
    fact (0) = 1
    fact (n) = n * fact (n-1)
    
    --fact (n) = product [1..n]
    
    sumlist ([]) = 0
    sumlist (x:xs) = x + sumlist (xs)
    
    fib (0) = 1
    fib (1) = 1
    fib (n) = fib (n-1) + fib(n-2)
    
    -- gcd is built in Prelude.hs
    gcd1 (u, 0) = u
    gcd1 (u, v) = gcd1 (v, (mod u v))
    
    --gcd1 (u, v) = if v == 0 then u else gcd1 (v, (mod u v))
    
    -- with pattern-directed invocation
    -- reverse is built-in Prelude.hs
    reverse1 [] = []
    reverse1 (h:t) = reverse1 (t ++ [h])
    
    -- without pattern-directed invocation need a head and tail,
    -- or an if-then-else and hd and tl;
    -- head and tail are the analogs of car and cdr, respectively
    reverse2 ([]) = ([])
    reverse2 (lst) = reverse2(tail(lst)) ++ [head(lst)]
    
    reverse3 (lst) =
       if lst == [] then []
       else reverse3 (tail(lst)) ++ [head(lst)]
    
    reverse4 lst@(x:xs) =
       if lst == [] then [] else reverse4 xs ++ [x]
    
    -- inspired by pp. 84-88 of [EMPL];
    -- use difference lists technique
    rev1 ([], m) = m
    rev1 ((x:xs), ys) = rev1 (xs, (x:ys))
    
    reverse5 (lst) = rev1 (lst, [])
    
    -- elem is the Haskell member function in Prelude.hs
    member (_, []) = False
    member (e, (x:xs)) = (x == e) || member (e, xs)
    
    insertineach (_, []) = []
    insertineach (item, (x:xs)) = (item:x):insertineach(item, xs)
    
    -- notice how use of "let"
    -- prevents re-computation
    powerset ([]) = [[]]
    powerset (x:xs) =
       let
          temp = powerset (xs)
       in
          (insertineach (x, temp)) ++ temp
    
    -- can be similarly written with where
    {--
    powerset ([]) = [[]]
    powerset (x:xs) = (insertineach (x, temp)) ++ temp
                      where temp = powerset (xs)
    --}
    


Mergesort

    split ([]) = ([], [])
    split ([x]) = ([], [x])
    split (x:y:excess) =
       let
          (left, right) = split(excess)
       in
          (x:left, y:right)
    
    merge (lst, []) = lst
    merge ([], lst) = lst
    merge (left@(l:ls), right@(r:rs)) =
       if l < r then l:(merge (ls, right))
       else r:(merge (left, rs))
       --if l < r then l:(merge (ls, (r:rs)))
       --else r:(merge ((l:ls), rs))
    
    mergesort ([]) = []
    mergesort ([x]) = [x]
    mergesort (lst) =
       let
          -- split it
          (left, right) = split (lst)
    
          -- mergesort each side
          leftsorted = mergesort (left)
          rightsorted = mergesort (right)
       in
          -- merge
          merge (leftsorted, rightsorted)
    
    -- alternatively
    {--
    mergesort ([]) = []
    mergesort ([x]) = [x]
    mergesort (lst) =
        -- merge
       merge (leftsorted, rightsorted)
       where
          -- split it
          (left, right) = split (lst)
    
          -- mergesort each side
          leftsorted = mergesort (left)
          rightsorted = mergesort (right)
    --}
    


Anonymous or literal functions

    (\n -> n+1) (5)
    addtwo n = n + 2
    map addtwo [1,2,3]
    map (\n -> n+2) [1,2,3]
    
    why use an anonymous function? see definition of string2int


Monads: side-effect-free I/O

An extension of the idea of potentially infinite data structures or streams made possible through lazy evaluation.

Monad takes its name from a branch of mathematics known as category theory.


References


Return Home