Chapter 4 Creating Our Own Functions
Section 4.1 Values, Variables, & Identifiers
Subsection 4.1.1 Literal Values
In code, a literal value is something whose value is exactly as it appears in code. For example, when you see a
51
in code, you know it represents the numeric value 51, you cannot assign it another value, or change it in any way. There are a few different literal types in Racket, but for now we’ll focus on numbers. Racket allows for the following kinds of literal numbers:- Integers: Numbers (positive or negative), without a
.
. e.g.33
,-24601
- Floating Points: Numbers (positive or negative) that contain a
.
(no numbers should have,
s). e.g.6.022
- Complex Numbers: Numbers (positive or negative) of the form
a+bi
. In this form,a
andb
can be integers or floating points, andi
represents \(\sqrt{-1}\text{.}\) e.g.0+1i
,9.3+5i
Subsection 4.1.2 Variables & Identifiers
An variable is a value that can (potentially) change while a program as runnning (as opposed to literals). Becuase variables can change, there are two different parts of any variable; its name and its value. The name of a variable is also called an identifier. When you need a variable in Racket, you assign a value to an identifier using the Identifiers are important, and programming languages each have their own take on what is allowed and what isn’t, but there are some common rules that most programming lanuages adhere to. If you’re going to be programming in multiple langauges, it’s a good idea to stick to a small set of rules that you can reasonably assume will work. Here is such a list:
define
function. define
is the only function that we will look at in racket that does note return a value. Here are a few examples of using define
: (define x 6)
(define y (expt 2 4))
(define x (- x y))
- Names can only contain upper (
A-Z
) and lower (a-z
) case letters, digits (0-9
) and underscores (_
). Notably, this means no spaces in names! - Names cannot start with numbers.
- Names are case sensitive. i.e.
ypos
andyPos
would be two different variables.
Section 4.2 Creating an Anonymous Function With Lambda
Let’s say you wanted to use racket to find the length of the hypotenuse of a right triangle. You may recall that according to ol’Pythagoras, we can find the hypotenuse \(c\) based on the lengths of the other 2 legs, \(a, b\) as follows:
\begin{equation*}
c = \sqrt{a^2 + b^2}
\end{equation*}
Translated into Racket syntax, we get: Of course, we can’t use the expression above unless we
(sqrt (+ (* a a) (* b b)))
define a
, and b
. Also, it’s kind of a pain to write this all out multiple times if you need to. What would be nice is a way to create our own functions, so that we could reuse this piece of code whenever we wanted to. Enter lambda
!(lambda (PARAMETER0 PARAMETER1 ...) FUNCTION_BODY)
lambda
is Racket’s function-making function! Technically, we say that lambda
creates and returns an anonymous function. At its core, lambda
is a function that takes 2 arguments. The first is a list of inputs, or parameters, and the second is the function body, where we put what the function should do. for our Pythagorean theorem example above, we will need 2 inputs, a
and b
, and the expression we already have can suffice for the fuction body. Putting it together with lambda
we get:(lambda (a b) (sqrt (+ (* a a) (* b b))))
Section 4.3 Using Anonymous Functions
So we’ve now got a
lambda
expression that creates a function that will solve for the hypotenuse of a right triangle. How do we use it?. Say I know the legs are length 3 and 4, how do I get the answer? Let’s go back to the one rule of Racket syntax:(FUNCTION ARGUMENT0 ARGUMENT1 ...)
In this case, we have two arguments, specifically 3, and 4, giving us:
(FUNCTION 3 4)
But what do we put in place of
FUNCTION
? Well, whatever we put there has to be a function itself, but instead of a built-in fucntion like sqrt
or abs
, we want to use our special hypotenuse lambda
expression. You may have noticed that I described lambda
as both creating and returning a function. That means that we can use a lambda
expression anyplace that we need a function.((lambda (a b) (sqrt (+ (* a a) (* b b)))) 3 4)
What we have now is a Racket statement that uses
lambda
to create a custom function that has two parameters and does some math to them, and then feeds in 3 and 4 to that custom function. Here’s another example, this time for a function that finds the area of a triangle if we know the base and height (\(\frac{1}{2} * base * height\)).((lambda (base height) (* (/ 1 2) base height)) 6 17)
Section 4.4 Naming Names
At this point, you’re probably thinking ok great, so we just took a complex Racket expression and made it even more complex, what gives?. You are not wrong, if we wanted to use Racket to find multiple hypotenuse values using our
lambda
expression, we’d need to do something like this:((lambda (a b) (sqrt (+ (* a a) (* b b)))) 3 4)
((lambda (a b) (sqrt (+ (* a a) (* b b)))) 13 75)
((lambda (a b) (sqrt (+ (* a a) (* b b)))) 28 11)
((lambda (a b) (sqrt (+ (* a a) (* b b)))) 133 45)
This brings us to possibly the most important guiding principle in all computer programming: Programmers are LAZY. One of the main reasons we write comptuer programs at all is to make the computer do things we don’t want to have to do! So we certainly don’t want to type the same thing over and over again! All jokes aside though, a good rule of thumb for programming is that if you find yourself writing the same line of code multiple times, there’s probably a way to simplify it.
The main issue here is that
lambda
makes an anonymous function, so we can’t refer to it multiple times. If only there were a way to attach a name to a value...We do have such a thing!
define
! define
can be used to a value to an identifier. In this case, the value is the function created by lambda
, but its the same as if the value were a literal number as far as Racket is concerned.(define hypotenuse (lambda (a b) (sqrt (+ (* a a) (* b b)))))
Now that we’ve attached a name to our
lambda
expression, we can use hypotenuse
in its place:(hypotenuse 3 4)
(hypotenuse 13 75)
(hypotenuse 28 11)
(hypotenuse 133 45)
Subsection 4.4.1 Let’s Talk About Style
When programmers discuss style, we are referring to the way that code is written. An important thing to consider about your code is its readability, or how easy it is for someone to look at what you’ve written and understand it. Think about when you’re reading an article or a textbook. If the entie text were just in one big paragraph, it would be pretty hard to keep track of things as you are reading. So the writer will use things like paragraphs, line breaks and indentation (among others) to make it easier for you to following along. The same is true of prorgramming, and in many cases we even use the same tools, like line breaks and indentation.
Subsubsection 4.4.1.1 Line Breaks & Indentation
Let’s look at our hypotenuse function definition.
(define hypotenuse (lambda (a b) (sqrt (+ (* a a) (* b b)))))
This is the programming equivalent of a run-on sentence. Let’s see if we cn make it a bit clearer:
(define hypotenuse
(lambda (a b)
(sqrt (+ (* a a) (* b b)))))
By splitting the function up into three lines, we can more quickly identify key components. The name is right at the top, followed on the next line by the parameter list and then finally the function body on the third line. This makes it easy to pull out how to use the function without needed to look at the function body itself, or focus on the function body when we are developing the function.
You’ll also notice that each line is indented further than the previous. If you’re programming in the definitions pane of DrRacket, it will automatically indent at these same points, so you don’t have to worry about it. But if your curious, DrRacket will insert one indentation for every
(
at the beginning of a line. On the second line, the (define
expression is not yet closed, so the line starts off one indentation in. At the third line, (define
and (lambda
are both left open, so the line starts two indetation levels in. The ()
help us create groups of programming statements, and indentation visual aid to let us quickly identify these groups. In fact, there’s a reasonable argument to be made that we should throw in a few more newlines and indents to make our function even clearer like so:(define hypotenuse
(lambda (a b)
(sqrt (+
(* a a)
(* b b)))))
Subsubsection 4.4.1.2 Comments
Sometimes, it is useful to provide notes in a program to someone reading it so they can better understand what is going on. Normally, all your Racket code would have to follow Racket syntax. But you probably don’t think in Racket syntax. Thankfully, there’s a way for us to leave notes in code that will be ignored entirely by Racket. These notes are called comments and we use
;
to specify a comment. Racket will ignore all text following ;
until this end of a line.;Return the length of a hypotenuse given the
;lengths of the other two legs of a right triangle.
;Assumes that a and b are both nonegative numbers.
(define hypotenuse ;function name
(lambda (a b) ;parameters
(sqrt (+ ;pythagoras!
(* a a)
(* b b)))))
Sometimes, there’s a section of code that you want Rcket to ignore for some reason, but you don’t want to delete it entirely. This is another use of
;
, that we refer to as commenting out code.Subsubsection 4.4.1.3 Names
Earlier in this chapter, we discussed the rules around what we can use for identifiers in racket. If we’re talkign about code readability though, we should also think about using meaningful identifiers. Let’s look at an example:
(define foo
(lambda (a b c)
(+ a (* a b) (* a c))))
Do you have any idea what
foo
does? Sure, it adds the products of some numbers, but to what end? Why did someone decide to take the time to write this thing? When would you want to use it? All of these are good questions, that could’ve been avoided with some better naming.(define total
(lambda (food_cost tax tip)
(+ food_cost
(* food_cost tax)
(* food_cost tip))))
Mathematically (and programatically),
foo
and total
are identical. Hopefully, the purpose of total
is much clearer, as well as what the parameters represent. The only difference is that the programmer of total
took a little more time picking useful names for the parameters and function.Subsubsection 4.4.1.4
The styling advice provided here is meant to be as portable to other languages as possible. Using newlines and indentation, comments, and meaningful identifiers will go a long way towards making your code readable. Different programming languages will have different syntactic rules, the comment symbol my be different, they may allow for differnt kinds of identifiers, or they may reuqire you to use newlines at sepcific times. Always follow the syntactic rules of the languge you are writting in, but remember that if a human is ever going to read your code, you should. do your best to make that process easier for them. Also keep in mind that the human most likely to read your code is you, so be kind to yourself.