-- HaskellExamples2.hs
--
-- CompSci 141 / CSE 141 / Informatics 101 Spring 2013
-- Code Examples
--
-- This module contains a set of functions we wrote in the second Haskell
-- lecture, along with some background on features like pattern matching,
-- primitive recursion, curried functions, lambdas, operator sections,
-- and higher-order functions.
--
-- Be sure to read through the "Learn You a Haskell for Great Good" book
-- chapters listed in the course Schedule if you want more information
-- about these topics.  You may not find that you need to read those
-- chapters all the way through, but skimming through them is a good
-- way to get yourself acquainted with some of these topics as you
-- prepare to work through Project #3.

module HaskellExamples2 where


-- Here, we see an example of processing a list recursively, and also of
-- using pattern matching to do two things: (1) differentiate between
-- the case where a list is empty and the case where it is not, and
-- (2) separately name the head (first element) and tail (all but the
-- first element) of a list within the pattern.  What results is a
-- shorter, clearer function than you would have to write in a language
-- that required you to take the parameter and then check cases and
-- split it up into pieces within the function's body.
--
-- The function below, listLength, takes a list as a parameter and returns
-- the number of elements in its list.  A few things that are interesting
-- about it:
--
-- * The type signature is different than the ones we've seen previously,
--   because it is "polymorphic," meaning that it has many types instead
--   of just one.  The "a" in the signature is what's called a "type
--   variable," akin to the generic type parameters we saw in Java; what
--   sets it apart from concrete type names is that its name begins with
--   a lowercase letter.  Our type signature here reads "listLength has
--   the type of a function that takes a list containing some kind of
--   value and returning an integer."
--
-- * The pattern [] in the function's first equation matches only the
--   empty list.  So the first equation establishes that the length of
--   an empty list is zero.
--
-- * The pattern (x:xs) matches a list that is non-empty, simultaneously
--   naming the head (the first element) x and the tail (all but the first
--   element) xs.  The name "xs" can be read as the plural of "x".  Note
--   that the names themselves aren't meaningful -- we could have said
--   (n:ns) or even (boo:alex) if we'd wanted to.  Customarily, though,
--   we tend to give the tail a name that is the plural of whatever we
--   named the head.
--
-- * In our function, we use the pattern (_:xs), which means the same
--   thing as (x:xs), except that we're explicitly *not* giving the
--   head a name.  We do this because we don't need to examine the head
--   element; we just need to count it, but that doesn't require us to
--   know what it is.
--
-- This function is an example of what I'm calling "primitive recursion,"
-- where we're the ones controlling every step of the process.  An
-- alternative, which we'll see later, is to use higher-order functions
-- that encapsulate common patterns of primitive recursion for us.

listLength :: [a] -> Integer
listLength []      = 0
listLength (_:xs)  = 1 + listLength xs


-- A slightly different function, which demonstrates using a pattern
-- that names both the head and the tail, follows.

sumAll :: [Integer] -> Integer
sumAll []       = 0
sumAll (x:xs)   = x + sumAll xs



-- Tuples are another useful Haskell data type.  If lists represent
-- collections of elements whose size is undetermined until run-time,
-- tuples represent collections of values whose size is known.  We
-- typically use these to represent heterogenous, related data,
-- similar to the set of fields in an object in Java or the set of
-- members of a struct in C.
--
-- The type of a tuple specifies not only the number of values stored
-- within it, but also their types.  Tuples with different types of
-- values have different types; tuples with different *numbers* of
-- values also have different types.
--
-- This function, given a two-element tuple that is assumed to be
-- an x/y coordinate, computes the distance that the x/y coordinate
-- is from the origin in two-dimensional space.  Note, too, that the
-- "sqrt" function calculates the square root of its argument.
--
-- The pattern (x, y) matches a two-element tuple and simultaneously
-- names its first element x and its second element y.

distanceFromOrigin :: (Float, Float) -> Float
distanceFromOrigin (x, y)   = sqrt (x * x + y * y)



-- We've yet to see a function that takes more than one parameter.
-- Here is a very simple function that takes two parameters and
-- multiplies them.

multiply :: Integer -> Integer -> Integer
multiply m n = m * n

-- We could call this function by passing it two parameters, so the value
-- of "multiply 3 4" is 12.
--
-- The type signature seems a little bit strange though, especially when
-- you know that the -> operator is right-associative, which means that
-- the type signature we wrote is equivalent to this one:
--
--     multiply :: Integer -> (Integer -> Integer)
--
-- That tells us that there's more going on than initially meets the eye.
-- If we read that type signature carefully, here's what it tells us:
--
-- "multiply has the type of a function that takes an Integer and returns
--  a *function* that takes an Integer and returns an Integer."
--
-- This is technically true of all functions in Haskell that accept
-- multiple parameters.  You can supply them with fewer parameters than
-- they require (leaving out the last one or more), in which case the
-- result is a function that requires the missing parameters and computes
-- the value given the parameters you originally passed.
--
-- So "multiply 3" has a value: it's a function that takes an Integer
-- and multiplies it by 3.
--
-- This technique, in general, is called "currying."  A curried function
-- is one that can be passed fewer parameters than it requires, in which
-- case you get a new function that requires only the ones that were
-- missing.
--
-- It might seem strange that a feature like this exists, but it's surprisingly
-- useful.  To see why, though, we need a little more background.



