Exercises for partial applications

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>.

  1. As mentioned on slide 77, one way to think of partial application is that as each argument is provided, a parameter is dropped and the arguments value is "wired" into the expression for the function, producing a new function with one less argument.

    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 :: Int
    
    then 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!

  2. 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 50
    
    Try it, and see if you see any hint that one's a partial application and one was defined with let.
  3. 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
    
  4. 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!

  5. 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
    
  6. 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 give f a Char, it'll give you back a function that wants an Int. If you give that function an Int, it'll give you back a function that wants a Bool. If you give that function a Bool, it will give you a Bool.

    1. See if you can predict the type of f. Use :t Data.Char.chr to remind yourself of the type of the chr function.
    2. Try this series of operations:
      f
      f 'A' 65 True
      f 'B' 65 False
      let f1 = f 'A'
      let f2 = f1 65
      let r = f2 True
      r
      
    3. 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.

  7. 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
    
  8. 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
    
  9. 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.

  10. 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.

  11. 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").

  12. Think up a good real-world analogy for partial application.

  13. 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 }
        }
    
  14. 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.

  15. 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?