Programming for Applications

Chapter 5: An Overview of the R Language

Yu-You Liou (NTU)

Shih Chien University

2026-08-03

How R Thinks

The Grammar Lesson

Learning a computer language is much like learning a spoken one (only simpler). A tourist can survive on memorized phrases; Chapter 3 gave you those phrases. But to speak fluently you need grammar — verb conjugation, sentence structure. This chapter is R’s grammar lesson, built on four ideas:

  • programs are built from expressions;
  • expressions operate on objects;
  • names are symbols bound to objects;
  • work is done by functions — and functions are objects too.

Master these and the detailed syntax of Chapter 6 will feel inevitable rather than arbitrary.

Expressions

R code is a series of expressions: assignments, conditionals, arithmetic, and more — each evaluated to produce a value:

x <- 1
if (1 > 2) "yes" else "no"
[1] "no"
127 %% 10
[1] 7

Expressions are composed of objects and functions, and may be separated by newlines or semicolons:

"this expression will be printed"; 7 + 13; exp(0+1i*pi)
[1] "this expression will be printed"
[1] 20
[1] -1+1.224647e-16i

Objects

All R code manipulates objects — “things” represented by the computer. Numeric vectors, character vectors, lists, and functions are all objects:

c(1, 2, 3, 4, 5)                       # a numeric vector (five elements)
[1] 1 2 3 4 5
"This is an object too"               # a character vector (one element)
[1] "This is an object too"
list(c(1,2,3,4,5), "This is an object too", "this whole thing is a list")
[[1]]
[1] 1 2 3 4 5

[[2]]
[1] "This is an object too"

[[3]]
[1] "this whole thing is a list"
function(x, y) { x + y }               # a function is an object as well
function (x, y) 
{
    x + y
}

Symbols

Formally, variable names in R are symbols. Assigning a value to a name binds the symbol to the object in the current environment — and, a touch tautologically, an environment is the set of symbols defined in a given context:

x <- 1     # binds symbol "x" to object 1 in the current environment

Chapter 8 treats symbols and environments in full; for now, just remember that a name and the thing it names are different objects.

Functions

All Work Is Done by Functions

A function takes input objects (its arguments) and returns an output object. Every statement — setting variables, doing arithmetic, looping — can be written as a function call. The book’s demonstration starts with a humble vector:

animals <- c("cow", "chicken", "pig", "tuba")
animals[4] <- "duck"     # the normal way to fix element 4
animals
[1] "cow"     "chicken" "pig"     "duck"   

