Class summary:   Higher-Order Functions
1 Functions as Arguments
1.1 Filter
1.2 map
2 Anonymous Functions (Optional)
3 What to Take from This Segment

Class summary: Higher-Order Functions

Copyright (c) 2017 Kathi Fisler

1 Functions as Arguments

We looked at two programs, get-long-wordsand get-square-shapes to see that sometimes entire programs are similar except for some local computation. We can create helper functions that take the "local computation" as an input by passing functions as inputs, just as we have been passing numbers, lists, and tables.

In class, we worked this out manually by creating keep, a function that captured the common parts of our two initial functions.

  fun keep(lst :: List<Any>, to-keep :: (Any -> Boolean)) -> List<Any>:

    doc: "return items from list on which to-keep function returns true"

    cases (List) lst:

      | empty => empty

      | link(f, r) =>

        if to-keep(f):

          link(f, keep(r, to-keep))

        else:

          keep(r, to-keep)

        end

    end

  end

We then recreated get-square-shapes by writing a separate function that checks whether an image is square in its dimensions and passing it to keep:

  fun is-square(ashape :: Image) -> Boolean:

    doc: "determine whether a shape has the same width and height"

    image-width(ashape) == image-height(ashape)

  end

  

  fun get-square-shapes2(shapes-list :: List<Image>) -> List<Image>:

    doc: "produce list of square shapes from given list"

    keep(shapes-list, is-square)

  end

We also introduced the idea of local function definitions when converting get-long-words to use keep:

  fun get-long-words2(word-list :: List<String>, cutoff :: Number) -> List<String>:

  

    # a function that returns bool indicating whether to keep element from list

    fun is-long(word :: String) -> Boolean:

      string-length(word) > cutoff

    end

  

    # call keep, giving it the list and the function

    keep(word-list, is-long)

  end

Could we move is-long outside of get-long-words2? What happens if we try? Do you see the issue?

1.1 Filter

Finally, we noted that keep is such a useful concept that it is already built-in. Pyret (and many other languages) call it filter. The arguments are swapped compared to our keep function, but the idea is the same:

  fun get-square-shapes3(shapes-list :: List<Image>) -> List<Image>:

    doc: "produce list of square shapes from given list"

    filter(is-square, shapes-list)

  end

1.2 map

The other two functions from our starter file (which we did not use in class) set up a similar exercise for a function called map, which takes a function and a list and produces a list of the results of running the function on each element of the list. Example:

  fun tip-left(shape :: Image) -> Image:

    rotate(25, shape)

  end

  

  # produce list of images1 shapes rotated left

  map(tip-left, images1)

2 Anonymous Functions (Optional)

The nested function definition in get-long-words2 feels a bit clunky – we are introducing a function just to pass it as an argument, but we don’t plan to use the function anywhere else (since it’s local). Much of the overhead comes from naming the function, which isn’t all that relevant since the name doesn’t really get used (after we do substitution into keep or filter).

Languages that provide map and filter tend to also provide a notion of anonymous functions, a lightweight notation for creating functions for one-time use. Here’s an example:

  fun get-long-words4(word-list :: List, cutoff :: Number) -> List:

    filter(lam(word :: String): string-length(word) > cutoff end,

      word-list)

  end

lam (short for lambda, the traditional name for a construct that creates functions) says "make a function with parameter word" (and the body that follows). With lam, writing functions like get-long-words feels much lighter, embracing the benefits of functions like filter that accept functions as arguments.

3 What to Take from This Segment

I’d expect that you took different things from that discussion depending on your comfort level with programming:

  • If you are still getting comfortable with functions, simply take away two ideas: (1) helper functions can capture large chunks of code, and (2) it is okay to pass functions as arguments.

  • If you’re comfortable with functions, you should have gotten a sense of how to create your own functions that take functions as inputs. Ideally, you should also be able to trace a function like that and see how the pieces fit together to reproduce the original code.

The essential take-away from that segment, however, is that Pyret (and many other languages) provide functions (like map and filter) that offer standard operations on lists, taking functions as arguments to customize them.

Note that map and filter have analogues in tables: filter is like sieve and map resembles transform.

It will be up to you whether you choose to use filter and map in subsequent assignments. Those heading into Data Science should practice with these operations, as they tend to leave your code easier to read. For others, if you aren’t ready to work with these functions yet, that’s fine.