100% found this document useful (1 vote)
57 views49 pages

Functional Patterns, Recursion & Polymorphism: MPCS 51400

The document discusses various functional programming concepts in Haskell including: 1) Partial functions and sectioning of functions which allows passing a fixed number of arguments to a function. 2) Function composition which combines multiple functions such that the result of one is passed as an argument to the next. 3) Anonymous functions defined using lambda syntax without a name. 4) Algebraic data types which allow defining new types by combining other types using data constructors. 5) Pattern matching which matches values against patterns to bind variables or determine multiple possibilities based on the match.

Uploaded by

Esther Alvarado
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
100% found this document useful (1 vote)
57 views49 pages

Functional Patterns, Recursion & Polymorphism: MPCS 51400

The document discusses various functional programming concepts in Haskell including: 1) Partial functions and sectioning of functions which allows passing a fixed number of arguments to a function. 2) Function composition which combines multiple functions such that the result of one is passed as an argument to the next. 3) Anonymous functions defined using lambda syntax without a name. 4) Algebraic data types which allow defining new types by combining other types using data constructors. 5) Pattern matching which matches values against patterns to bind variables or determine multiple possibilities based on the match.

Uploaded by

Esther Alvarado
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 49

2

Functional Patterns,
Recursion & Polymorphism
MPCS 51400
Functions (cont.)
Partial Functions
• Haskell allows for passing around partial functions:

- Functions that are produced by other functions where a fixed number of


arguments are already applied.

- For example, the “(++)” operator is part of the Prelude and appends two
lists together:

Prelude> :t (++)
(++) :: [a] -> [a] -> [a]
Prelude> myPartial = (++) [1,2]
Prelude> myPartial [4,5,6]
[1,2,4,5,6]
Prelude> myPartial [111,112,113]
[1,2,111,112,113]
Function Sectioning
• Sectioning is partial application of infix operators. It allows you to choose whether the
argument you’re partially applying the operator to is the first or second argument

-- normal infix operation for addition operator 

1 + 2  ==> 3

-- prefix notation for the addition operator

(+) 1 2 ==>  3

• Sectioning notation for addition operator : (+1)  or (1+)

- Here we are applying one of the argument (i.e. operands) for the addition operation.
Sectioning (Cont’d.)
• Commutative functions (e.g. addition) no difference between (+)
or (3+) because the order won’t change the result.

• Non-commutative operations order matters  (note (/) performs


real number division)

Prelude> (1/) 2 

0.5 

Prelude> (/1) 2 

2.0
Sectioning: Subtraction
• Subtraction (-), is a special case because the (-) operator could also mean
negation. 

Prelude> 2 - 1  -- works just fine

Prelude> (-) 2 1 -- works just fine

1


Prelude> (-2) 1 -- ERROR: does not work.


Sectioning: Subtraction
• Still can use subtraction but it either must be the first argument or use
the (subtract x) function for subtraction sectioning: 

Prelude> x = 5 

Prelude> y = (1 -) 

Prelude> y x

-4 

Prelude> (subtract 2) 3 -- using the (subtract x) function

1
Function Type (cont.)
• So an expression like this:
(+) 3 4

• Could have the various subexpression types:


(+) :: Int -> Int -> Int {- also equivalent to ==> Int -> (Int -> Int) -}

3 :: Int

(+) 3 :: Int -> Int

4 :: Int

(+) 3 4 :: Int
Function Composition
• Function composition allows you to combine/chain multiple functions
together such that the result of applying one function gets passed to the next
function as an argument

• Similar to function composition in math:


(f . g) x = f (g x)

• Example:
Prelude> addOne n = n + 1
Prelude> addNegate = negate.addOne
Prelude> addNegate 34
-35
Prelude> addNegate (-34)
33
Function Composition
• We can also have partial functions as part of the function
composition

Prelude> addNum n m = n + m
Prelude> addNegate = negate.addNum 4
Prelude> addNegate (-34)
30
Prelude> addNegate 34
-38
($) Operator 
• ($) is the function application operator. The following expressions are equivalent and produce the
value 3:

addOne num = num + 1

addOne 2 addOne $ 2 ($) addOne 2

• Very commonly used in Haskell as a convenience to limit the number of parentheses in an expression.  
(2^) (2 + 2) ==> 16 

- Can replace those parentheses 

(2^) $ 2 + 2 ==> 16

- Without either parentheses or $ 

(2^) 2 + 2 ==> 6

- Can also stack up the $ in an expression 

