Programming Languages
Typing Systems with Haskell
Jim Posen - ECE/CS 2014
Type Systems
- A type system is the model a programming language uses to assign types to values or variables
- Static vs dynamic typing
- Strong vs weak typing
- Type safety properties
Static vs dynamic type checking
-
Are the types known without running the program?
- Type checkers perform static analysis
- Are the types known during compilation?
- Do variables have types?
- Languages may have optional type declarations
Staticly typed languages
- Java
- C/C++
- SML (functional language)
Dynamicly typed languages
Hybrids
-
Common Lisp
- Dynamic by default with optional type declarations
Strong vs weak typing
- Do values/objects have types?
- Higher standard of type safety
- Weakly typed languages have less type safety
- Not as clear cut as static/dynamic type checking
-
I know it when I see it
Strongly typed languages
-
Java
- ints can be added to Strings
- Is this an exception?
-
Python
-
Racket
- Rationals vs fixnums vs booleans vs symbols
- Weak languages often have more unexpected behavior
Typing Chart
|
Strong
|
Weak
|
Static
|
Java |
C |
Dynamic
|
Python |
JavaScript |
Back to Haskell
Prelude> "hello " ++ "world"
"hello world"
Prelude> "hello " ++ True
:9:13:
Couldn't match expected type `[Char]' with actual type `Bool'
In the second argument of `(++)', namely `True'
In the expression: "hello " ++ True
In an equation for `it': it = "hello " ++ True
OK, there is type safety, so Haskell must be strongly typed
- The above code won’t even compile
- Haskell is staticly typed
Type inference
-
Haskell
infers
the types of variables
- If variable types can’t be inferred, you will get a compilation error
- Type declarations can be used to remove ambiguity
- By convention, functions types are declared
Haskell types
- Use :t to determine type
- Bool, Char, Int, Float, Integer, Double are some basic types
-
[] denotes list
- [Int] is a list of Ints
- [Char] is a String
- This is why you can’t mix types in lists
- (Int, Bool) is a tuple with and Int and a Bool
Type declarations
- Use :: to declare type
- Type declarations can be used to remove ambiguity
Prelude> 5 :: Float
5.0
Prelude> True :: Float
:17:1:
Couldn't match expected type `Float' with actual type `Bool'
In the expression: True :: Float
In an equation for `it': it = True :: Float
Also declare the types of variables
pi :: Double
pi = 3.1415
Function types
- Function types are determined by parameter types and return type
- Parameter and return types separated by -> arrows
sumThree :: Int -> Int -> Int -> Int
sumThree x y z = x + y + z
Let’s annotate some functions
factorial n = product [1..n]
factorial :: Int -> Integer
firstLetter :: [Char] -> Char
fibHelper (a, b) 0 = a
fibHelper (a, b) 1 = b
fibHelper (a, b) n = fibHelper (b, a + b) (n - 1)
fibHelper :: (Integer, Integer) -> Int -> Integer
Type variables
-
What is the type of
head
?
-
head :: [Char] -> Char
-
head :: [Int] -> Int
-
head :: [Bool] -> Bool
- We use a type variable for the type
- head :: [a] -> a
- a represents any type
- Type variables are like generics in Java
What is the type of
zip
?
zip :: [a] -> [b] -> [(a, b)]
Type classes
How about this function that tests equality and returns 1 or 0
foo :: a -> a -> Int
foo x y = if x == y then 1 else 0
Why won’t this compile?
- What if we can’t check the equality of values of type a?
- We need to impose a constraint on the type variable a
- We use type classes for this
Type classes
- Types belong to type classes
- Similar to interfaces in Java
- Below we enforce type variable a is of typeclass Eq
foo :: (Eq a) => a -> a -> Int
foo x y = if x == y then 1 else 0
- Types of class Ord have comparison operators defined
-
show :: (Show a) => a -> String
is defined on types with class
Show
- Int, Integer, Float, Double are Nums
- Int, Integer are Integrals
- Float, Double are Fractionals
Partial Function Application
Lambdas
- Haskell has nice syntax for anonymous functions
addThree x = x + 3
addThree = (\x -> x + 3)
Currying
- In lambda calculus, functions take one parameter
- How do you create a multivariable function?
- Currying!
Currying
- Currying is a way to convert a function with n parameters into a function returning a function with n - 1 parameters
Here is an example without currying
sumThree x y z = x + y + z
Here is the curried form
sumThree = (\x -> (\y -> (\z -> x + y + z))
Currying meets typing
What is the type of the uncurried form?
sumThree :: (Num a) => a -> a -> a -> a
sumThree x y z = x + y + z
What is the type of the curried form?
sumThree :: (Num a) => a -> (a -> (a -> a))
sumThree = (\x -> (\y -> (\z -> x + y + z))
What if I told you -> was right associative?
That would mean
sumThree :: (Num a) => a -> (a -> (a -> a))
is equivalent to
sumThree :: (Num a) => a -> a -> a -> a
Mind Blowing Slide No. 1
- All functions in Haskell take one parameter and are curried
- Haskell syntax makes it easy to forget this sometimes
- What does this mean?
ghci> let sumThree x y z = x + y + z
ghci> ((sumThree 1) 2) 3
6
Partial function application
-
If a function is called with fewer parameters than it takes, it is
partially applied
- You get back a new function that takes the remaining parameters
- This is super useful
Partial application example
Let’s write a function that returns a number if it is positive or 0 if it’s negative
makeNatural = (Num a) => a -> a
makeNatural x = max 0 x
Partial application example
How about?
makeNatural = (Num a) => a -> a
makeNatural = max 0
You can even partially apply infix functions
addThree = (Num a) => a -> a
addThree = (+3)
Function composition
- . operator composes functions
-
(f . g) x = f (g x)
ghci> map (negate . abs) [-1, 2, -4, 5]
[-1, -2, -4, -5]
Function application operator
- Usually function application is performed using spaces
- $ is a lower precedence operator
ghci> map (2*) (filter (< 5) [1..])
[2, 4, 6, 8]
ghci> map (2*) filter (< 5) [1..]
-- Crash!
ghci> map (2*) $ filter (< 5) [1..]
-- Crash!
One more example
Let’s tighten up our
qsort
example
qsort [] = []
qsort (p:xs) = (qsort $ filter (<= p) xs) ++ [p] ++ (qsort $ filter (> p) xs)