That bracket assignment is parsed into a call to the function [<- (the full expansion also rebinds the result: animals <- `[<-`(animals, 4, "duck")). Calling it directly:

animals <- c("cow", "chicken", "pig", "tuba")
`[<-`(animals, 4, "duck")
[1] "cow"     "chicken" "pig"     "duck"   

(Two subtle differences: called directly the result is auto-printed — and without the rebinding step, animals itself is left unchanged.)

Why Care That Everything Is a Function Call?

In practice you would never write `[<-`(animals, 4, "duck") — bracket notation is far easier to read. But knowing the function is there means you can inspect its code, read its help page, or write methods with the same name for your own classes (Chapter 10). More pretty-vs-functional pairs:

apples <- 3            # pretty assignment
`<-`(oranges, 4)       # functional form of assignment
apples + oranges       # pretty arithmetic
[1] 7
`+`(apples, oranges)   # functional form
[1] 7
if (apples > oranges) "apples are better" else "oranges are better"
[1] "oranges are better"
`if`(apples > oranges, "apples are better", "oranges are better")
[1] "oranges are better"
x <- c("apple","orange","banana","pear")
x[2]                   # pretty vector reference
[1] "orange"
`[`(x, 2)              # functional form
[1] "orange"

Objects Are Copied in Assignment

In assignment statements, most objects are immutable: R copies the object, not merely a reference to it. (Immutability is a virtue — it prevents whole categories of errors, e.g., in multithreaded programs.)

u <- list(1)
v <- u
u[[1]] <- "hat"
u
[[1]]
[1] "hat"
v          # v still holds the original
[[1]]
[1] 1

This holds for vectors, lists, and most other primitive objects.

Copying Applies to Function Calls Too

Arguments are copied as well. This function sets the ith element of x to 4 — and accomplishes nothing visible:

f <- function(x, i) { x[i] = 4 }
w <- c(10, 11, 12, 13)
f(w, 1)
w          # untouched: w was copied into x
[1] 10 11 12 13

Technically, the interpreter copies the object bound to w and binds the symbol x to the copy; the modification happens only inside the function’s context. (Mutable objects and pass-by-reference become possible with environments — Chapter 8.)

Note

Semantics vs. performance. R behaves as if every assignment copies, but often modifies objects in place — e.g., v <- 1:100; v[50] <- 27 alters the vector without copying it. Identical meaning, much better speed. Details live in the R Internals guide.

Everything in R Is an Object

Not just data: functions, symbols, even expressions are objects. A function name is really a symbol pointing at a function object (a relationship itself stored in an environment object). The same symbol can hold a number one moment and a function the next:

x <- 1
x
[1] 1
x(2)                     # x is not (yet) a function
Error in `x()`:
! 沒有這個函式 "x"
x <- function(i) i^2
x
function (i) 
i^2
x(2)
[1] 4

You can even write R code that constructs new functions — in principle, a function that rewrites its own definition.

Special Values

NA: Missing Values

NA (“not available”) represents missing values — common in loaded text data, and substituted for database NULLs. Expanding a structure beyond its defined values fills the new cells with NA:

v <- c(1, 2, 3)
length(v) <- 4
v
[1]  1  2  3 NA

Inf, -Inf, and NaN

Numbers too big to represent become infinity, signed accordingly — also the result of dividing by zero:

2 ^ 1024
[1] Inf
- 2 ^ 1024
[1] -Inf
1 / 0
[1] Inf

Computations that make no sense yield NaN (“not a number”):

Inf - Inf
[1] NaN
0 / 0
[1] NaN

NULL

The symbol NULL always points to one special object: the null object.

  • Often used as a function argument meaning “no value supplied”; some functions also return it.
  • NULL is not the same as NA, Inf, -Inf, or NaN — it is not a value at all, but the absence of one.
is.null(NULL)
[1] TRUE

Coercion

Automatic Type Conversion

Call a function with the “wrong” type and R tries to coerce the value so the call still works. Two mechanisms operate automatically:

  • formal objects: generic functions lacking an exact method look for a coercion method to a type that has one (creating such methods: Chapter 10);
  • built-in types: R converts from more specific to more general types on its own:
x <- c(1, 2, 3, 4, 5)
typeof(x); class(x)
[1] "double"
[1] "numeric"
x[2] <- "hat"          # one character value forces the whole vector over
x
[1] "1"   "hat" "3"   "4"   "5"  
typeof(x); class(x)
[1] "character"
[1] "character"

The Coercion Rules

  • Logical values become numbers: TRUE → 1, FALSE → 0.
  • Values convert to the simplest type that can represent all the information.
  • The ordering is roughly: logical < integer < numeric < complex < character < list.
  • raw objects are not converted to other types.
  • Object attributes are dropped during coercion.
  • To inhibit coercion when passing arguments, wrap the value in I() (the AsIs function) — see ?AsIs.

Note

Why so permissive? Strongly typed languages (Java) raise exceptions instead of converting. John Chambers, S’s designer, recalled that early S “made as many cases work as possible,” but the later, formal conclusion was that converting richer types to simpler ones in all situations breeds confusing, untrustworthy results. In daily practice — numeric vectors of one type — coercion rarely bites.

The R Interpreter

Parse, Substitute, Evaluate

R is an interpreted language: the interpreter executes your expressions directly, with no compile step (unlike C, C++, Java; like LISP, Perl, JavaScript). For each expression, the interpreter:

  1. parses it, translating syntactic sugar into functional form;
  2. substitutes objects for symbols where appropriate;
  3. evaluates it, returning an object — recursively for complex expressions.

Example: typing x <- 1 becomes the call `<-`(x, 1), which binds 1 to x in the current environment and returns 1.

Walking Through a Conditional

With x bound to 1, consider:

x <- 1
if (x > 1) "orange" else "apple"
[1] "apple"

The interpreter parses the line as an if-then-else structure, evaluates the condition (x > 1) first — false — and therefore never evaluates "orange"; it evaluates the expression after else and returns "apple". In special cases like this, not all arguments are evaluated — which is why if cannot be an ordinary function. And whatever an expression returns at the console gets handed to print.

.Internal and .Primitive

Some functionality lives inside the R system itself:

  • Many functions call internal system code via .Internal (or its sibling .External) — e.g., the graphics workhorse, which nowadays goes through .External.graphics:
plot.xy
function (xy, type, pch = par("pch"), lty = par("lty"), col = par("col"), 
    bg = NA, cex = 1, lwd = par("lwd"), ...) 
{
    if (is.null(type)) 
        type <- "p"
    type <- as.character(type)
    if (length(type) != 1L || !nzchar(type) || is.na(type)) 
        stop(gettextf("invalid plot type"))
    if (nchar(type) > 1L) 
        warning(gettextf("plot type '%s' will be truncated to first character", 
            type))
    t <- substr(type, 1L, 1L)
    if (!isTRUE(t %in% c("l", "o", "b", "c", "s", "S", "h", "p", 
        "n"))) 
        stop(gettextf("invalid plot type '%s'", t))
    invisible(.External.graphics(C_plotXY, xy, t, pch, lty, col, 
        bg, cex, lwd, ...))
}
<bytecode: 0xa43208ba0>
<environment: namespace:graphics>
  • Where even .Internal’s calling overhead is too high, functions are implemented entirely internally via .Primitive — reserved for a few performance-critical basics:
`<-`
.Primitive("<-")

Seeing How R Works

quote: Parsing Without Evaluating

Because R expressions are R objects, you can parse — or partially evaluate — expressions and inspect the result. The quote() function parses its argument but does not evaluate it, returning a language object:

typeof(quote(if (x > 1) "orange" else "apple"))
[1] "language"
quote(if (x > 1) "orange" else "apple")   # the print method isn't very informative...
if (x > 1) "orange" else "apple"

The Parse Tree

…but convert the language object to a list and the structure appears — this is the expression’s parse tree:

as(quote(if (x > 1) "orange" else "apple"), "list")
[[1]]
`if`

[[2]]
x > 1

[[3]]
[1] "orange"

[[4]]
[1] "apple"

Apply typeof to every element to label the tree (here using lapply — and thanks to coercion, the as is optional):

lapply(as(quote(if (x > 1) "orange" else "apple"), "list"), typeof)
[[1]]
[1] "symbol"

[[2]]
[1] "language"

[[3]]
[1] "character"

[[4]]
[1] "character"

Observations: the else keyword vanishes in parsing; the first element is a symbol referring to the if function — so even special syntax becomes function name first, arguments after, exactly like any call. For constants, the list has a single item: as.list(quote(1)).

Sugar Exposed, and deparse

The bracket lookup x[2] and its functional twin parse to the identical tree:

as.list(quote(x[2]))
[[1]]
`[`

[[2]]
x

[[3]]
[1] 2
as.list(quote(`[`(x, 2)))
[[1]]
`[`

[[2]]
x

[[3]]
[1] 2

The translation is irreversible — both collapse to one tree — but deparse turns a parse tree back into properly formatted R code, choosing the pretty syntax:

deparse(quote(x[2]))
[1] "x[2]"
deparse(quote(`[`(x, 2)))
[1] "x[2]"

Tip

Tools to carry through the book: quote, substitute, typeof, class, and methods let you watch the interpreter parse and dispatch. When R surprises you, these are your microscope.