Monday, October 20, 2014

Haskell - Functions, Overloading, Pattern Matching, Variables

III. Functions

Function application is left associative
The expression a b c d is equivalent to (((a b) c) d)

If we want to use one expression as an argument to another, we have to use explicit parentheses to tell the parser what we really mean

Defining and calling functions is simple
add a b = a + b
add 1 2


When we apply add to the values 1 and 2, the variables a and b on the left hand side of our definition are given (or “bound to”) the values 1 and 2, so the result is the expression 1 + 2.
Haskell doesn't have a return keyword, as a function is a single expression, not a sequence of statements. The value of the expression is the result of the function. (Haskell does have a function called return, but we won't discuss it for a while; it has a different meaning than in imperative languages.)
When you see an = symbol in Haskell code, it represents “meaning”: the name on the left is defined to be the expression on the right.

A function can be defined for different arguments (the order in which the definitions are written is important)

When a function has type variables in its signature, indicating that some of its arguments can be of any type, we call the function polymorphic

When we want to apply last to, say, a list of Char, the compiler substitutes Char for each a throughout the type signature, which gives us the type of last with an input of [Char] as [Char] -> Char

This kind of polymorphism is called parametric polymorphism. The choice of naming is easy to understand by analogy: just as a function can have parameters that we can later bind to real values, a Haskell type can have parameters that we can later bind to other types

Subtype polymorphism refers to ability of methods in some base type (base class) process values of its subtypes (subclasses) *without* re-implementing these methods.
It's restricted variant of parametric polymorphism.

Ad-hoc polymorphism refers to concept of overloading: re-implementing function for every type which function must be able to process. Don't know, but may be ad-hoc polymorphism should not be called polymorphism at all. (for example, in Hutton's book "Programming in Haskell" there are two distinct sections for "Polymorphic types" and "Overloaded types") Haskell supports overloading via type classes.


Parametric polymorphism can be thought of as infinitely overloaded types, so not only ad-hoc polymorphism should be called polymorphism, it is in some sense more general than parametric polymorphism.

Overloading

Imagine an operator + that may be used in the following ways:
  1. 1 + 2 = 3
  2. 3.14 + 0.0015 = 3.1415
  3. 1 + 3.7 = 4.7
  4. [1, 2, 3] + [4, 5, 6] = [1, 2, 3, 4, 5, 6]
  5. [true, false] + [false, true] = [true, false, false, true]
  6. "bab" + "oon" = "baboon"
To handle these six function calls, four different pieces of code are needed—or three, if strings are considered to be lists of characters:
Thus, the name + actually refers to three or four completely different functions. This is an example of overloading. (Note that string types used in the last case do not, by themselves, lend themselves to the programmer naturally assuming concatenation, rather than addition, is meant; consider "123" + "456", which might reasonably be expected to yield "579". Overloading can therefore provide different meaning, or semantics, for an operation, as well as differing implementations.)

Function types and purity
:type lines
lines :: String -> [String] 
We can read the -> above as “to”, which loosely translates to “returns”. 
The signature as a whole thus reads as “lines has the type String to list-of-String”.
ghci> lines "the quick\nbrown fox\njumps"
["the quick","brown fox","jumps"] 
The above function akes one String, and returns many.

Side effects are essentially invisible inputs to, or outputs from, functions. In Haskell, the default is for functions to not have side effects: the result of a function depends only on the inputs that we explicitly provide. We call these functions pure; functions with side effects are impure.

If a function has side effects, we can tell by reading its type signature: the type of the function's result will begin with IO
ghci> :type readFile
readFile :: FilePath -> IO String 
Haskell's type system prevents us from accidentally mixing pure and impure code. the 'IO' monad indicates that the function 'returns' its side effects.

The function readFile performs some I/O with the outer world'. Pure functions as it was mentioned above do not interact with the rest of the world. so the use of the name 'IO

Impure means that if the function is called multiple times the results are not guaranteed to be the same even given the same arguments. IO has an inherent no guarantee quality. If I call input 100 times, can I be sure it will always give me the same results? Not likely! Hence, it's impure.

Syntax in Functions: (pattern matching, guards, where, let / in, case)

1. General pattern matching

