Functional Patterns, Recursion & Polymorphism: MPCS 51400
Functional Patterns, Recursion & Polymorphism: MPCS 51400
Functional Patterns,
Recursion & Polymorphism
MPCS 51400
Functions (cont.)
Partial Functions
• Haskell allows for passing around partial functions:
- 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
1 + 2 ==> 3
(+) 1 2 ==> 3
- 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.
Prelude> (1/) 2
0.5
Prelude> (/1) 2
2.0
Sectioning: Subtraction
• Subtraction (-), is a special case because the (-) operator could also mean
negation.
1
1
Prelude> x = 5
Prelude> y = (1 -)
Prelude> y x
-4
1
Function Type (cont.)
• So an expression like this:
(+) 3 4
3 :: 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
• 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:
• Very commonly used in Haskell as a convenience to limit the number of parentheses in an expression.
(2^) (2 + 2) ==> 16
(2^) $ 2 + 2 ==> 16
(2^) 2 + 2 ==> 6
- 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:
• 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> v1 = Vec3 3 3 4
Prelude> v1
Vec3 3.0 3.0 4.0
Types vs. Values
*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 _ = False
isItTwoOrThreeAlwaysFalse 2 = True
isItTwoOrThreeAlwaysFalse 3 = True
• 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
Warning:
Pattern match(es) are non-exhaustive
In an equation for ‘isItTwoOrThreeBad’:
Patterns not matched: #x with #x `notElem` [2#, 3#]
Pattern Matching Tuples
fstCom (x,y) = x
sndCom (x,y) = y
Pattern Matching Tuples
• You can also break down tuples inside a let expression:
data Complex
= Rectangular Double Double
| Polar Double Double
deriving (Show)
{- Tacks on the number two if the list has more than two
elements -}
- Function pattern matching is syntactic sugar for a case expression where the matching is done at the
beginning of the function.
- 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.
4! = 4 * 3 * 2 * 1
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?
- 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.
table2 = None
| k == key = v