Programming Languages

Datatypes & IO

Jim Posen - ECE/CS 2014

Last time on Programming Languages

  • We discussed Haskell’s static type system
  • Values in Haskell have types
  • Haskell infers the types of values
  • Types have typeclasses
  • Functions have types

Let’s make some types

Types in other languages

  • Object oriented languages allow you to create types
  • In OOP, classes define types
  • In C, you can use structs and unions

What types have we seen?

  • Bool, Char, Int, Float, Integer, Double
  • Often these are “primitives” in other languages

Let’s look at Bool

  • A Bool is either True or False
data Bool = False | True

Points

  • We want a datatype for a point in the 2D plane
data Point = Point Float Float

What does that do?

  • Given two Floats, we can create a Point
let x = Point 3.0 4.0 :: Point

Shape Types

  • There are some operations we want to do on 2D shapes
  • Let’s just consider Rectangles

Circles

  • A Circle can be defined by a center and a radius
data Shape = Circle Point Float

Rectangles

  • But a rectangle is also a shape
  • A rectangle can be defined by the top left corner and the bottom right corner
data Shape = Circle Point Float | Rectangle Point Point

Datatypes

  • A datatype has one or more constructors
  • An value of a datatype is built with one of the constructors
  • The value has each of the required parameters

Constructors

  • Constructors are just functions
  • Constructors take arguments and return a value of a datatype
ghci> :t Point
Point :: Float -> Float -> Point

Constructors can be partially applied

ghci> let points = map (Point 0) [1 2 3]

Why doesn’t this work?

Prelude> map (Point 1) [0, 2, 3]
:7:1:
No instance for (Show Point) arising from a use of `print'
Possible fix: add an instance declaration for (Show Point)
In a stmt of an interactive GHCi command: print it

Typeclasses again

  • Point needs typeclass Show
  • deriving keyword used to give typeclasses to datatypes
data Point = Point Float Float deriving (Show)

Operating on Datatypes

How do we compute the area of a Shape?

area :: Shape -> Float
area s = ...

Pattern matching to the rescue!

area :: Shape -> Float
area (Circle center radius) = pi * r ^ 2
area (Rectangle (Point x1 y1) (Point x2 y2)) = (abs $ x2 - x1) * (abs $ y2 - y1)

Case Expressions

  • Sometimes you can’t easy pattern match in the function declaration
  • Case statement performs pattern matching
  • myFavoriteShape :: Shape
    ...
    
    doILikeCircles =
        case myFavoriteShape of
        Circle _ _ -> True
        Rectangle _ _ -> False
    

Let’s model a Person

  • A person has a first name, last name, phone number, email address, age, and height
data Person = Person String String String String Int Float

How do we get a person’s age?

age :: Person -> Int
age (Person _ _ _ _ a _) = a

Oh hell no. This is bad.

Record Syntax

  • Special syntax for named constructor parameters
  • You don’t need to remember the order of parameters
  • Haskell generates getter functions for you
ghci> data Person = Person {
    firstName :: String,
    lastName :: String,
    phoneNumber :: String,
    emailAddress :: String,
    age :: Int,
    height :: Float
    } deriving (Show)

ghci> let chuck = Person {
    firstName = "Chuck",
    lastName = "Norris",
    phoneNumber = "(123) 456-7890",
    emailAddress = "chuck@norr.is",
    age = -5,
    height = 200
    }
ghci> height chuck
200

Recursive Data Types

  • Recursive data types are often very useful
  • Let’s make a tree of Ints
data Tree = EmptyTree | Node Int Tree Tree

Polymorphic Data Types

  • Sometimes we want general types
  • Say we want a Tree of Chars or any other type
  • We want to construct a new type for each type of Tree
  • Think of generics in Java, but better

Instead of defining a type, we define a Type constructor

data Tree a = EmptyTree | Node a (Tree a) (Tree a)

One caveat: Tree is NOT a type, it is a type constructor

This is an invalid type declaration

insert :: a -> Tree -> Tree

This is correct

insert :: a -> Tree a -> Tree a

Lists

  • Let’s make a Lisp-style linked list
  • A list is either nil or a (cons a b)
data List a = Nil | Cons a (List a)

Mind blowing slide 2

  • The lists we have been using all along are just syntactic sugar for this type of list
  • Nil => []
  • Cons x xs => (x:xs)
  • Cons x Nil => [x]
  • The reason list pattern matching works is because pattern matching with this data type works

Input/Output

How does Haskell do IO?

  • Programs are useless without IO
  • Without IO, everything can be computed at compile time
  • However, IO is a side effect

Side effects revisited

  • Recall the definition of referential transparency
  • A function applied to some arguments can be replaced by its value
  • What is the behavior of getLine and putStrLn ?

Input

If we try to declare getLine :: String it must always return the same string

This is not what we want

Output

If we try to declare putStrLn :: String -> () it is not referentially transparent as it has a side effect

IO Witchcraft

  • Haskell’s way around this is to use the IO type constructor
  • An IO String is an object that contains a string
  • How do we get the value out of an IO?
  • Pattern matching will not work

A Simple Greeting Program

greet :: IO ()
greet = do
    putStr "What is your name? "
    name <- getLine
    putStrLn $ "Hi " ++ name ++ "!"
    return ()

What is an IO ()?

It’s a () wrapped in an IO

What is an IO String?

It’s a String wrapped in an IO

This may be a bit more useful

greet :: IO String
greet = do
    putStr "What is your name? "
    name <- getLine
    putStrLn $ "Hi " ++ name ++ "!"
    return name

Return (to a time before you knew what return was)

  • return in Haskell is unlike any other return you have seen before
ghci> :t return
return :: Monad m => a -> m a

That’s scary.

  • All return does is takes an a and returns an IO a
  • return wraps an object in an IO
  • The last statement in your do block is the return value of the function

What does this do?

helloWorld :: IO ()
helloWorld = do
    return ()
    putStrLn "Hello World!"

The main function

  • Compiled Haskell programs execute the main function
  • main :: IO ()
    main = ...