CMSC 15100 — Lecture 4

More about expressions

So + is a function that works on Number, like so,

(+ 2+4i -3-4i)

Here, you might pause and go "wait a second, I thought we couldn't use infix notation???" This is where you must remember what an expression is defined to be: it must begin with a (, which is followed by a function name, which is followed by space-separated arguments, and ends with a ).

The answer here is that 2+3i is not an expression! First of all, it does not begin with a (. Secondly, it contains no spaces! This is important: Racket treats 2+3i as an entire unit, which is interpreted as a constant. This is kind of like how 23 is a constant, while 2 3 is two different constants, 2 and 3. So be careful: if you try to write 2 + 3i, Racket will not like that.

The same rules apply to negative numbers and rational numbers! The number -1 is treated as a constant and not as an expression, which would be (- 1). The number 31/5 is treated as a constant and not an expression, which would be (/ 31 5).

But there's more! The parentheses in Racket are actually meaningful! That is, we typically think of parentheses as an "extra" that only groups terms together. However, consider the differences you would get if you tried running the following in Racket:


5 * 7
(5 * 7)
(* 5 7)
    

The last is the correct expression. However, note that the first two lines will result in very different behaviour from Racket. In the first case, it responds with a list of values, interpreting each of 5, *, 7 as a value. In the second, it complains about trying to apply a type.

Recall that according to our rules, Racket expects that the thing immediately following an open parenthesis is a function. Here, it complains that 5 is not a function, but a Positive-Byte. A very common construction is the following,


((* 5 7))
    

which appears to be a regular old expression. But upon closer examination, we run into the same issue: ((* 5 7)) gets evaluated to (35), which is interpreted as an attempt to treat 35 like a function.

More about functions

Mathematically speaking, functions are simply mappings of inputs to outputs, with the restriction that an input gets mapped to only one output (i.e. if $f(x) = y$, then $f(x)$ will always be $y$, and not sometimes something else). We also saw that type ascriptions harken to the idea that the inputs and outputs of functions are drawn from some domain, like the natural numbers or complex numbers. We want these functions to work for all inputs for the domain that we specify. What we don't want to have happen is something like


(+ "taco" "horse")
    

Of course, strictly speaking, we can't, but we can think of similar functions that behave the same way. It might seem strange at first, but if we take the example of complex numbers, it is clear that addition on, say, natural numbers is not the same as addition on complex numbers. We can extend these ideas to all sorts of objects and types (and some are even very mathematically grounded!).

For instance, here's a similar kind of basic operation we can do with strings:

(string-append "cool" "boat")

The function string-append performs an operation called concatenation, which informally is just sticking a string onto the end of another string. But it's often thought of as a "product" for strings in the algebraic sense.

What other things might we want to do with strings? We already saw a few in the homework: we can ask about the length of a string, we can compare strings. We can use the results of these functions in other places.

For example, there is a popular website allows users to post messages of up to 280 characters in length. We can think of what kinds of information we would need to know in order to enforce this restriction. We would need to compare numbers and we would need to acquire the length of a message. This gives us an expression like the following.


(<= (string-length msg) 280) 
    

We also often want to compare strings. Checking whether two strings are equal or not is easy enough to do, with the special string equality function:


(string=? "goose" "duck")
    

However, we can do more interesting comparisons. For instance, just as we have numeric comparisons like greater than or less than, strings also have a notion of "ordering", so we can ask things like the following:


(string<? "goose" "duck")
    

While we interpret < mathematically for numbers, there's nothing stopping us from treating strings with an ordering. In fact, we do so without thinking all the time: taking the lexicographic (or alphabetical) order. This is exactly what string comparison does. You can learn more about string functions in Racket in the Racket documentation.

We can also play the same game with images. Obviously, we can't "add" two images in the same way as we do with numbers, because it doesn't make sense. But the example of strings is instructive: we can think of ways to arrange images.

Unlike with strings, we have many more ways of arranging and combining images. We can place them side by side like we would imagine we do with strings, but even here, we have many choices of how to do so.

Do we arrange them side by side?


(beside (circle 30 'solid 'orange) (square 60 'solid 'blue))
    

Or one above the other?


(above (circle 30 'solid 'orange) (square 60 'solid 'blue))
    

Or how about placing images on top of each other?


(overlay (circle 30 'solid 'orange) (square 60 'solid 'blue))
    

In addition to applying functions on the same kind of values, we can imagine how we might transform values of different types from one to another. For instance, we're already familiar with turning a Number into a String via the function number->string.

We also have facilities for turning a string into an image:


(text "here is a string" 24 'red)
    

You can find out more about images in Racket in the Racket documentation.