Teacher's Guide: Comparison Operators!
Overview
This lesson continues the discussion of conditional logic and Boolean values with the introduction of comparison operators, ==
, !=
, >=
, etc. The lesson also introduces the distance
and direction
functions, useful tools when developing programs in which sprites interact or take actions based on positioning on screen.
More about the lesson
In previous lessons, students have repeatedly used =
as an assignment operator rather than as a symbol establishing equality between two expressions. Given this background, the introduction of ==
in this lesson generally goes smoothly. With the exception of !=
, the syntax of all other comparison operators (>
, >=
, etc.) should be obvious to students.
The newly introduced functions distance
and direction
are straightforward. The potentially most troublesome aspects to using these functions—calling them using dot-notation and the need to take animation-queue issues into consideration—are issues with which students are well aware by now.
As shown in the handout, when used without an argument, direction
returns the direction the calling sprite currently faces, with 0
being "north". The returned value from such calls to direction
will always be between -180
and 180
.
The lesson illustrates that the distance
function accepts arguments. It provides two examples, one which involves passing a single sprite reference, and another which involves passing a pair of numeric values representing a set of (center-based) coordinates. Additionally, distance
accepts other representations of locations, including an array of two number values (direction [-100,150]
) and an HTML location object (direction {pageX: 50, pageY: 50}
). (Objects will be addressed formally in later units, beginning with the Date Objects! lesson.)
Similar to distance
, direction
also accepts arguments representing locations. When invoked with arguments, direction
's return value is the measure of an angle with vertex at the location of the calling sprite, with an initial side facing "north" (i.e., direction 0) and terminal side extending in the direction of the location specified by the argument, as illustrated by θ in the following diagram:
While this setup may seem counterintuitive for some purposes, it is a sensible choice for carrying out computations. Notably, distance
's output is consistent with the turnto
function: t.turnto t.direction(turtle)
generates the same behavior as t.turnto turtle
, so long as the former is preceded by a call to await done defer()
to ensure the call to direction
is evaluated at the right time.
A the benefit of using direction
in calls to turnto
is the greater flexibility it provides. It makes it possible to direct the calling sprite to turn in a direction based on another sprite's location, but not turn directly towards it. For example, t.turnto t.direction(turtle) + 180
causes the calling sprite to turn in the direction away from turtle
.
Using direction
to compute the relative facing angle from one sprite to another (e.g., how far the first would have to turn to face the other) can be tricky. The algorithm depends on which orientation one is considering (to the left or to the right) and also the magnitude of the angle. These more advanced issues are considered in this script.
Redundancy in Boolean expressions
It is not uncommon for students to write statements that contain redundancies in Boolean expressions, such as if touches(red)==true
. Upon evaluation of the call to touches
, this statement is equivalent either to if false==true
or if true==true
. Ideally, in this example, students would simply code if touches(red)
, as instructed in the previous lesson. While such redundancies are harmless, it suggests an incomplete understanding of Boolean expressions, though one that is easily remedied by identifying and explaining the underlying logic when the redundancies occur.
Notes to activities
The first two additional activities, Triangulate and ConcentricStars, do not necessarily involve comparison operators or conditional logic. They do make use of the distance
function, and also offer significant reinforcement of previously learned concepts.
The most common approach to InvisibleSquareFence is to compare the output from getxy
(unpacked into idividual numbers values using array destructuring to values representing the maxiumum and minimum x and y values of the fenced-in area.
When coding RaceWithEnd, students may choose ==
to attempt to determine when the race ends. However, depending on their approach to solving the problem, it may be possible that one or more sprites never hits that distance exactly. A better solution would make use >=
. This is an important issue that affects many coding challenges, especially when the output from calculations can be a non-integer value, so it is worth emphasising it to the whole class.
Additional activities
When you use a mobile phone, an individual cell phone tower can determine your distance from it using the time it takes to ping you and knowledge of the speed radio waves travel (i.e., they use the formula
distance = rate × time
). However, a single tower cannot determine which direction the signal came from. To pinpoint your location, three towers can work together using a technique called triangulation. Write a Triangulate program that places three sprites representing cell phone towers at different locations on the screen. Hide the turtle and then randomly position it on the screen. Draw a circle around each tower-sprite, with a radius equal to the distance to the hidden sprite. The intersection of the three circles gives the location of the hidden sprite.- The code on the right produces a star with
n
points usingn
equally sized diamonds. Use this code as the basis for a ConcentricStars program. When the turtle is at the tip of one of the diamonds, use thedistance
function to measure the distance to the origin. Use this value as the side length for a star that you will build around this original star. Hint: when drawing the next layer(s), you will mostly use the same algorithm as the one provided on the right, except adding some lines forpu()
andpd()
. ExpandingDots: Instruct a randomly moving turtle to make dots that get bigger and lighter the further the turtle gets from the origin. Keep the dots slightly inside the visible window (using comparison operators rather than the
inside
function). Note: this activity is challenging because students will likely need to consider both the location of the turtle with respect to the width and height of the screen, to ensure the dots are in the right place; and they will need to compute a color variable based on distance from the center of the screen.- CenterPoints: Copy your CenterPointArt script (from the Coordinates! lesson) and modify it so that it can create shaded center point squares in any of the four quadrants. Extra challenge: make the shading dark grey when it is on the bottom or the left side, or light grey otherwise.
- CarnivorousTurtle: Randomly place several turtles on the screen, and have each move randomly. Add a red turtle that heads to and eats the turtle closest to it. The red turtle should get bigger and faster with each successive meal. An easy way to code this is to use
scale
. Thescale
function works similar togrow
, but it also makes the Sprite move at a scaled speed as well. - Code a face with BedHead: Use your coding skills to draw some crazy hair, one colorful, wavy strand at a time. Use comparison operators to keep the hair within a circular region.
- EscapeArtist Code a variant of TurtleTag (an additional activity in the notes to the Conditional Logic! lesson) in which the chased turtle turns away from the follower whenever it gets close.
Ricochet: Draw a box on screen, to serve as a container for the turtle. Then instruct the turtle to move along straight paths within this box, until it reaches one of the sides. When it does, it should follow a new path that makes it look like it bounces off the side, the way you would expect an object in the real world to move. Use
touches
to sense when the sprite reaches an edge. Usedirection
to determine the turtle's new direction; you will likely need to use conditional logic as well, as the computations for the new direction depend on which side you are touching: left, top, right, or bottom.LaserTurtle: Write a script that creates many turtles and places them randomly about the screen. As the turtles, are created, have the default turtle zap and destroy any turtle that gets within 200 units of it. (For this exercise, you can "destroy" a turtle by simply hiding it.) To show the laser, use a hidden turtle that draws a line from the default turtle to the turtle it is zapping, and use the
cg
function to subsequently clear the laser beam after the target is destroyed. (Don't usecs
, as that will erase all turtles other than the default turtle!)
Beyond the lesson
Chained Comparisons
CoffeeScript allows for chained comparisons, such as a<b<c
. This is similar to the multiple assignment feature, e.g., x = y = 7
, introduced in passing in the Notes to the Variables! lesson, although the comparison with inequalities is evaluated from left to right, whereas the multiple assignment flows from right to left.
Admittedly, chained comparisons is a useful feature, which can be used to simplify code and make it more readible. However, this curriculum does not make use of this feature, for pedagogical reasons. Not all coding languages support chained comparisons, and eschewing them in this environment promotes student use of workarounds, in particular nested if
/else
statements (in this lesson) or logical operators such as and
and or
(introduced in the Logic Ops! lesson).
is
and isnt
Coffeescript provides the aliases is
and isnt
for ==
and !=
. In fact, it is idiomatic in CoffeeScript to use these equivalent keywords, a fact perhaps reflected in their appearance of the if
statements options in the Pencil Code coding-blocks palette.
While not denying arguments in favor of using these aliases, this curriculum opts for the traditional syntax ==
and !=
. This is based primarily on the lack of support for the aliases is
and isnt
across other coding languages
Comparison operators and non-Number values
Though the lesson limits the discussion of comparison operators to Numeric data (i.e., having data type Number
), these operators also work with other data types. The comparison of values of other primitive data types (i.e., all types except Object
, the sole collection data type in JavaScript) are fairly straightforward, though there are a few nuances.
Comparisons of two Boolean values are computed as if true
equals 1 and false
as 0. Hence, true > false
evaluates to true
.
Perhaps the most common other use of these operators is to compare strings. For example, "dog">"cat"
evaluates to true
, reflecting that "dog" comes after (i.e., is greater than) "cat" in the dictionary. However, comparisons of strings are complicated by the fact that such comparisons are based on the lexicographical ordering of letters in JavaScript (and other languages), which differs from standard dictionary ordering. For example, all capital letters precede all lower-case letters, resulting in potentially undesired or initially confusing results, as illustrated in the following example:
Comparison of values of type Object
is more complicated. When using ==
or !=
with objects, the comparison is based on references to the objects, rather than the values stored in the objects (refer back to the notes to the Variables! lesson for a discussion of the differences between primitive and object data types). The upshot is that tests for equality between variables referencing objects determine whether the two variables refer to the same object, rather than whether two distinct objects are otherwise equivalent. When using comparison operators that include the inequality sign, in contrast, the result of comparisons does take into account the content of the referenced objects, with the result of the comparison depending on how the referenced objects are defined. An example of comparing objects is provided in the notes to the Date Objects! lesson and discussed further in the notes to the Custom Objects! lesson.
Type coersion and truthy-falsy expressions
Coffeescript generally attempts to coerce values that do not match the expected data type to nonetheless work in an expression. For example, 3 * "5"
will evaluate to 15
. This is a feature that coders make broad use of, though there are nuances to its use and some serious potential pitfalls as well. This curriculum avoids intentional reliance on the type conversion features of Coffeescript, but the issue will undoubtably inadvertantly surface from time to time in student work, likely as seemingly inexplicable bugs (see What can go wrong for an example).
In the case of if
, Coffeescript attempts to convert the inputted expression to a Boolean value. Only a few values are coerced to false
. These are the so-called falsey values: undefined
, null
, 0
, and strings of length zero (e.g., ""
). Most other values are converted to true
when encountered in a Boolean context. Among other things, this means that non-zero Numeric values and non-empty strings are truthy.
An important exception to type coercion involves tests for equality and inequality. The ==
and !=
operators do not make use of type conversion. As will be discussed in the Technicalities section, below, comparisons using these two operators between values of non-matching type will always evaluate to false
.
What can go wrong
Mistakenly using =
instead of ==
.
A common mistake involves writing a Boolean expression using =
instead of ==
. For example, a student who intended to code if count==7
might mistakenly enter if count=7
. This can be a particularly vexing mistake because it doesn't stand out as being wrong and it won't yield an error to point the user in the right direction. Rather, the program will likely consistently fail to behave as expected.
The underlying pitfall is type coersion. Consider what happens when CoffeeScript encounters if count=7
: it first carries out the assignment contained in the argument to the if
statement, assigning the value of 7 to count
. Because an assignment statement such as count=7
always returns the assigned value (which you can verify at the Test Console: entering count=7
echoes 7
on the following line), CoffeeScript proceeds by interpreting the statement if 7
. As a non-zero Numeric value, 7
is truthy, so Coffeescript coerces the statement to if true
. The upshot is that, not only is the value of count
unintentionally set to 7, but the expression always evaluates to true
. This is illustrated, along with some other examples in the following script.
count = 10 if count=0 label "This won't print" if count=1 label "This will print!" #this prints!
Pedagogy
More-experienced teachers may note that in most other coding curricula, if logical operators haven't already introduced by this point, they would be the focus of the following lesson. This makes sense from a logical perspective, to wrap up the basics of Boolean topics. However, from a pedagogical perspective, there is little to argue in favor of that move; in fact the opposite is the case. In this curriculum, Boolean logical operators and
, or
, and not
have intentionally not yet been introduced, and will not be for some time.
As noted previously, this curriculum is designed to introduce concepts at a measured pace, in order to give students time to focus on acquiring a select few new ideas and develop computational thinking skills, all without becoming their becoming overwhelmed. In the case of logic operators, students who are introduced to them early on confuse the roles played by comparison and boolean operators, or get hung up in order-of-operations difficulties. True, delaying the introduction of logic operators involves some difficult tradeoffs. This is particularly true in the case of not
: coding if not inside(window)
is undoubtedly simpler than using an equivalent if
/else
structure without not
. However, while expedient to introduce it in the short run, students don't actually need not
now. Coming up with the alternative without not
is good computational thinking exercise, and does much to reinforce fluency in other aspects of conditional logic.
Technicalities
Abstract versus strict equality comparison
Javascript has two sets of comparison operators to test for equality and inequality, compared to only one in CoffeeScript. In JavaScript, ===
(!==
) is used for strict (in)equality comparison and ==
(!=
) is for abstract (in)equality comparison. Coffeescript's use of ==
lines up with JavaScript's use of ===
. That is, Coffeescript makes use of strict equality comparison.
With strict equality comparison, a boolean expression can only evaluate to true
if the two operands are of the same type (e.g., both are Numeric or both are String) and have the same value. If they are not the same type, the result is always false
.
The javascript abtract equality comparison operators are more accomodating. If the compared values are of different types, the ==
and !=
operators attempt to coerce the values to be of the same type, leading to the truthy-falsey results described in the discussion on Type Coersion above.
Enclosing a Coffeescript expression in backticks (`
), we can force Javascript abstract equality comparisons:
false=='0' #evaluates to false `false==''` #true; empty String is false `false==0` #true; zero is false `false=='0'` #true; String coerced to Number, 0 is false
A script contrasting abstract and strict equality comparison is available here.