Polymorphism, type classes, maybe, either
Class: CSCE-314
Notes:
Knowledge Check 4
- Things typically involved in implementing recursive functions?
- Base case
- Recursive call
- Termination condition
- This is typically something that you do not account for, (you do not want this to happen)
1. Introduction: Why Do We Need Polymorphism and Type Classes?
- Up to now, many of our functions worked only on specific types (like
IntorBool). - But often we want functions to be more general: e.g., a function to reverse a list of any type, not just
[Int]. - Polymorphism allows functions to work on values of different types without rewriting them for each type.
- Type classes allow us to specify constraints on those types: 'this function works for all types that can be compared for equality' or 'all types that can be ordered.'
Eq,Ord,Show
2. Polymorphism in Haskell
Haskell functions are polymorphic by default if they don’t require special operations.
Example:
length :: [a] -> Int
- Works for
[Int],[Char],[Bool],[Shape]... any type! - The type variable 'a' can stand for any type.
Another example:
identity :: a -> a
identity x = x
- Returns whatever you give it, unchanged.
- 'a' returns itself
- In java you would have to use
genericto describe types - Here you just reuse code and get it to do what you want, but you still got to decide a few things
Teaching point (benefit): polymorphism increases code reuse and reduces redundancy.
3. Type Classes: Eq, Ord, Show
A type class in Haskell is like an interface — it defines a set of functions that a type must implement.
Common built-in type classes:
Eq: for equality (==, /=).
(==) :: Eq a => a -> a -> Bool
Only works on types that are members of the Eq type class.
Ord: for ordering (<, >, <=, >=, compare).
(<) :: Ord a => a -> a -> Bool
- Requires the type to have a defined ordering.
- We could argue that there is an order to for example 'students', in that way strings have an order but how do we come up with that? you need to have ordering.
- There are things that have no order
Show: for converting values to strings (so they can be printed).
show :: Show a => a -> String
- Without Show, you can’t print a type in GHCi.
- We need to be able to convert things to a string so that we can show them
- Sometimes we also want our answer to be a string
Example:
isEqual :: Eq a => a -> a -> Bool
isEqual x y = x == y
maxOfTwo :: Ord a => a -> a -> a
maxOfTwo x y = if x > y then x else y
- Defining an equal sign
- Defining a function that gives the maximum of two numbers/strings/chars
4. Combining Polymorphism and Type Classes
- Polymorphism gives us flexibility.
- Type classes give us constraints.
Example:
describe :: (Eq a, Show a) => a -> a -> String
describe x y = if x == y then "They are equal: " ++ show x else "They are different: " ++ show x ++ " vs " ++ show y
- Turn your function into something that returns a sentence that describes what you are looking at.
- Key idea: type classes let us say 'this function works for many types, as long as they support these operations.'
5. Error Handling with Maybe
Sometimes a function might fail or not return a result.
In many languages, this causes a runtime error. In Haskell, we can model this with Maybe.
data Maybe a = Nothing | Just a
- Nothing OR Just a
Example: safe division:
safeDiv :: Int -> Int -> Maybe Int
safeDiv _ 0 = Nothing
safeDiv x y = Just (x `div` y)
- What is 7/1? that is
Just 7. - What is 7/0? that is runtime error, it explodes, but instead of that it will return
Nothing. - Then we do not do whatever else we needed to do after that check
Example: safe head of a list:
safeHead :: [a] -> Maybe a
safeHead [] = Nothing
safeHead (x:_) = Just x
Teaching point: Maybe makes failure explicit in the type signature
6. Error Handling with Either
Either is like Maybe, but gives more information about what went wrong.
data Either l r = Left l | Right r
- Like
Maybe, but includes error details
Example:
safeDivMsg :: Int -> Int -> Either String Int
safeDivMsg _ 0 = Left "Division by zero!"
safeDivMsg x y = Right (x `div` y)
Either is useful when we want to return not only success/failure but also an error message.
7. Summary
Polymorphism: write functions that work across types (e.g., length :: [a] -> Int).
- Polymorphism - FP: general functions
- Haskell: type variables (a, b, ...)
Type classes: restrict polymorphism to types with specific capabilities (Eq, Ord, Show).
- FP: ad-hoc polymorphisml
- Haskell:
Eq,Ord,Showwith (=>) constraints
Maybe/Either: handle errors safely without crashing, by encoding failure into the type system.
Next time (Wednesday), we’ll practice by writing polymorphic functions with type class constraints, and Friday we’ll apply this to build a dictionary with safe lookups using Maybe.
Takeaway: Many ideas are universal in FP, Haskell provides its own tools for you.