(2^) $ (+2) $ 3*2 ==> 256


Anonymous Functions
• An anonymous function is a function without a name. It is written using lambda syntax
represented by a backslash:

Prelude> \x -> x + 2 :: Int -> Int


Prelude> (\ x -> x + 2 ) 3
5
• Here’s two ways to write the triple function:
{- Named function -}
triple :: Integer -> Integer
triple x = x * 3
{- Anonymous function -}
(\x -> x * 3) :: Integer -> Integer
• Why Anonymous functions?

- Mostly used as a function that is passed to a higher-order function (more on this next
week) since in most cases that’s the only place you are going to use that particular
function.
Function Composition and
Lists
• Type for the (.) operator:

(.) :: (b -> c) -> (a -> b) -> a -> c
• Example:
Prelude> negate . sum $ [1, 2, 3, 4, 5]
-15
-- note: this code works as well
==> negate (sum [1, 2, 3, 4, 5])
==> negate (15)
==> -15
PointFree Style
• Refers to a style of composing functions without specifying their
arguments:

Prelude> f = negate . sum


Prelude> f [1, 2, 3, 4, 5]
-15

Prelude> f = length . filter (== 'a')


Prelude> f "abracadabra"
5
• This style makes code much cleaner and clearer because the reader
focuses on the function rather than the data.
Base Library
• From Hackage:

“Hackage is the Haskell community's central package


archive of open source software. Package authors use it to
publish their libraries and programs while other Haskell
programmers use tools like cabal-install to download and
install packages (or people get the packages via their
distro).”

- Base Library: https://hackage.haskell.org/package/base

- Haskell Prelude: https://hackage.haskell.org/package/


base-4.12.0.0/docs/Prelude.html
Algebraic Data Types
Algebraic Data Types (ADTs)
• Haskell allows you to extend the type system with new types via algebraic data
types, which is a type formed by combining other types. Often times called tagged
unions or sum types.

• An ADT consists of zero or more data constructors:

• Data constructors can be thought of as a box with a fixed number of fields of a


fixed type (“similar” to unions in C and C++).

data Rational = Rational Int Int


deriving (Show)
• data : Keyword that introduces the declaration of a new type, which in this case is
called Rational. Types and data constructors ALWAYS begin with a capital letter.

• Rational: We define data constructor Rational, which has a place for two Int
values, which represent the numerator and dominator respectively.

• deriving (Show) : Allows for the ADT to be printed naturally (more on that later…)
ADTs (Cont’d)
• ADTs can have more than just one data constructor by using a Pipe (“|”):
data Complex
= Rectangular Double Double
| Polar Double Double
deriving (Show)

data Vector
= Vec2 Double Double
| Vec3 Double Double Double
| Vec4 Double Double Double Double
deriving (Show, Eq)
ADTs creation
• Construction of a ADT involves using one of its data
constructors along with the values for the constructor’s fields
(if applicable):

Prelude> oneHalf = Rational 2 4


Prelude> oneHalf
Rational 2 4

Prelude> v1 = Vec3 3 3 4
Prelude> v1
Vec3 3.0 3.0 4.0
Types vs. Values

Types Int Bool Rational Vector

Vec2 2.0 3,0,


Rational 5 3,
1, 2, 3, 4, 5, Vec3 5.0 7.0 9.0,
Values True, False Rational 7 8,
… Vec2 3.0 4.0,


Pattern Matching
Pattern Matching
• A way of matching values against patterns and binding
variables to successful matches (one of my favorite aspects of
functional languages):

- Patterns: numerical literals, list syntax, data constructors,


undefined variables.

- Allows for writing functions that can determine two or


more possibilities based on which value it matches.
Pattern Matching Examples
{- True if the Integer is 2 or 3, otherwise False-}
isItTwoOrThree :: Integer -> Bool
isItTwoOrThree 2 = True
isItTwoOrThree 3 = True
isItTwoOrThree _ = False

*Main> isItTwoOrThree 3
True
*Main> isItTwoOrThree 2
True
*Main> isItTwoOrThree 24
False

• The “_” represents the universal pattern that never fails to match (i.e. “anything else”
case).
Pattern Matching Cases
• The order of pattern matches matters! The evaluation of pattern matching
proceeds from top to bottom and left to right on each case. Thus, the following
example always returns “False” because the universal pattern will always
match

isItTwoOrThreeAlwaysFalse :: Integer -> Bool

isItTwoOrThreeAlwaysFalse _ = False

