Refresher: Algebraic expressions and functions from Math
Algebraic expressions are mathematical expressions that are constructed using
-
numbers
-
variables
-
and operators
For example the expression
is constructed by using
-
The number \(1\)
-
The operator \(+\)
-
The number \(2\)
Operators like \(+\) take inputs, in the case of \(+\) 2 inputs one to the left of the \(+\) and one to the right, and gives us back (we say return) an answer.
Numbers on the other had do not take any inputs, they just denote a value.
We can also use variables, these are names that we introduce in our expressions and refer to numbers or even other expressions. For example
The natural question now is, how do we go about evaluating the preceding 3 equations in order to find out what \(z\) refers to. To do so, we need to know
-
The rules of evaluation for each operator, i.e., what does \(+\) do when given two numbers
-
The mapping of each variable used in our set of equations
So, in our example, given that \(x\) is \(10\) and \(y\) is \(20\) we can calculate \(z\) by adding \(20\) to \(10\) to give us \(30\)
Of course there are many other operators in math that we can use, and each operator comes with a set of rules that tell us what the operator does to its inputs. Here are some other, typical, mathemtical operators
-
\(-\), subtraction, takes 2 inputs like \(+\), but instead of adding one number to the other, we take away one number from the other
-
\(\times\), multiplication, takes 2 inputs and returns the result of adding the first input to itself exactly the number of times as the second input, i.e., \(2 \times 3\) is the same as adding \(2\) to itself 3 times, e.g., \(2 + 2 + 2\) or to follow our definitions here
\[\begin{eqnarray*} x & = & 2 + 2 \\ z & = & x + 2 \end{eqnarray*}\] -
\(\sqrt{x}\), square root, takes 1 input \(x\) and returns a new number \(y\) such that \(y \times y\) returns \(x\)
We can use more than one operator in our expressions, we already slipped and provided an example above with the explanation of multiplication. We know that we can build large and complicated expressions that use more than one operator and in fact more than one different operator. The question then becomes, which operator are we supposed to work on first? Does the order matter or do we get the same end result regardless of the order of evaluation that we take? We already know that the answer is it depends. We also know that in Mathematics we have a way of grouping sub-parts of an expression (called sub-expressions) in order to indicate the order using parenthesis. For example
We match an open parenthesis with a closing parenthesis starting from left and moving to the right. Everything inside the matched parenthesis denotes a sub-expression. So the above expression has the following sub-expressions as denoted by the matching parenthesis
We can even rewrite our expression by generating variables for each sub-expression and replacing with our new variable name the sub-expression in our equation e.g.,
We know from Mathematics that if we want to evaluate our original \(q\) expression we do so by going from left to right inner-most parenthesized sub-expression first. For each sub-expression that we evaluate we use the result to replace the sub-expression. Here are the steps
Can you see the relation between the steps that we took to evaluate \(q\) and the set of equations that we generated when we re-wrote \(q\) by introducing new variables for each sub-expression?
Having all these operators in Mathematics is useful but we typically also would like to define our own operations. In fact all the given operations like \(+\), \(\times\) etc. have been defined by mathematicians and because of their common use we all learn these operations early on and consider them as given. For this purpose we use a function which typically has the following form
where
-
\(f\) is the new name or symbol that we are introducing as a new operator, this is the function name.
-
\(x\) is called a formal argument, it is a name that we are giving to the input that our new operator will accept. If we have more than one input we then add a new argument and separate each argument with a comma, i.e., \(f(x,y,x) = e\)
-
the equal sign \(=\)
-
and an expression \(e\), we will refer to this expression as the body of the function, that will use the inputs by referring to their names, and when evaluated will provide the output for our new function \(f\)
-
we say that \(f(x) = e\) is the function’s definition.
Here is the same function definition but annotated with the names that we use for each component
Let’s define a function for raising a number to a given power. We are going to call this function \(poower\), here is our definition
Notice how \(x\) is used inside the body of the function \(power\) to refer to the functions first input. Let’s decompose \(power\) into its subparts
function name |
\(power\) |
function arguments |
\(x\) and \(y\) |
function body |
\(\underbrace{x \times x \times \cdots \times x}_{\mbox{ $y$ times }}\) |
Now that we have a new function defined how do we use it. We use the function by writing the name of the function and providing inpouts for each of the arguments, for example \(power(2,3)\). We call \(power(2,3)\) a function call and we call the numbers that we are giving to \(power\) inputs.
In the case of an expression that uses a function like \(power\) our process of evaluating the function call to \(power\) we need to take care of passing the inputs as arguments. We take the inputs provided in the function call and match the inputs by position to the argument names given in the function’s definition. This matching gives us a temporary mapping from arguments to inputs for this specfific function call. Now that we have our temporary mapping we then perform a search and replace on the function definition’s body and replace all occurrence of an argument with its mapped input. Once we are done with our search and replace we proceed with the evaluation of the now modified function definition body.
Let’s look at the steps in detail using an example.
We would like to evaluate the following expression \(q = power(2,4)\)
From Math to Racket
Most languages and especially Racket have their roots in Mathematics. The languages that we will be using in this course have functions, similar to mathematical functions, as the main mechanism to transcribe a computation for the computer to evaluate.
The syntax used in our languages is close but not identical to mathematical equations.
Math syntax |
Racket syntax |
Comments |
\(1\) |
|
We type numbers and Racket knows what we mean |
\(1 + 1\) |
|
Parenthesis are required and the operator goes first! |
\(1 + 1 + 1 + 1\) |
|
Parenthesis are required and the operator goes first! |
\((1 + 2) - (3 + 4)\) |
|
Nesting of sub expressions is similar but, operators first! |
\(x = 2\) |
|
Special word |
The languages we are going to use can deal with numbers just like Mathematics. Our languages however also accommodate for other inputs like
-
Strings, we create a string by wrapping the text we want to use as a string with
"
, e.g."this is a string"
-
Symbols, we create a symbol by prepending a
'
to a word, e.g.,'name
. Symbols do not allow spaces like strings and we will later see more on the difference between symbols and strings -
Booleans, are exactly 2 distinct values, the value
#true
and the value#false
.
All of these are expressions in our language. They are a special sub-group of expressions in that, like numbers, we can create them by typing them in our programs and we can provide them as inputs to a function, and/or, they can be returned as results from calling a function. We refer to this special sub-group of expressions as values and we have a way of identifying them
Values
A value is anything in our program that can be
-
Given as an input to a function.
-
Returned as a result of a function call.
-
Stored (this criterion will make more sense as we progress in the course and introduce more language features)
The Stepper
DrRacket (by extension your computer as well) is a very fast evaluator. The languages that we use allow us to use their syntax in order to write our expressions and our own functions. [1] Once we have written down our expressions we then ask from the computer (in our case DrRacket) to use its predefined rules for evaluation to read in our expressions and evaluate them. DrRacket will then do one of two things
-
return back a result. The result might be the one we were expecting or not.
-
return an error because something went wrong while evaluating our expressions.
Let’s try a little experiment.
-
Take our expression \(q\) from earlier.
-
Rewrite \(q\) using the syntax that DrRacket understands (remember, parenthesis are required and operators go first)
-
Type your rewritten expression in DrRacket’s definitions window
-
Click Run. Did you get an answer? Was it the one you expected?
-
Let’s try something else, this time click on Step. A new window should appear
This new window is the Stepper Window. The Stepper is part of DrRacket. The Stepper’s job is to evaluate your expression, in the same way that clicking Run will do, but the Stepper does not perform all the evaluation steps in one go. The stepper
-
stops before each evaluation step
-
displays the expression before taking an evaluation step (the expression on the left side of the Stepper window)
-
marks in green the sub-expression that the evaluation step worked on
-
displays the expression after taking an evaluation step (the expression on the right side of the Stepper window)
-
marks in purple the result of the evaluation step taken on the previously selected sub-expression (the one on the left in green)
The top left corner of the Stepper window allows you to navigate forward and backwards (like you do on your browser when you want to move forward in your browser history or backwards) in the evaluation steps taken by DrRacket. This is close to what we write when we evaluate our mathematical expressions and we need to show all our steps.
Functions
Our languages in DrRacket allow us to define functions. Let’s write a simple function that takes one argument and adds 2
to it in both mathematical syntax
and Racket syntax and then explain in detail how function definitions work in Racket.
Mathematical syntax |
Racket syntax |
\(add2(x) = x + 2\) |
|
To define a function in Racket we again use the special keyword define
but this time the parenthesis look a little weird. Let’s walk through it. Let’s first split our Racket’s function definition in pieces
-
(define
and its matching closing parenthesis on the next line)
we’ve seen before. This is part of usingdefine
we need to wrap it with parenthesis -
(add2 x)
this part of the definition is new. When we usedefine
and immediately after it we have a sub-expression in parenthesis then we are defining a function.-
The name of the function is the first word (or symbol) after the open parenthesis, in this example
add2
-
Following the name of the function we then provide our arguments separated by one space, in this example we have one argument
x
-
-
(+ x 2)
this is the body of our function which must be a valid Racket expression.
Great! Let’s use our new function. Paste the definition of add2
in the Definitions Window and click Run. The Interactions window refreshes letting you know that DrRacket has read your code in the Definitions Window and it is now ready to run any expression that you type in the Interactions Window.
Let’s try
(add2 3)
What do you get back as the answer?
It is always a good idea to check (we say test) your code once you have it written down. Having to write tests every time in the Interactions Window
is a lot of work. There is a better way to write tests. We can use a special function called check-expect
. check-expect
takes 2 arguments and checks if the two arguments are the same. Let’s use it to write a test for add2
.
Write the following in your Definitions Window and click Run
(define (add2 x)
(+ x 2))
(check-expect (add2 3) 5)
What do you see in the Interactions Window?
Now let’s try this again, this time click Step. What happens now?
Observe that the first evaluation step that the Stepper performed was to work on the sub-expression (add2 3)
.
This is one of the rules of our languages.
When we call a function and we provide the necessary inputs for the function being called, the first step in our evaluation is to make sure that all the expressions passed as inputs to the function are values. This gives us 2 cases
-
the input is already a value, e.g., a number or a boolean or a string. Then there is nothing to do here.
-
the input is an expression that is not a value. Then we grab that input and we start evaluating until we get to a value or the evaluation hits an error.
We process all of our inputs, left to right first until they are all value. Once all our inputs are values we then proceed to call our function.
Use the Stepper to step through the evaluation of check-expect
.
Let’s intentionally cause an error in our test to see what happens? Let’s change the code in our Definitions Window to be
(define (add2 x)
(+ x 2))
(check-expect (add2 3) 4)
Click Run. What happens now? Use the Stepper again and see how the evaluation proceeds.
The need for comments
Notice how the documentation for check-expect
tells the reader in English what the function does and also provides examples.
It is always a good idea to document your code, both for others to read and understand your code but also for yourself when you have
to revisit your code.
In Racket (and the languages we are going to use in this class) we can create a comment by typing a semicolon ;
this indicates
that after the semicolon and until the end of the line, the text is a comment and it does not take part in the evaluation process.
;; add2 takes as input a number and returns the result of add 2 to the number
;; example:
;; (add2 1) => 3
(define (add2 x)
(+ x 2))
(check-expect (add2 3) 5)
Operations on different values
The languages that we will use come with an extensive library of functions predefined in DrRacket that we can use.
Making decisions
A common pattern when building our expressions to solve a problem is the ability to make a decision based on a condition.
Let’s take a simple toy example here. We would like to detect if a given number falls above or below a threshold. Let’s say that
our threshold is 100
. We would like to have a function that will take as input a number and return #true
if the given number
is equal to or greater than 100
, otherwise it should return #false
.
Racket has special syntax on how to make decisions called cond
.
cond
takes a series of question-answer pairs. Each question-answer pair is wrapped in square parenthesis.
The evaluation rules for cond
are as follows:
-
We proceed from top to bottom, left to right.
-
Grab the first question-answer pair. The first element of the pair is the question. This is an expression that when evaluated returns a Boolean. The second element of the pair can be any expression that can return whatever we deem appropriate.
-
Evaluate the question in our question-answer pair and
-
if the question evaluates to
#true
then proceed to evaluate the answer from our question-answer pair. The result of the answer element from our question-answer pair is the result of the wholecond
expression. -
if the question evaluates to
#false
, then move down one line to the next question-answer pair and repeat the process starting at step 2.
-
Let’s write the code to detect if a given number is greater or equal to 100
;; given a number return true if the number is greater or equal to 100
;; else return false
(define (over-threshold? number)
(cond
[(< number 100) #false]
[(>= number 100) #true]))
(check-expect (over-threshold? 100) #true)
(check-expect (over-threshold? 150) #true)
(check-expect (over-threshold? 10) #false)
Here is another function that uses cond
can you figure out what it does?
(define (to-letter total)
(cond
[(>= 100 total 95) 'A]
[(>= 94 total 90) 'A-]
[(>= 89 total 85) 'B+]
[(>= 84 total 80) 'B]
[(>= 79 total 75) 'B-]
[(>= 74 total 70) 'C+]
[(>= 69 total 65) 'C]
[(>= 64 total 60) 'C-]
[(>= 59 total) 'D]))