Syntax in
Functions
- Pattern matching
- specifying patterns to which some data should conform and then checking to see if it does and deconstructing the data according to those patterns.
- Define separate function bodies for different patterns.
- Can pattern match on any data type — numbers, characters, lists, tuples, etc.
- Patterns will be checked from top to bottom
- Order is important when specifying patterns and it's always best to specify the most specific ones first and then the more general ones later.
- Non-exhaustive patterns fail
- _ means the same thing as it does in list comprehensions.
- Can also pattern match in list comprehensions.
- Should a pattern match fail, it will just move on to the next element.
- If you want to bind to several variables we have to surround them in parentheses.
- error function takes a string and generates a runtime error - causes the program to crash.
- Note that (x:[]) == [x] and (x:y:[]) == [x,y]
- We can't rewrite (x:y:_) with square brackets because it matches any list of length 2 or more.
- empty list - also known as the edge condition.
- as patterns - a name and an @ in front of a pattern.
- For instance, the pattern xs@(x:y:ys) lets you get the whole list via xs
- capital "" = "Empty string, whoops!"
- capital all@(x:xs) = "The first letter of " ++ all ++ " is " ++ [x]
- ghci> capital "Dracula" --> "The first letter of Dracula is D"
- Can't use ++ in pattern matches. If you tried to pattern match against (xs ++ ys), what would be in the first and what would be in the second list? It doesn't make much sense. It would make sense to match stuff against (xs ++ [x,y,z]) or just (xs ++ [x]), but because of the nature of lists, you can't do that.
- Guards, guards!
- patterns ensure a value conforms to some form and deconstruct it
- guards test whether some property of a value (or several of them) are true or false.
- otherwise is defined simply as otherwise = True and catches everything.
- If all the guards of a function evaluate to False, evaluation falls through to the next pattern. That's how patterns and guards play nicely together. If no suitable guards or patterns are found, an error is thrown.
- There's no = right after the function name and its parameters, before the first guard.
- Guards can also be written inline, it's less readable, even for very short functions.
- Note: Not only can we call functions as infix with backticks, we can also define them using backticks.
- Where!?
- Put the keyword where after the guards (usually it's best to indent it as much as the pipes are indented) and then define several names or functions.
- It improves readability by giving names to things.
- Can make our programs faster since stuff like our bmi variable here is calculated only once.
- The names we define in the where section of a function are only visible to that function
- Align names at a single column otherwise Haskell gets confused because then it doesn't know they're all part of the same block.
- where bindings aren't shared across function bodies of different patterns. If you want several patterns of one function to access some shared name, you have to define it globally.
- You can also use where bindings to pattern match
- where bmi = weight / height ^ 2
- (skinny, normal, fat) = (18.5, 25.0, 30.0)
- Can also define functions in where blocks.
- calcBmis xs = [bmi w h | (w, h) <- xs]
- where bmi weight height = weight / height ^ 2
- The reason we had to introduce bmi as a function in this example is because we can't just calculate one BMI from the function's parameters. We have to examine the list passed to the function and there's a different BMI for every pair in there.
- where bindings can also be nested. It's a common idiom to make a function and define some helper function in its where clause and then to give those functions helper functions as well, each with its own where clause.
- Let it be
- Where bindings are a syntactic construct that let you bind to variables at the end of a function and the whole function can see them, including all the guards.
- Let bindings let you bind to variables anywhere and are expressions themselves but don't span across guards.
- let bindings can be used for pattern matching.
- cylinder r h =
- let sideArea = 2 * pi * r * h
- topArea = pi * r ^2
- in sideArea + 2 * topArea
- The form is let <bindings> in <expression>.
- The names that you define in the let part are accessible to the expression after the in part.
- As you can see, we could have also defined this with a where binding.
- Names are also aligned in a single column.
- Difference with where is that let bindings are expressions themselves.
- where bindings are just syntactic constructs.
- Just like the if else statement is an expression that can go almost anywhere
- ghci> 4 * (let a = 9 in a + 1) + 2 --> 42
- Can also be used to introduce functions in a local scope:
- ghci> [let square x = x * x in (square 5, square 3, square 2)] --> [(25,9,4)]
- Can separate them with semicolons.
- ghci> (let a = 100; b = 200; c = 300 in a*b*c, let foo="Hey "; bar = "there!" in foo ++ bar) --> (6000000,"Hey there!")
- You don't have to put a semicolon after the last binding but you can if you want.
- Pattern matching with let bindings are very useful for quickly dismantling a tuple into components and binding them to names and such.
- ghci> (let (a,b,c) = (1,2,3) in a+b+c) * 100 --> 600
- You can also put let bindings inside list comprehensions.
- calcBmis xs = [bmi | (w, h) <- xs, let bmi = w / h ^ 2]
- We include a let inside a list comprehension much like we would a predicate, only it doesn't filter the list, it only binds to names.
- The names defined in a let inside a list comprehension are visible to the output function and all predicates and sections that come after of the binding.
- calcBmis xs = [bmi | (w, h) <- xs, let bmi = w / h ^ 2, bmi >= 25.0]
- We can't use the bmi name in the (w, h) <- xs part because it's defined prior to the let binding.
- We omitted the in part of the let binding when we used them in list comprehensions because the visibility of the names is already predefined there.
- However, we could use a let in binding in a predicate and the names defined would only be visible to that predicate.
- The in part can also be omitted when defining functions and constants directly in GHCi. If we do that, then the names will be visible throughout the entire interactive session.
- Case expressions
- Case expressions are expressions much like if else expressions and let bindings.
- Can do pattern matching.
- Pattern matching on parameters in function definitions is actually syntactic sugar for case expressions.
- These two pieces of code do the same thing and are interchangeable:
- head' [] = error "No head for empty lists!"
- head' (x:_) = x
- head' xs = case xs of [] -> error "No head for empty lists!"
- (x:_) -> x
- case expression of pattern -> result
- pattern -> result
- pattern -> result
- ...
- expression is matched against the patterns.
- If it falls through the whole case expression and no suitable pattern is found, a runtime error occurs.
- Case expressions can be used pretty much anywhere. For instance:
- describeList xs = "The list is " ++ case xs of [] -> "empty."
- [x] -> "a singleton list."
- xs -> "a longer list."
- They are useful for pattern matching against something in the middle of an expression.
- Could also define like this:
- describeList xs = "The list is " ++ what xs
- where what [] = "empty."
- what [x] = "a singleton list."
- what xs = "a longer list."
Questions
- Why can we pattern match against cons (:) but not (++)? Because (:) is a constructor but (++) is a function??
- Why can't we match stuff against (xs ++ [x,y,z]) or (xs ++ [x]) ??
- Why can't we use an as-pattern in a where clause?
e.g.: where all@(skinny, normal, fat) = (18.5, 25.0, 30.0) - It works! We can use as-pattern in a where clause.
No comments:
Post a Comment