isItTwoOrThreeAlwaysFalse 2 = True

isItTwoOrThreeAlwaysFalse 3 = True

• Order patterns from most specific to least specific

• Normally, the compiler will throw a pattern match warning if the above case
happens
Forgetting Pattern Cases
• What happens if we forget to match a case in our pattern?
isItTwoOrThree :: Integer -> Bool

isItTwoOrThree 2 = True

isItTwoOrThree 3 = True

• The above example is considered an incomplete pattern because it


cannot match any other data (i.e., all other integers except 2 and 3).

• Incomplete pattern matches applied to values that they cannot


handle return a bottom, a non-value used to denote that the program
cannot return a value or result. This problem causes the program to
throw an exception, which if unhandled, makes the program fail.
Forgetting Pattern Cases
Warnings
• You can be notified at compile time when your patterns are non-exhaustive (i.e.
they don’t handle every possible case) by turning on all warnings.

Prelude> :set -Wall --turn on Prelude> :set -w --turn off

• Thus, the following code will provide a non-exhaustive warning:


isItTwoOrThreeBad :: Integer -> Bool
isItTwoOrThreeBad 2 = True
isItTwoOrThreeBad 3 = True

Warning:
Pattern match(es) are non-exhaustive
In an equation for ‘isItTwoOrThreeBad’:
Patterns not matched: #x with #x `notElem` [2#, 3#]
Pattern Matching Tuples

• You can pattern match on tuples instead of using functions


(such as “fst” and “snd” for two-tuples) to retrieve its
contents:

fstCom :: (Int, Int) -> Int

fstCom (x,y) = x

sndCom :: (Int, Int) -> Int

sndCom (x,y) = y
Pattern Matching Tuples
• You can also break down tuples inside a let expression:

fstComLet :: (Int, Int) -> Int


fstComLet tup = let (x,_) = tup in x

sndComLet :: (Int, Int) -> Int


sndComLet tup = let (_,y) = tup in y

• Use the “_” in for the components we don’t use in other


expressions.
Pattern Matching Lists
• Pattern matching on lists can be done by using the “: “
operator, which matches on one element within the list and
the rest of the list:

- x:xs ==> is common notation for retrieving the head of


the list (x) and the remaining elements of the list minus the
head (xs).

listHead :: [Double] -> Double


listHead [] = error "Can't not retrieve the head of an empty list"
listHead (x:xs) = x
ADTs & Pattern Matching
• You can pattern match on an ADT’s structure just like other
types:

data Complex
= Rectangular Double Double
| Polar Double Double
deriving (Show)

magnitude :: Complex -> Double


magnitude (Rectangular a b) =
sqrt $ a^2 + b^2
magnitude (Polar r theta) = r
As-Patterns

• A convenient way to name a pattern that can be used on the


right-hand side of an expression:

- Use the “@“ symbol to give a name to the pattern.

{- Tacks on the number two if the list has more than two
elements -}

tackOnTwo :: [Double] -> [Double]


tackOnTwo s@(_:_:xs) = 2:s
tackOnTwo l = l
Case Expressions
• Similar to case statements seen in imperative languages (C++, Java, C) but more powerful because case
expressions can pattern match on expressions:

- Function pattern matching is syntactic sugar for a case expression where the matching is done at the
beginning of the function.

- Case expressions allow you to pattern match in the middle of an expression.

- Syntax:
case expr of 
patternA -> exprA   
     patternB -> exprB
     patternC -> exprC

      
isEvenText :: Integer -> String
isEvenText num = let message num parity = "The number" ++ show num ++ " is " ++ parity
in
case even num of
True -> message num "even"
False -> message num “odd"
Guards
• Allows two or more possible outcomes when writing functions. The return value of the
function is dependent on the evaluation of the guard’s condition expression

- Guard syntax is represented by pipes (|) that follow a function's name and its
parameters.

- They are mainly used for decomposing complex conditional expressions into more
readable code.

bmiDescription ::Double -> String


bmiDescription bmi
| bmi <= 18.5 = "Underweight"
| bmi <= 25.0 = "Normal weight"
| bmi <= 30.0 = "Overweight"
| otherwise = “Obesity"
• otherwise is a boolean expression typically used as the last guard pattern. Otherwise
evaluates to True and represents the catch-all guard.
Guards
• Use a where-clause to allow for declarations to be used within the guard
clauses:

taxBracketRate :: Double -> String