-- Instead of using primitive recursion, we can use what are called "higher-
-- order functions".  Higher-order functions are functions that take other
-- functions as a parameter or that return functions as their result.
-- Their purpose is to encapsulate common patterns of code you might
-- otherwise have to write by hand; you pass a function that fills in the
-- pattern's gaps and you're done.
--
-- A few examples of higher-order functions built into Haskell's standard
-- library are listed below, along with their type signatures (which is a
-- handy way to understand how they work):
--
--     map :: (a -> b) -> [a] -> [b]
--     Given a function that takes values of some type a and returns values of
--     another type b, along with a list of values of type a,
--     returns a list of the results you get when you apply the function to
--     every element in the list.
--     Example: "map square [1..5]" returns "[1,4,9,16,25]"
--
--     filter :: (a -> Bool) -> [a] -> [a]
--     Given a function that takes a value of some type a and returns a boolean
--     result, along with a list of values of type a, returns a list of the
--     values in the given list for which the function returns True.
--     Example: "filter isPositive [-1,1,-2,2,-3,3]" returns "1,2,3"
--
--     zip :: [a] -> [b] -> [(a, b)]
--     Given two lists of elements, returns a list of tuples that contain the
--     positionally-matched elements of the two lists (i.e., the first tuple
--     is the first element of the first list combined with the first element
--     of the second list, and so on).  Stops when one of the lists runs out
--     of elements.
--     Example: "zip [1..3] [11..15]" returns "[(1,11),(2,12),(3,13)]"
--
--     zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
--     Given a function that takes a value of type a and a value of type b
--     and transforms it to a value of type c, along with lists of a's and
--     b's, returns the result of transforming positionally-matched elements
--     of the two lists using the given function.  This is the general case
--     of zip.
--     Example: "zipWith multiply [1,2,3] [4,5,6,7]" returns "[4,10,18]"
--
--     foldr :: (a -> b -> b) -> b -> [a] -> b
--     This one is the trickiest of these to understand.  It "folds" a list,
--     by applying a function "in between" adjacent pairs of elements.  The
--     "r" in the name means "right", which means that the function is
--     applied from right-to-left.  An additional element is used at the
--     opposite end from where the folding starts, as a way to establish a
--     base case.
--
--     So if the function was our "multiply" function from earlier, our
--     list was [1..4], and our base case element was 1, the effect would
--     be this:
--
--         multiply 1 (multiply 1 (multiply 2 (multiply 3 4)))
--
--     and the result would be 24.


-- So what does all of this have to do with curried functions?  Because we
-- can pass partially-applied functions (those that have had some of their
-- parameters specified, but others left out) as parameters to higher-order
-- functions.  Suppose you have a list of integers and you want a list
-- where all of those integers has been doubled.  This function will do it.

doubleAll :: [Integer] -> [Integer]
doubleAll xs  = map (multiply 2) xs

-- Consider why this works.  "map" takes a list and a function that transforms
-- the elements of that list one at a time.  The list contains integers, so
-- the function needs to be one that takes a single integer and does something
-- to it.  What is "multiply 2"?  A function that takes a single integer (the
-- one we didn't pass to it originally) and multiplies it by 2.


-- Note, too, that operators like * or == are also functions, and they're also
-- curried.  Haskell provides a shorthand syntax called "operator sections"
-- that allows you to leave out one operand or the other, in which case you get
-- a partially-applied function that expects the other.

doubleAll2 :: [Integer] -> [Integer]
doubleAll2 xs  = map (2*) xs

-- (2*) is a partially-applied multiplication operation, which has had its first
-- operand specified by needs its second.  Similarly, we could map any of these
-- operator sections over a list; think about what the result would be:
--
--     (*2)   (3/)   (/3)   (>0)   (0>)
--
-- Note, too, that the parentheses are required here to allow the code to parse
-- correctly.  "map 2 * xs" means something very different (and, by the way,
-- something illegal!) than "map (2*) xs" does.



-- Finally, we can also make our doubleAll function above polymorphic, to extend
-- its usefulness.  The multiplication operator is capable of multiplying any
-- kind of number, not just an Integer, so we could specify that our doubleAll
-- function is capable of doubling any kind of number.  Notice that the
-- function's equation doesn't change at all, but its type signature does.

doubleAll3 :: Num a => [a] -> [a]
doubleAll3 xs  = map (2*) xs

-- "Num a =>" represents something called a "type constraint."  It's akin to
-- the bounded type parameters we specified in Java (e.g., "K extends Comparable<K>")
-- when we wanted to constrain the set of types allowable as keys in our binary
-- search tree implementation in Project #2.  The constraints we write in Java are
-- centered around the concept of inheritance (classes that extend some class or
-- implement some interface).  Haskell's constraints are similar, though they're
-- centered around what are called "typeclasses," which indicate sets of
-- functions that are available to certain types.  The typeclass Num specifies a
-- type that is numeric, which means that it supports certain operations like
-- arithmetic operators, a function that returns an absolute value, and a handful
-- of others.
--
-- So, in general, we read doubleAll3's type signature like this: "doubleAll3 has
-- the type of a function that, for any numeric type a, takes a list of a's and
-- returns a list of a's".


-- Other useful typeclasses that are built into Haskell include:
--
-- * "Eq", which specifies a type for which == and /= operators exist.
-- * "Ord", which specifies a type whose values have a natural ordering, which
--   means that not only are they Eq, but they also have <, >, <=, and >=
--   operators, along with a small handful of additional functions.
