Before working through these be sure you've got import Text.Show.Functions
in your ~/.ghci
(or ghc.conf
on Windows), or enter it as the first command in your ghci
session. If you don't, when a function value is displayed you'll get that error with No instance for (Show (a0 -> a0))...
instead of getting just <function>
.
Enter this function, which we'll use in several of the following exercises.
let f a b c d e = a + b + c + d + e :: Intthen try the following, paying close attention to the pattern with
f1
, f2
, ... fN
.
:set +t f let f1 = f 10 -- imagine f1 as being this: f1 b c d e = 10 + b + c + d + e :: Int -- note that: -- (1) 'a' has been removed from the parameter list -- (2) 'a' to the right of '=' has been replaced with 10 f1 let f2 = f1 20 -- imagine let f2 c d e = 10 + 20 + c + d + e :: Int f2 let f3 = f2 30 -- imagine let f3 d e = 10 + 20 + 30 + d + e :: Int f3 let f4 = f3 40 -- imagine let f4 e = 10 + 20 + 30 + 40 + e :: Int f4 let r = f4 50 -- imagine let r = 10 + 20 + 30 + 40 + 50 :: Int r
Each of f1
, f2
, f3
, f4
are partial applications—each was producing by calling a function with fewer arguments than it required.
If you don't understand the above example, BE SURE to let me know! I'm not kidding!
As mentioned in the slides, there's nothing special about a partial application: it's just a function like any other function. There's no way to tell it resulted from providing a function less arguments than it requires.
In the following we create two functions, fpartial
, a partial application of f
; and flet
, an equivalent computation defined with let.
let fpartial = f 10 let flet b c d e = 10 + b + c + d + e :t fpartial :t flet fpartial 20 30 40 50 flet 20 30 40 50Try it, and see if you see any hint that one's a partial application and one was defined with
let
.
In the previous example we bound partial applications to fN
but we could have used it
, too. Try these:
f 10 it 20 it 30 it 40 it 50
In the examples above we create a series of partial applications by supplying one argument at a time. Let's try supplying two arguments at a time.
We're still working with the function f
defined in the first exercise.
let f1 = f 10 20 -- imagine let f1 c d e = 10 + 20 + c + d + e f1 let f2 = f1 30 40 -- imagine let f2 e = 10 + 20 + 30 + 40 + e f2 let r = f2 50 -- imagine let r = 10 + 20 + 30 + 40 + 50 r
If you don't understand the above example, BE SURE to let me know! I'm not kidding!
Let's repeat the previous exercise using it
instead of binding names to intermediate results with let
.
f 10 20 it 30 40 it 50
Let's work with a different function:
let f x y z = (x == chr y) == z
Here's a way to think about f
:
If you givef
aChar
, it'll give you back a function that wants anInt
. If you give that function anInt
, it'll give you back a function that wants aBool
. If you give that function aBool
, it will give you aBool
.
f
. Use :t Data.Char.chr
to remind yourself of the type of the chr
function.
f f 'A' 65 True f 'B' 65 False let f1 = f 'A' let f2 = f1 65 let r = f2 True r
In the first example above I show f1
, f2
, etc. as being versions of the function f
with parameters dropped and values plugged in. Follow that example and show f1
, f2
, and r
in the same way.
I like using the Data.Char
functions for simple experiments; I've got :m +Data.Char
in my ~/.ghci
, so I don't need to bother with loading that module, or using a fully qualified name, like Data.Char.chr
.
Slide 72 shows a model of partial application using boxes with inputs and outputs. Using the notation of that model, draw a diagram that corresponds to this sequence of operations:
let f x y z = (x == chr y) == z let f1 = f 'A' let f2 = f1 65
Then, draw a diagram that corresponds to this sequence of operations:
let f x y z = (x == chr y) == z let f1 = f 'B' 80
Now for a curveball! First, predict and check the type of the function f
below. Then predict and check the results of the following operations
let f a b c d = 10 f 1 2 it 3 it 4 f f f f it f
Slide 73 points out that the familiar notation log2 is in fact a partial application.
The logBase
function computes the logarithm for an arbitrary base. Check its type and then create and test functions log2 and log10 that compute base-2 and base-10 logarithms.
Write a function between first last value
that returns True
iff value
is between first
and last
, inclusive.
Example of usage:
> between 10 20 15 True > between 'X' 'Z' 'Y' True
Next, bind isLower
to a partial application of between
that will test to see if isLower
's argument is a lower-case letter. See if you can replace the ...
below with code to make the expressions that follow work.
> let isLower = ... > isLower 'm' True > isLower 'X' False
Here are between
and isLower
:
let between first last value = value >= first && value <= last let isLower = between 'a' 'z'
Your first thought with let isLower = ...
might have been, "Where's the argument of isLower
?" Instead of defining a function
let isLower letter = letter >= 'a' && letter <= 'z'we can just plug a couple of values into
between
. Compare isLower
to between
. isLower
looks like between
with 'a' plugged in for first
and 'z' plugged in for last
. This a key idea, at the heart of partial application. Let me know if you don't understand it.
Also, notice how the arguments for between
have been arranged to facilitate this partial application. If it were between value first last
we couldn't create isLower
by making a partial application of between
.
Another problem! (Yes, even the answers have problems!) Define isUpper
and isDigit
, too.
The last example on slide 30 looks like string concatenation but we'll learn that there's more to it than just that. In the meantime, use ++ to create a function wrap that behaves like this:
> wrap "(" ")" "abc" "(abc)" > let parens = wrap "(" ")" > let brackets = wrap "<" ">" > parens (brackets (parens "123")) "(<(123)>)"If you succeed, use :t to check the type of wrap, parens, and brackets.
let wrap before after text = before ++ text ++ after
Note that parens
and brackets
are partial applications, with matching parentheses and brackets, respectively, plugged-in ("wired-in").
Think up a good real-world analogy for partial application.
If you know JavaScript, create a JavaScript version of add from slide 85. The expression add(3)(4) should produce 7.
function add(x) { return function (y) { return x + y } }
Look through some old code you've written—Java or whatever—and see if you can find some cases where the ability to use a partial application might have saved some repetition. If you know C, think about fprintf.
Many UNIX shells have a command aliasing mechanism. A common bash alias is this:
alias ll="ls -l"
How are aliases like partial applications? How do they differ?