Presentation is loading. Please wait.

Presentation is loading. Please wait.

Lecture 2:Data Types, Functional Patterns, Recursion, Polymorphism

Similar presentations


Presentation on theme: "Lecture 2:Data Types, Functional Patterns, Recursion, Polymorphism"— Presentation transcript:

1 Lecture 2:Data Types, Functional Patterns, Recursion, Polymorphism
MPCS 51400: Functional Programming

2 Algebraic Data Types (ADTs)
Haskell allows you to extend the type system with new types via algebraic data types, a type formed by combining other 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++). E.g.: data Rational = Rational Integer Integer 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 Integer values, which represent the numerator and dominator respectively. deriving (Show) : Allows for the ADT to be printed naturally (more on that later..)

3 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

4 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> let oneHalf = Rational 2 4 Prelude> oneHalf Rational 2 4 Prelude> let v1 = Vec Prelude> v1 Vec

5 Ranges Lets say we wanted a list that contains the numbers 1 to 10. We could right it as: myList = [1,2,3,4,5,6,7,8,9,10] But writing out long lists can be tedious. Instead you can use ranges, which make lists that are arithmetic sequences of elements that can be enumerated. Use the syntax [low..high] to create a range: Prelude> [1..20]   [1,2,3,4,5,6,7,8,9,10]    Prelude> ['a'..'z']   "abcdefghijklmnopqrstuvwxyz"   Ranges can also have a a “step value” to increment a range by a certain amount ( [step, low..hight]). The first value in the list will begin at the step value: myEvenNumbersTo20 = [2,4..20] ==> [2,4,6,8,10,12,14,16,18,20] myMultiplesOfThreeTo20 = [3,6..20] ==> [3,6,9,12,15,18]

6 Ranges & Infinite Lists
You can make lists infinite by not specifying an upper bound: numbers = [1..] evenNumbers = [2, 4..] Be careful entering infinite list into the ghci because you could get a infinite printout.

7 Pattern Matching Pattern matching is a way of matching values against patterns and when appropriate binding variables to successful matches (one of my favorite aspects of functional languages): Patterns: Undefined variables, numerical literals, list syntax, and data constructors. Allows for writing functions that can determine two or more possibilities based on which value it matches.

8 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 *Main> isItTwoOrThree 24 False The “_” represents the universal patter that never fails to match (i.e. “anything else” case).

9 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

10 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.

11 Forgetting Pattern Cases Warning
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 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#]

12 Pattern Matching Tuples
You can pattern match on lists instead of using functions (such as “fst” and “snd” for two-tuples) to retrieve its contents: fstCom :: (Integer, Integer) -> Integer fstCom (x,y) = x sndCom :: (Integer, Integer) -> Integer sndCom (x,y) = y

13 Pattern Matching Tuples
You can also break down inside a let expression: fstComLet :: (Integer, Integer) -> Integer fstComLet tup = let (x,_) = tup in x sndComLet :: (Integer, Integer) -> Integer sndComLet tup = let (_,y) = tup in y Use the “_” in for the components we don’t use in other expressions.

14 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

15 Pattern Matching Lists
{- Drops the head element in the list -} dropHead :: [Double] -> [Double] dropHead [] = [] dropHead (_:xs) = xs {- Adds the first two elements in the list -} addFirstTwo :: [Double] -> Double addFirstTwo [] = error "Not enough elements" addFirstTwo (_:[]) = error "Not enough elements" addFirstTwo (x:y:xs) = x + y

16 As-patterns Convenient for allowing 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 = 2:s tackOnTwo l = l

17 Case Expressions Similar to case statements seen in imperative languages (C++, Java, C) but more powerful because case expressions can pattern match on types of 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 expression of pattern -> result                       pattern -> result                          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"

18 ADTs & Pattern Matching
You can pattern match on a ADT’s structure just like other types: data Complex = Rectangular Double Double | Polar Double Double deriving (Show) magnitude :: Complex -> Double magnitude (Rectangular real imaginary) = sqrt $ real^2 + imaginary^2 magnitude (Polar r theta) = r

19 Guards Guards allow two or more possible outcomes when writing functions. The return value of the function is dependent on the evaluation of the evaluation of the guard’s condition. 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 typically used as the last guard pattern. Otherwise is just another name for True and represents the catch-all guard.

20 Guards Use a where-clause to allow for declarations to be used within the guard clauses: taxBracketRate :: Double -> String taxBracketRate amount | inRange = "10%" | inRange = "15%" | inRange = "25%" | inRange = "28%" | inRange = "33%" | inRange = "35%" | otherwise = "39.6%" where inRange minAmt maxAmt = amount >= minAmt && amount < maxAmt

21 Anonymous functions An anonymous function is a function without a name. It is written using lambda syntax represented by a backslash: Prelude> \x -> x :: Integer -> Integer 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.

22 List Recursion Haskell like many other functional languages uses 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' [] = sum' [] = 0 length' (_:xs) = 1 + length' xs sum' (x:xs) = x + sum' xs

23 List Recursion length’ and sum’ function from the previous slide is what we call natural recursion on the list structure: There's a natural correspondence between the equations we use to define length and the list constructors ([] and (:)). In the case where one of the constituent values of an argument (e.g., the cs in the second line above) has the same type as whole argument, we apply our top- level function to that value. © 2009–2016 Ravi Chugh, Stuart A. Kurtz

24 Polymorphism Polymorphism is 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: Integers, Double, and Floats? Well 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: sumFloat :: Float -> Float -> Float sumInt :: Int -> Int -> Int sumDouble :: Double -> Double -> Double Polymorphism makes programs easily extensible and facilitates rich libraries and common vocabularies.

25 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): > Pair 1 "one" :: Pair Int String > Pair "one" 1.0 :: Pair String Double Type variables always begin with a lower-case letter.

26 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!!!

27 Polymorphic functions
Polymorphic functions are functions whose types include type variables. For example: length [] = 0 length (_:as) = 1 + length as What is the the type of length? Could be: [Int] -> Int or [Double] -> Int, etc… But what is the type of length inferred by Haskell? > :t length length :: [a] -> Int This means that length can take as input a list over any type, and compute its length.


Download ppt "Lecture 2:Data Types, Functional Patterns, Recursion, Polymorphism"

Similar presentations


Ads by Google