Functions¶
Intro Notes¶
Assume import Prelude
unless otherwise noted.
!!! tip “REPL or module‽”
If you see `>` as the first character in a line, it means we are in the REPL, otherwise, assume it is in a module.
!!! danger “Bad function names‽”
Some of these functions have non-meaningful names on purpose. The idea is that we know what a function does by careful scrutiny of the signature and implementation as a way to force ourselves to read and understand each bit.
If we say `add1 = ...`, we immediately know this functions adds 1 to its argument. If we name it `f` or `g`, we have to read the signature and implementation carefully to understand its ideas and what it does.
That is a terrible idea for production code, but a very good approach to study, practice, ponder about stuff, and *learn*.
Lambda Expression¶
Applying a lambda expression right then and there:
> (\n -> (mod n 2)) 6
0
> (\n -> (n `mod` 2)) 5
0
Note that functions are not printable; they do not have an instance of the type class Show
:
(\n -> n) # (1)
It produces an error that we cannot “print” something that does not implement Show
.
> (\n -> n)
Error found:
in module $PSCI
at <internal>:0:0 - 0:0 (line 0, column 0 - line 0, column 0)
No type class instance was found for
Data.Show.Show (t2 -> t2)
The instance head contains unknown type variables. Consider adding a type annotation.
while solving type class constraint
PSCI.Support.Eval (t2 -> t2)
while applying a function eval
of type Eval t1 => t1 -> Effect Unit
to argument it
while checking that expression eval it
has type Effect t0
in value declaration $main
where t0 is an unknown type
t1 is an unknown type
t2 is an unknown type
See https://github.com/purescript/documentation/blob/master/errors/NoInstanceFound.md for more information,
or to contribute content related to this error.
Assigning a lambda expression to a variable:
> f = (\n -> mod n 2)
> f 3
1
module Data.Examples where
import Prelude
isEven :: Int -> Boolean
isEven n = mod n 2 == 0
f1 :: Int -> Boolean
f1 = eq 0 <<< (_ `mod` 2)
Verifying if a number is even:
## Aliasing functions to infix symbol
> f1 n = eq 0 (mod n 2)
> f2 n = eq (mod n 2) 0
> f1 4
true
> f2 4
true
In f1
we eq
compare 0
with the application (mod n 2)
. In f2
we eq
compare eq (mod n 2)
with 0
.
Aliasing functions to infix symbol¶
Making an alias to mod
:
infixr 4 mod as %
f2 :: Int -> Boolean
f2 = eq 0 <<< (_ % 2)
f3 :: Int -> Boolean
f3 = (_ % 2) >>> eq 0
Currying and Partial Application¶
Let’s define a function that “takes two arguments”:
f :: Int -> Int -> Int
f x y = (+) x y
The function f
is curried by default. It is the default PureScript (and Haskell) behavior that all functions are curried by default (unless you make some tuple magic to de-curry a function).
The Quintessential “add 1” Example¶
In fact, f
does NOT take two arguments. It takes one argument, and returns a function that takes the other argument, which then returns the final, sum result.
The add 1 quintessential partial application example:
> :type f
Int -> Int -> Int
> g = f 1
> :type g
Int -> Int
> g 5
6
Partially apply f
, that is, pass one argument. It returns a function with that argument already applied. g
now is the partially applied f
.
As we see, increment
has the value 1 partially (or pre) applied, so, when we later apply increment 5
, the body of the function (+) x y
becomes (+) 1 5
and therefore the result 6.
We can apply all arguments at once, but that just seems like “all at once”:
> f 1 2
3
But this is what is really happening (more or less 😅)
> (f 1) 2
3
> f 1 $ 2
3
!!! tip “Currying and Partial Application”
When we define a function, we say it is a curried function if it has this property of not requiring all arguments at once upon application. PureScript and Haskell functions are curried by default. No especial syntax or anything else is needed to get curried functions.
So, **currying** happens (automatically) when we define functions.
After a function exists, we can *apply the function to arguments*. If we apply less than the total number of arguments the function requires to be fully applied, we say we *partially applied the function*.
Therefore, **partial application** happens when applying (invoking, calling) the function (if less than the total number of argument a function requires to be fully applied are provided).
A partially applied function returns a function which some of the parameters applied, still awaiting for the remaining parameters to fully realize the function application, which then produces the final value or result.
Also note that the returned function from a partial application is itself curried.
Example with replace¶
With Object Oriented languages with create specializations from generalizations mostly through the use of inheritance and interfaces. In functional languages, we do this mostly through composition and partial application.
Consider the function replace
from the Data.String
module:
> import Data.String
> :type replace
Pattern -> Replacement -> String -> String
> replace (Pattern " ") (Replacement "-") "Tomb Raider I 1996"
"Tomb-Raider I 1996"
replace
replaces any Pattern
with some Replacement
. We could make it more specialized by partially applying its Pattern
argument.
> replaceSpaces = replace (Pattern " ")
> :type replaceSpaces
Replacement -> String -> String
> replaceSpaces (Replacement "-") "Tomb Raider I 1996"
"Tomb-Raider I 1996"
Now, the function replaceSpaces
is a specialized version of the more generic replace
, in which it always replaces spaces with some Replacement
.
We could further specialize replace
by partially applying the first two arguments. In this case, the Pattern
and the Replacement
specialize the function, and the remaining argument is the String
to which the substitution will be performed on:
> replaceSpacesWithHyphen = replace (Pattern " ") (Replacement "-")
> :type replaceSpacesWithHyphen
String -> String
> replaceSpacesWithHyphen "Tomb Raider I 1996"
"Tomb-Raider I 1996"
Since replaceSpaces
exist, we could specialize from that instead of from the original replace
:
> replaceSpaces = replace (Pattern " ")
> replaceSpacesWithHyphen = replaceSpaces (Replacement "-")
> replaceSpacesWithHyphen "Tomb Raider I 1996"
"Tomb-Raider I 1996"
!!! info “Examples in JavaScript”
I have some examples of creating specialized functions from generic functions through the use of currying and partial application in this [Code Sandbox project](https://codesandbox.io/s/webinar-functional-programming-in-javascript-ts0jt?file=/src/replace1.js). It is talk I sometimes give to introduce or motivate coworkers about functional programming.
Here’s one example using replaceAll
with proper type signatures:
import Data.String.Pattern (Pattern(..), Replacement(..))
import Data.String.Common (replaceAll)
replaceSpaces :: Replacement -> String -> String
replaceSpaces r s = replaceAll (Pattern " ") r s
replaceSpacesWithHyphens :: String -> String
replaceSpacesWithHyphens s = replaceSpaces (Replacement "-") s