You can pattern match on any data type — numbers, characters, lists, tuples, etc. 

lucky :: (Integral a) => a -> String 
lucky 7 = "LUCKY NUMBER SEVEN!"  
lucky x = "Sorry, you're out of luck, pal!"    
patterns will be checked from top to bottom and when it conforms to a pattern, the corresponding function body will be used

without pattern matching
addVectors :: (Num a) => (a, a) -> (a, a) -> (a, a)  addVectors a b = (fst a + fst b, snd a + snd b) 
with pattern matching
addVectors :: (Num a) => (a, a) -> (a, a) -> (a, a)  addVectors (x1, y1) (x2, y2) = (x1 + x2, y1 + y2) 
2. The underline pattern match

The _ means the same thing as it does in list comprehensions. It means that we really don't care what that part is, so we just write a _.

3. The as pattern (@)

There's also a thing called as patterns. Those are a handy way of breaking something up according to a pattern and binding it to names whilst still keeping a reference to the whole thing. You do that by putting a name and an @ in front of a pattern.

capital :: String -> String 
capital "" = "Empty string, whoops!"  
capital all@(x:xs) = "The first letter of " ++ all ++ " is " ++ [x]  

4. Guards

Guards are a way of testing whether some property of a value (or several of them) are true or false

myCompare :: (Ord a) => a -> a -> Ordering  
`myCompare` b  
    | a > b     = GT  
    | a == b    = EQ  
    | otherwise = LT  
 
5. The where keyword

bmiTell :: (RealFloat a) => a -> a -> String 
bmiTell weight height 
    | bmi <= 18.5 = "You're underweight, you emo, you!" 
    | bmi <= 25.0 = "You're supposedly normal. Pffft, I bet you're ugly!" 
    | bmi <= 30.0 = "You're fat! Lose some weight, fatty!" 
    | otherwise   = "You're a whale, congratulations!" 
    where bmi = weight / height ^ 2 
We put the keyword where after the guards (usually it's best to indent it as much as the pipes are indented) and then we define several names or functions. These names are visible across the guards and give us the advantage of not having to repeat ourselves

The names we define in the where section of a function are only visible to that function, so we don't have to worry about them polluting the namespace of other functions.

6. The 'let' binding.

Very similar to where bindings are let bindings.
  • 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 are very local, so they don't span across guards. 

ghci> [let square x = x * in (square 5, square 3, square 2)]  [(25,9,4)]  
calcBmis :: (RealFloat a) => [(a, a)] -> [a]  calcBmis xs = [bmi | (w, h) <- nbsp="" span="">xs, let bmi = w / h ^ 2]
7. Case expressions
head' :: [a] -> 
head' xs = case xs of [] -> error "No head for empty lists!" 
                      (x:_) -> x

case expression of pattern -> result 
                   pattern -> result 
                   pattern -> result 
                   ... 

b. Variables

In imperative programming, a variable is set, temporarily, to contain a value. In functional programming, a name is defined to be defined to a value.
Haskell variables are more like math variables and less like C variables. 

In Haskell, a variable is used to provide a name for an expression. Once a variable is bound to (i.e. associated with) a particular expression, it can never refer to a different expression: we can always use the name of the variable instead of writing out the expression, and get the same result either way.

 If you're used to imperative programming languages, you're likely to think of a variable as a way of identifying a memory location (or some equivalent) that can hold different values at different times. In an imperative language we can change a variable's value at any time, so that examining the memory location repeatedly can potentially give different results each time.

 The critical difference between these two notions of a variable is that in Haskell, once we've bound a variable to an expression, we know that we can always substitute it for that expression, because it will not change. In an imperative language, this notion of substitutability does not hold.

If we run the following tiny Python script, it will print the number 11
x = 10
x = 11
# value of x is now 11
print x
 
 -- file: ch02/Assign.hs
x = 10
x = 11
 
 ghci> :load Assign
[1 of 1] Compiling Main             ( Assign.hs, interpreted  

We cannot assign a value to x twice
Assign.hs:4:0:
    Multiple declarations of `Main.x'
    Declared at: Assign.hs:3:0
                 Assign.hs:4:0
Failed, modules loaded: none. 
 

No comments:

Post a Comment