Recitation 2

Bohan Jiang

2026-09-10

Logistics

  • data declarations & type synonyms
  • currying & partial application
  • lambda expressions
  • higher-order functions over lists (elem, all, any)
  • point-free style

Algebraic Data Types

data declarations

  • A data declaration introduces a brand-new type with named constructors
data Color = Red | Green | Blue
  deriving (Eq, Ord, Show, Enum, Bounded)
  • Red, Green, Blue are values of type Color – not strings, not variables
  • deriving (...) asks GHC to auto-implement typeclass behavior for you
    • Enum/Bounded are why [minBound .. maxBound] :: [Color] works

Practice

  • Define a type Season with four constructors, deriving Eq, Ord, Show, Enum, Bounded
  • Then evaluate [minBound .. maxBound] :: [Season]
data Season = Spring | Summer | Fall | Winter
  deriving (Eq, Ord, Show, Enum, Bounded)

[minBound .. maxBound] :: [Season]
[Spring,Summer,Fall,Winter]

Type synonyms

  • type gives an existing type a new name -> it does NOT create a new type
type Name = String

greet :: Name -> String
greet n = "Hello, " ++ n

greet "world"
"Hello, world"
  • Purely for readability: Name and String are interchangeable to GHC
  • Handy when a signature is long or when the alias carries meaning (e.g. type Age = Int)

Practice

  • Define type Predicate = Int -> Bool
  • Override existing isEven function by isEven :: Predicate and add one pattern determining an integer is an even number
  • Check its type with :t isEven (does it print Predicate or Int -> Bool?)
type Predicate = Int -> Bool

isEven :: Predicate
isEven n = n `mod` 2 == 0

:t isEven
isEven :: Predicate

Currying & Partial Application

What is currying?

  • Definition: turning a function that takes multiple arguments at once into a chain of functions, each taking exactly one argument
  • Mathematically: \(f(x, y) = x + y\) becomes \(f'(x) = (\lambda y.\ x + y)\)
    • \(f'\) takes one argument \(x\) and returns another function that takes \(y\)

What is partial application?

  • Definition: calling a curried function with fewer arguments than it ultimately needs
  • The result isn’t an error or an incomplete value, but it’s a legitimate new function, still waiting for the rest of its arguments
  • Using \(f'\) from before: \(f'(3)\) is the function “add 3 to whatever you give me next”
  • Partial application is only possible because the function was curried in the first place

Currying in Haskell

  • In Haskell, every function is curried by default
  • a -> b -> c parses as a -> (b -> c)
  • so a “two-argument” function is really a one-argument function that returns another function
add :: Int -> Int -> Int
add x y = x + y

:t add
:t add 3
add 3 4
add :: Int -> Int -> Int
add 3 :: Int -> Int
7
  • add 3 is a perfectly good value – a function still waiting for its second argument

Practice

  • Define combine :: Int -> Int -> Int -> Int that adds three numbers
  • Check :t combine 1 and :t combine 1 2 in ghci – what do you get back at each step?
combine :: Int -> Int -> Int -> Int
combine x y z = x + y + z

:t combine 1
:t combine 1 2
combine 1 2 3
combine 1 :: Int -> Int -> Int
combine 1 2 :: Int -> Int
6

Why argument order matters

  • The same information can be curried in different orders, and the order changes what a partial application gives you
above :: Int -> Int -> Bool
above threshold x = x > threshold

:t above 10
above 10 :: Int -> Bool
  • above 10 is a reusable one-argument function (“is greater than 10”)
  • had the arguments been flipped, partially applying would fix x instead, which is a much less reusable partial application

Practice

  • Define between :: Int -> Int -> Int -> Bool where between lo hi x checks lo <= x && x <= hi
  • What is the type of between 0 10? What does that partial application mean?
between :: Int -> Int -> Int -> Bool
between lo hi x = lo <= x && x <= hi

:t between 0 10
between 0 10 5
between 0 10 :: Int -> Bool
True

Lambda Expressions

Anonymous functions

  • The simplest lambda expression is \(\lambda x . f(x)\)
  • In Haskell, simlarly, \x -> <body> is a function with no name
    • Read the backslash as \(\lambda\) (lambda)
  • Syntax:
\arg -> body
double = \x -> x + x
double 5
Redundant lambda
Found:
double = \ x -> x + x
Why Not:
double x = x + x
10
  • Equivalent to double x = x + x (pattern-match syntax is sugar over a lambda)

Multi-argument lambdas

  • A lambda can take more than one argument, separated by spaces before the arrow
  • Syntax:
\arg1 arg2 -> body
  • This is sugar for a curried chain of one-argument lambdas – exactly the currying idea from before, just written with \ instead of named functions
\x y -> body   ==   \x -> (\y -> body)
addL = \x y -> x + y
:t addL
:t addL 3
addL 3 4
addL :: forall {a}. Num a => a -> a -> a
addL 3 :: forall {a}. Num a => a -> a
7

Practice

  • Rewrite isPositive n = n > 0 using a lambda instead of a named argument
  • Write a lambda that checks whether a value is a member of a fixed list, e.g. [1,2,3], using function elem
isPositive = \n -> n > 0
isPositive (-3)

inList = \x -> x `elem` [1,2,3]
inList 2
Redundant lambda
Found:
isPositive = \ n -> n > 0
Why Not:
isPositive n = n > 0
Avoid lambda using `infix`
Found:
\ n -> n > 0
Why Not:
(> 0)
Redundant lambda
Found:
inList = \ x -> x `elem` [1, 2, 3]
Why Not:
inList x = x `elem` [1, 2, 3]
False
True

Higher-Order Functions

What is the higher-order function?

  • A function that takes other functions as arguments OR return functions
  • What is the example for returning a function?
    • add 3

Common higher-order function?

  • elem: check if the argument is in the provided foldable
  • map: apply a function to every element of a list
  • all
    • similar to map, apply a “function” to a foldable, where the function checks every element in the foldable
  • any

Practice

  • Using all and even, write a check that every element of [2,4,6,8] is even
:t all
all even [2,4,6]
all :: forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
True

Point-Free Style

Same function, no explicit argument

  • If a function’s last argument on both sides matches, you can drop it (\(\eta\)-reduction)
squareAll xs = map (^2) xs 
squareAll = map (^2)
  • Both functions behave identically, but the second one is “point-free”: no explicit variable (“point”) named for the argument

Practice

  • oldEnough age = age >= 18 – rewrite this point-free
  • memberOfXs x = elem x xs (for some fixed list xs = [1,2,3]) – rewrite this point-free using lambda expressions
oldEnough = (>= 18)

xs = [1,2,3]
memberOfXs = \x -> elem x xs
Redundant lambda
Found:
memberOfXs = \ x -> elem x xs
Why Not:
memberOfXs x = elem x xs
Avoid lambda using `infix`
Found:
\ x -> elem x xs
Why Not:
(`elem` xs)
Use infix
Found:
elem x xs
Why Not:
x `elem` xs

Workflow reminder

  • runghc file.hs – run a script’s main, e.g. to see a whole test suite’s results
  • ghci file.hs – load a file, then poke at individual pieces interactively
  • Before composing the next piece, sanity-check with :t – e.g. check the type of a partial application before applying it further