As you already know if you've read my other article on currying, Haskell may appear to have multiple parameters but in reality all functions take one argument and return one result.
A function in Haskell is a first-class value; all Haskell values can be arguments to functions.
You name parameters to function by declaring them between the name of the function and the equals sign, separating the name from both the function name and the equals sign with white space.
Anonymous means "without a name".
We can construct functions without giving them a name.
Here is an example of a named function vs an anonymous function:
double :: Integer -> Integer -- named
double x = x * 2
(\x -> x * 2) :: Integer -> Integer -- anonymous
Lambda Syntax Utility
You most often use this syntax when you're passing a function in as an argument to a higher-order function and that's the only place in your program where that particular function will be used.
If you're never going to call it, then it doesn't need to be given a name.
Pattern matching is a method of matching values against patterns and binding variables to successful matches.
Patterns can include things as diverse as undefined variables, numeric literals, and list syntax.
Pattern matching matches on any and all data constructors.
We can pattern match on numbers for example:
isItOne :: Integer -> Bool
isItOne 1 = True
isItOne _ = False
The underscore represents the default pattern if nothing else explicitly defined matches.
The order of pattern matches matters. Always put your default pattern at the end. Always order from most specific to least specific.
You can check your pattern matches at compile time to see if your pattern matches every case:
Prelude> :set -Wall
Prelude> :{
Prelude| let isItOne :: Integer -> Bool
Prelude| isItOne 1 = True
Prelude| :}
<interactive>:8:5: warning: [-Wincomplete-patterns]
Pattern match(es) are non-exhaustive
In an equation for ‘isItOne’:
Patterns not matched: p where p is not one of {1}
Pattern Matching Against Data Constructors
We can unpack and expose the contents of our data using pattern matching.
newtype
is a special case of a data declaration; it is different from data
in that it only permits one constructor and only one field.
Let's use it to focus on how pattern matching can be used to expose the contents of data and specify behavior based on that data:
module Users where
newtype Username
= Username String
newtype AccountNumber
= AccountNumber Integer
newtype Password
= Password String
data User
= UnregisteredUser
| RegisteredUser Username AccountNumber Password
We can use pattern matching to accomplish two things.
User is a sum with two constructors: UnregisteredUser
and RegisteredUser
. We use pattern matching to cause the function to act differently depending on which value is passed.
With the RegisteredUser
, we see that it is a product of three newtypes
: Username
, AccountNumber
, and Password
.
RegisteredUser
is a function that constructs a User
out of three arguments: Username
, AccountNumber
, and Password
. This is why it's called a "data constructor".
Case expressions are similar to if-then-else
.
You can use case expressions with any datatype that has visible data constructors.
Consider Bool
:
data Bool = False | True
-- [1] [2] [3]
False
True
Let's write a case expression, matching on the data constructors of Bool
:
f x =
case x + 1 == 1 of
True -> "Cool"
False -> "Uncool"
Prelude> f 0
"Cool"
Higher-order functions are functions that accept functions as arguments.
When we want to express a function argument within a function type, we must use parentheses to nest it.
Remember, that functions are values, and thus, can be used as arguments.
flip :: (a -> b -> c) -> b -> a -> c -- HOF
flip f x y = f y x
Guard syntax allows us to write compact functions that allow for two or more possible outcomes depending on the truth of the condition.
Let's rewrite a function to use guards:
myAbs :: Integer -> Integer -- return the absolute value of an Integer
myAbs i = if i < 0 then (-x) else x
myAbs :: Integer -> Integer
myAbs i -- guards
| x < 0 = (-x)
| otherwise = x
Each guard has its own equals sign.
|
denotes a new guard case.
otherwise
is another name for True
, used here as a fallback case in case x < 0
is False
.
Function composition is a type of higher-order function that allows us to combine functions such that the result of applying one function gets passed to the next function as an argument.
(.)
is the function composition operator
(.) :: (b -> c) -> (a -> b) -> a -> c
-- [1] [2] [3] [4]
The basic syntax of function composition looks like this:
(f . g) x = f (g x)
This composition operator, takes two functions, named f and g. The f corresponds to (b -> c)
and the g corresponds to (a -> b)
.
An example:
negate . sum $ [1, 2, 3, 4, 5]
-- -15
Pointfree style refers to a style of composing functions without specifying their arguments.
For example:
f . g = \x -> f (g x)
f . g . h = \x -> f (g (h x))
We can rewrite the example from earlier:
Prelude> let f = negate . sum
Prelude> f [1, 2, 3, 4, 5]
-15
It is important to remember that functions in composition are applied from right to left.
Case expressions and pattern matching - https://www.haskell.org/tutorial/patterns.html
The implementation of functional programming languages - http://research.microsoft.com/en-us/um/people/simonpj/papers/slpj-book-1987/index.htm
Point free program calculation - http://www4.di.uminho.pt/~mac/Publications/phd.pdf
Haskell Book - https://haskellbook.com/