More on Functions

Mini-Lecture 9

Ben Baumer

Smith College

2024-10-08

GitHub review

Remote setup

Divergent histories

Slack review

Reading code

I am very confused by the coding in the body component of each function. There are some lines of code that I am able to understand, however, there are others, such as long if/else statements that are very hard to follow.

  • Yes.
  • Nearly all of that code is written in base R
  • That code is (supposedly) optimized for performance, not readability

Generic functions

Why is the body of a generic function like print() is differnt than the method it calls?

  • The body of a generic function is typically just UseMethod()
  • The generic itself is not a method
  • See also the default method

More about functions

The dots (…)

  • Passing arguments to other functions

  • Accepting arguments from S3 methods

Passing arguments

ggplot2::scale_alpha_continuous
function (name = waiver(), ..., range = c(0.1, 1)) 
{
    continuous_scale("alpha", name = name, palette = pal_rescale(range), 
        ...)
}
<bytecode: 0x55af21f33f90>
<environment: namespace:ggplot2>
args(ggplot2::continuous_scale)
function (aesthetics, scale_name = deprecated(), palette, name = waiver(), 
    breaks = waiver(), minor_breaks = waiver(), n.breaks = NULL, 
    labels = waiver(), limits = NULL, rescaler = rescale, oob = censor, 
    expand = waiver(), na.value = NA_real_, transform = "identity", 
    trans = deprecated(), guide = "legend", position = "left", 
    call = caller_call(), super = ScaleContinuous) 
NULL

You can capture the dots

catch_the_dots <- function(...) {
  list(...)
}

catch_the_dots(whatever = "hi", "bye")
$whatever
[1] "hi"

[[2]]
[1] "bye"

You can use arguments that a generic function doesn’t accept!

args(plot)
function (x, y, ...) 
NULL
plot(rnorm(20), rnorm(20), main = "Sweet plot")

Check arguments

body(plot)
UseMethod("plot")
args(plot.default)
function (x, y = NULL, type = "p", xlim = NULL, ylim = NULL, 
    log = "", main = NULL, sub = NULL, xlab = NULL, ylab = NULL, 
    ann = par("ann"), axes = TRUE, frame.plot = axes, panel.first = NULL, 
    panel.last = NULL, asp = NA, xgap.axis = NA, ygap.axis = NA, 
    ...) 
NULL

Return values

  • Implicit returns

  • Watch out for types!

  • Explicit return with return()

Type problems

what_do_i_return <- function(x) {
  if (x %% 10 == 0) {
    return(as.character(x))
  } else {
    return(x)
  }
  message("This will never get run")
}

what_do_i_return(100)
[1] "100"
what_do_i_return(53)
[1] 53

Function is vectorized, but not appropriately!

what_do_i_return(c(100, 53))
Error in if (x%%10 == 0) {: the condition has length > 1

Coercion

library(tidyverse)
map(c(100, 53), what_do_i_return)
[[1]]
[1] "100"

[[2]]
[1] 53
map_int(c(100, 53), what_do_i_return)
Error in `map_int()`:
ℹ In index: 1.
Caused by error:
! Can't coerce from a string to an integer.
map_chr(c(100, 53), what_do_i_return)
[1] "100"       "53.000000"

Invisibility

  • Force visibility with ()
cant_see_me <- function(x) {
  invisible(x)
}

cant_see_me("now")
y <- cant_see_me("later")
y
[1] "later"
(cant_see_me("now"))
[1] "now"

Function forms

  • Prefix: f(x, y)

  • Infix: x %f% y

  • Replacement: f(x) <- y

  • Special: if

  • Everything can be written in prefix

Now

Work on