We’ve now seen all sorts of things that exist in the Wolfram Language: lists, graphics, pure functions and much more. And now we’re ready to discuss a very fundamental fact about the Wolfram Language: that each of these things—and in fact everything the language deals with—is ultimately constructed in the same basic kind of way. Everything is what’s called a symbolic expression.

Symbolic expressions are a very general way to represent structure, potentially with meaning associated with that structure. f[x,y] is a simple example of a symbolic expression. On its own, this symbolic expression doesn’t have any particular meaning attached, and if you type it into the Wolfram Language, it’ll just come back unchanged.

f[x,y]

{x,y,z} is another symbolic expression. Internally, it’s List[x,y,z], but it’s displayed as {x,y,z}.

List[x,y,z]

List[List[a,b],List[c,d]]

FullForm shows you the internal form of any symbolic expression.

FullForm[{{a,b},{c,d}}]

Graphics[Circle[{0,0}]] is another symbolic expression, that just happens to display as a picture of a circle. FullForm shows its internal structure.

Graphics[Circle[{0,0}]]

FullForm[]

Symbolic expressions often don’t just display in special ways, they actually evaluate to give results.

Plus[2,2]

{Plus[3,3],Times[3,3],Power[3,3]}

{Plus[3,3],Times[3,3],Power[3,3]}//FullForm

Blur[,5]

Blur[Graphics[Circle[{0,0}]],5]

What are the ultimate building blocks of symbolic expressions? They’re called atoms (after the building blocks of physical materials). The main kinds of atoms are numbers, strings and symbols.

Things like x, y, f, Plus, Graphics and Table are all symbols. Every symbol has a unique name. Sometimes it’ll also have a meaning attached. Sometimes it’ll be associated with evaluation. Sometimes it’ll just be part of defining a structure that other functions can use. But it doesn’t have to have any of those things; it just has to have a name.

A crucial defining feature of the Wolfram Language is that it can handle symbols purely as symbols—“symbolically”—without them having to evaluate, say to numbers.

x

x+x+x+2y+y+x

Given symbols like x, y and f, one can build up an infinite number of expressions from them. There’s f[x], and f[y], and f[x,y]. Then there’s f[f[x]] or f[x,f[x,y]], or, for that matter, x[x][y,f[x]] or whatever.

In general, each expression corresponds to a tree, whose ultimate “leaves” are atoms. You can display an expression as a tree using TreeForm.

TreeForm[{f[x,f[x,y]],{x,y,f[1]}}]

TreeForm[Graphics[{Circle[{0,0}],Hue[0.5],Disk[{1,1}]}]]

Because expressions ultimately have a very uniform structure, operations in the Wolfram Language operate in a very uniform way on them.

For example, any expression has parts, just like a list, and you can extract them using [[...]].

List[x,y,z][[2]]

f[x,y,z][[2]]

Graphics[Circle[{0,0}]][[1]]

Graphics[Circle[{0,0}]][[1,1]]

[[1,1]]

In f[x,y], f is called the head of the expression. x and y are called arguments. The function Head extracts the head of an expression.

Head[{x,y,z}]

Every part of an expression has a head, even its atoms.

Head[1234]

Head[12.45]

Head["hello"]

Head[x]

In patterns, you can ask to match expressions with particular heads. _Integer represents any integer, _String any string and so on.

Cases[{x,y,3,4,z,6,7},_Integer]

Cases[{99,x,y,z,101,102},n_Integer->{n,n}]

In using the Wolfram Language, most of the heads you’ll see are symbols. But there are important cases where there are more complicated heads. One such case is pure functions—where when you apply a pure function, the pure function appears as the head.

FullForm[#^2&]

Function[Power[Slot[1],2]] [1000]

As you become more sophisticated in Wolfram Language programming, you’ll encounter more and more examples of complicated heads. In fact, many functions that we’ve already discussed have operator forms where they appear as heads—and using them in this way leads to very powerful and elegant styles of programming.

Select[#>4&][{1,2.2,3,4.5,5,6,7.5,8}]

Cases[_Integer]@Select[#>4&]@{1,2.2,3,4.5,5,6,7.5,8}

All the basic structural operations that we have seen for lists work exactly the same for arbitrary expressions.

Length[f[x,y,z]]

f/@g[x,y,z]

Since there are lots of functions that generate lists, it’s often convenient to build up structures as lists even if eventually one needs to replace the lists with other functions.

f@@{x,y,z}

Plus@@{1,1,1,1}

#1->#2&@@{x,y}

Rule@@{x,y}

A surprisingly common situation is to have a list of lists, and to want to replace the inner lists with some function. It’s possible to do this with @@ and /@. But @@@ provides a convenient direct way to do it.

f@@@{{1,2,3},{4,5,6}}

Rule@@@{{1,10},{2,20},{3,30}}

Here’s an example of how @@@ can help construct a graph from a list of pairs.

Partition[Characters["antidisestablishmentarianism"],2,1]

Rule@@@Partition[Characters["antidisestablishmentarianism"],2,1]

Graph[Rule@@@Partition[Characters["antidisestablishmentarianism"],2,1],VertexLabels->All]

Find the head of the output from ListPlot. »

Use @@ to compute the result of multiplying together integers up to 100. »

Use @@@ and Tuples to generate {f[a,a],f[a,b],f[b,a],f[b,b]}. »

Make a list of tree forms for the results of 4 successive applications of #^#& starting from x. »

Find the unique cases where i^2/(j^2+1) is an integer, with i and j going up to 20. »

Create a graph that connects successive pairs of numbers in Table[Mod[n^2+n,100],{n,100}]. »

Generate a graph showing which word can follow which in the first 200 words of the Wikipedia article on computers. »

Find a simpler form for f@@#&/@{{1,2},{7,2},{5,4}}. »

How are @@ and @@@ interpreted?

f@@expr is Apply[f,expr]. f@@@expr is Apply[f,expr,{1}]. They’re usually just read as “double at” and “triple at”.

Are all expressions in the Wolfram Language trees?

At a structural level, yes. When there are variables with values assigned (see Section 38), though, they can behave more like directed graphs. And of course one can use Graph to represent any graph as an expression in the Wolfram Language.

Tech Notes