taxBracketRate amount
| inRange 0 9275 = "10%"
| inRange 9275 37650 = "15%"
| inRange 37650 91150 = "25%"
| inRange 91150 190150 = "28%"
| inRange 190150 413350 = "33%"
| inRange 413350 415050 = "35%"
| otherwise = "39.6%"
where inRange minAmt maxAmt =
amount >= minAmt && amount < maxAmt
Recursion
Recursion Review
• Recursion is defining an operation in terms of itself:

- In Haskell, those “operations” are functions that call


themselves to solve problems.

• Every recursive function contains at least 2 cases:

- Base case(s): The value is known without referring to the


function. A base case stops a recursive execution.

- Recursive case: The value is defined in terms of the function.

• There are NO LOOPS in Haskell. You will get comfortable with


recursion in this course.
Factorial
• You are all probably familiar with factorials. For example:

4! = 4 * 3 * 2 * 1

• We can define factorials as a recurrence relation as such:

• Thus, factorial can be defined in Haskell as such:

factorial 1 = 1
factorial n = n * factorial (n - 1)
List Recursion
• Haskell like many other functional languages use recursion to
iterate over structures (e.g. lists) instead of looping constructs
(e.g. for, while, do, etc.)

• How can we sum up the elements within the list or find the
length of a list? Recursion

length' [] = 0 sum' [] = 0
length' (_:xs) = 1 + length' xs sum' (x:xs) = x + sum' xs
List Recursion in
Lec2.hs
Polymorphism
Polymorphism
• The ability to implement expressions that can accept arguments and return
results of different types without having to write variations on the same
expression for each type.

• What if we wanted to use the (+) operator for all types of numbers: Int, Double,
and Float?

- If we did not have polymorphism then we would have to write multiple


functions that have the same addition code, since we cannot mix types. For
example,

addFloat :: Float -> Float -> Float

addInt :: Int -> Int -> Int

addDouble :: Double -> Double -> Double

• Polymorphism makes programs easily extensible and facilitates rich libraries.


Type Variables
• A type variable is a way to refer to an unspecified type or set of types
in Haskell type signatures.

- They can be used to define polymorphic abstract data types:

data Pair x y = Pair x y

- x and y are type variables (i.e. they can be represented by any


concrete type):

Prelude> Pair 1 "one" :: Pair Int String

Prelude> Pair "one" 1.0 :: Pair String Double

• Type variables always begin with a lower-case letter.


Polymorphic functions
• Polymorphic functions are functions whose types include type variables:
last [] = error "List is empty"
last (x:[]) = x
last (_:xs) = last xs
What is the the type of last?

Could be: [Int] -> Int or [Double] -> Double, etc…

• But what is the type of last inferred by Haskell?


Prelude> :t last

last :: [a] -> a

- This means that last can take as input a list over any type, and return the last
element in the list.
Maybe Type
• Used to package either zero or one value of a given type. This type is
useful for dealing with situations where we want to return "no
result”:

data Maybe a
= Nothing
| Just a
- It is often preferable to denote "errors" explicitly using a Maybe
value rather than implicitly with failure.

• This type is seen in many different functional languages (e.g. options


in SML) and is becoming a feature built into more modern imperative
languages (e.g., Swift - optional type). No more NULL or None!!
Recursive ADTs
Recursive ADTs

• A data constructor of a ADT can also contain a field with the


same type as the ADT that is being defined. We call this ADT
type a Recursive ADT.

• For example, a recursive data structure that describes a finite


function from keys of type String to values of type Int:

data Bindings = None

| Bind String Int Bindings


Creation of Bindings

• Note the “()” or $ around the recursive binding expression.

table1 = Bind "Bob" 34 (Bind "Sally" 12 (Bind "Joe" 52 None))

table2 = None

table3 = Bind "Carl" 28 None

table4 = Bind "Bob" 34 $ Bind "Sally" 12 $ Bind "Joe" 52 None


Recursive ADTs
• To manipulate this data structure, we'll use two functions:
bind and lookup

lookup :: String -> Bindings -> Int

lookup _ None = error "value: key is not bound."

lookup key (Bind k v bs)

| k == key = v

| otherwise = lookup key bs


Recursive ADTs
• The bind function takes into consideration if the key is already
in the Bindings to alter the value:

bind :: String -> Int -> Bindings -> Bindings


bind key value None = Bind key value None
bind key value (Bind k v bs)
| k == key = Bind key value bs
| otherwise = Bind k v (bind key value bs)

You might also like