Luet oppimateriaalin englanninkielistä versiota. Mainitsit kuitenkin taustakyselyssä osaavasi suomea. Siksi suosittelemme, että käytät suomenkielistä versiota, joka on testatumpi ja hieman laajempi ja muutenkin mukava.

Suomenkielinen materiaali kyllä esittelee englanninkielisetkin termit.

Kieli vaihtuu A+:n sivujen yläreunan painikkeesta. Tai tästä: Vaihda suomeksi.


Chapter 5.1: Logic, Blood, and Shopping

../_images/person03.png

Intro: Let’s Make FlappyBug Harder

In Chapter 3.3, you wrote isLost, a method that defines a Game of FlappyBug to be over if the obstacle touches the bug.

def isLost = this.obstacle.touches(this.bug)

Recap: touches returns true or false. Consequently, isLost also returns one of the two Booleans.

As things stand, it’s too easy for the bug to advance simply by lurking at the top or bottom. Let’s edit touches so that the player loses if an obstacle hits or the bug is at the top or bottom of the playing area.

Here’s one way to do it:

def isLost =
  if this.obstacle.touches(this.bug) then
    true
  else if this.bug.pos.y <= 0 then
    true
  else
    this.bug.pos.y >= GroundY

The game is lost if the bug hits an obstacle.

Or if it didn’t hit an obstacle but is too high.

Or if it isn’t too high either but is too low.

That works. But it’s a complicated way of expressing something rather simple. We’d much rather write “if that condition or that other condition is met.” Perhaps you’ve already yearned after such an expression in the earlier chapters.

In this chapter, you’ll learn to combine Boolean values with logical operators such as “and” and “or”. We’ll get back to FlappyBug after a general introduction to these operators.

Logical Operators

The Scala language supplies us with several logical operators (logiikkaoperaattori).

Operator

Name

Example

Meaning

&&

and

someClaim && otherClaim

“Are both Booleans true?”

||

or

someClaim || otherClaim

“Is at least one of the Booleans true?”

^

exclusive or (xor)

someClaim ^ otherClaim

“Is exactly one of the Booleans true?”

!

not (negation)

!someClaim

“Is the Boolean false?”

In O1, you’ll mostly find use for &&, ||, and !. The last of those you already know from earlier chapters.

The logical operators take in Boolean values and also produce Booleans:

val number = 50number >= 0 && number <= 5res0: Boolean = false
if number >= 0 && number <= 5 then "a valid grade" else "an invalid grade"res1: String = "an invalid grade"
number == 100 || number == 50res2: Boolean = true
!(number >= 0)res3: Boolean = false

The “and” operator && produces true if and only if both subexpressions are true. Here, the first subexpression is true but the second isn’t, so the whole expression evaluates to false.

The “or” operator || produces true if either of the two subexpressions is true (or both are). Here, the first subexpression isn’t true but the second is, so the whole expression evaluates to true.

Terminological note: the subexpressions that an operator works on are often called operands (operandi). Of the logical operators introduced here, the negation operator ! takes only a single operand while the others take two.

Non-strict evaluation of logical operators

The operators && and || are evaluated non-strictly (väljästi). What this means for these operators is essentially that the left-hand operand is evaluated first and if it alone determines the value of the logical operation, the right-hand operand isn’t evaluated at all. (We will take a broader look at non-strict evaluation in Chapter 7.2.)

This non-strictness has practical significance, as illustrated below.

First, let’s define a couple of variables. Here, we use literals, but you can easily imagine the numbers being read from the user’s keyboard, for instance.

val dividend = 50000dividend: Int = 50000
var divisor = 100divisor: Int = 100

Let’s divide one number with the other and see what we get. As we do so, let’s play things safe and ensure that divisor doesn’t equal zero. We can use either && or || to do that:

divisor != 0 && dividend / divisor < 10res4: Boolean = false
divisor == 0 || dividend / divisor >= 10res5: Boolean = true

Assuming the divisor is not zero, we can swap the operands and still get the same result:

dividend / divisor >= 10 || divisor == 0res6: Boolean = true

But what if the divisor is zero?

divisor = 0divisor: Int = 0
dividend / divisor >= 10 || divisor == 0java.lang.ArithmeticException: / by zero
...

The computer performs the division as it evaluates the first operand. We get a runtime error. We never even reached the expression divisor == 0.

On the other hand, if we check the divisor in the first operand, things work out fine:

divisor == 0 || dividend / divisor >= 10res7: Boolean = true

Since divisor equalled zero, the expression divisor == 0 evaluated to true. It becomes unnecessary to evaluate the rest of the logical expression given that true || anythingAtAll is always true. Because of the non-strictness of these operators, the computer doesn’t even evaluate the second operand. Execution order matters!

Suppose you have the variables divisor and dividend as above and divisor equals zero. Below are a number of claims about the expression divisor != 0 && dividend / divisor < 10. Which of them are correct? Select all that apply.

Combining logical operators

You can use rounds brackets to group logical expressions just like arithmetic and other expressions.

if (number > 0 && number % 2 == 0) || number < 20 then
  println("Either both positive and even, or less than 20.")

if number > 0 && (number % 2 == 0 || number < 20) then
  println("Positive; moreover, either even or less than 20.")

Practice on logical operators

Study the following piece of code. Carefully consider what happens as it runs. You might wish to make notes on a piece of paper.

val first = 20
val second = 10
var third = 50

if first > second && first + second < third then
  third = 30
  println("One")

val result = first > second && first + second < third

if first < second || result then
  println("Two")

if first > second || second == third then
  println("Three")

third = 10

if !(result && third >= 50) then
  println("Four")
else
  println("Five")

In the second if above, the || operator in first < second || result has two operands. What happens when this expression is evaluated?

In the third if above, the || operator in first > second || second == third has two operands. What happens when this expression is evaluated?

Which lines of text do the println commands in the above code produce?

Further improvements to the vending machine

Logical operators are a convenient replacement for multiple ifs.

Recall the sellBottle method from Chapter 3.5. Here is the original Int-returning version:

def sellBottle() =
  if this.isSoldOut then
    -1
  else if !this.enoughMoneyInserted then
    -1
  else
    this.earnedCash = this.earnedCash + this.bottlePrice
    this.bottleCount = this.bottleCount - 1
    val changeGiven = this.insertedCash - this.bottlePrice
    this.insertedCash = 0
    changeGiven

Here are two attempts to reimplement the method with logical operators:

def sellBottle() =
  if this.isSoldOut || !this.enoughMoneyInserted then
    -1
  else
    this.earnedCash = this.earnedCash + this.bottlePrice
    // Etc. As above.
def sellBottle() =
  if this.enoughMoneyInserted && !this.isSoldOut then
    this.earnedCash = this.earnedCash + this.bottlePrice
    // Etc. As above.
  else
    -1

Assess the two shorter implementations.

Assignment: FlappyBug (Part 15 of 17: Deadly Edges)

Make FlappyBug harder as suggested at the top of the chapter. Do it as follows.

  1. Add an effect-free, parameterless method isInBounds in class Bug. It should return a Boolean that indicates whether the bug is within the legal playing area. The legal playing area is between the zero coordinate and the ground level (excluding those border values). Use a logical operator to implement the method.

  2. Modify isLost in class Game so that it also considers a game to be lost if the bug is out of bounds. Use logical operators and the isInBounds method you just wrote.

A+ presents the exercise submission form here.

Assignment: Blood Types

Task description

The module Blood contains several numbered packages. For now, we’ll concentrate on o1.blood1. Within that package, there’s a class named BloodType. Implement that class so that it matches the module’s documentation.

Instructions and hints

  • This example shows how the class should work:

    import o1.blood1.*val myBlood = BloodType("AB", true)myBlood: o1.blood.BloodType = AB+
    val yourBlood = BloodType("A", true)yourBlood: o1.blood.BloodType = A+
    val theirBlood = BloodType("O", false)theirBlood: o1.blood.BloodType = O-
    myBlood.canDonateTo(yourBlood)res8: Boolean = false
    yourBlood.canDonateTo(myBlood)res9: Boolean = true
    theirBlood.canDonateTo(yourBlood)res10: Boolean = true
    
  • You can also run the given program (test) to try out all the possible combinations of blood types.

  • Once again, you don’t have to attend to special cases or erroneous input unless specifically instructed to. For example, you can count on any user of class BloodType to pass in only meaningful strings as constructor parameters.

  • Try to implement the methods as simply as you can and without duplicating the same piece of code.

    • Use logical operators. Can you make do without if and match altogether (apart from toString)?

    • Call the other methods of the same class. Further tip: you can pass this as a parameter to a method.

A+ presents the exercise submission form here.

Assignment: FixedPriceSale

The Scaladocs in module AuctionHouse1 describe several classes for representing items that have been put up for sale in an imaginary online auction house. In this assignment, we’ll focus on a single way to sell an item: simply giving it a fixed price. Later assignments will feature auctions and changing prices.

Task description

Fetch the AuctionHouse1 module and view its Scaladocs. Implement class FixedPriceSale of package o1.auctionhouse so that its behavior matches the documentation. Write your solution in FixedPriceSale.scala.

Instructions and hints

  • You may wish to use an instance variable of type Option, with an initial value of None. If you do, remember that you need to annotate the variable with a data type (as in Chapter 4.3). Like this: nameOfVariable: Option[Type].

    Additional hints about instance variables

    You should make two instance variables of the three constructor parameters. You’ll find these two public variables listed in the docs.

    You’ll need a couple of more variables: one to track the number of remaining days and another to store the buyer’s name.

    Since there isn’t necessarily any buyer, Option[String] is a suitable type for the latter variable.

    The two variables just mentioned aren’t listed in the docs; they are part of the class’s internal implementation. Make them private and name them as you please.

  • The Scaladocs list the class’s members in alphabetical order, but that’s probably not the most convenient order for you to implement them (nor do the methods need to be in that order in your code). You are free to choose the order in which you implement the methods, but here’s one suggestion: toString, daysLeft, buyer, isExpired, isOpen, advanceOneDay, buy.

    Hints about the methods

    Study the docs with care. Notice, for instance, that “open” and “expired” aren’t exact opposites even though an item cannot be both at the same time.

    Most of the methods are effect-free. They only need to return the value of an instance variable or check whether the instance variables meet a certain condition. A single-line expression will do as the implementation for each of these methods.

    Items have two effectful methods: advanceOneDay changes the number of days remaining and buy changes who’s bought the item. You should have each of those things available in instance variables. Remember, in both methods, to check whether the sale was open; you can call isOpen to do that.

  • You may use the program testFixedPrice for testing. Use it.

    • That program initially generates error messages, which is simply because you haven’t yet implemented the methods that the program calls. The errors will go away once you do the assignment. (Remember: the cause of an error is not always where the message points to.)

    • You can initially “comment out” part of the test program if you want to test a partial version of your class.

  • You can also run TestApp, a program in o1.auctionhouse.gui that lets you create and manipulate FixedPriceSale objects in a graphical window that looks like this:

    ../_images/auctionhouse1_gui.png

A+ presents the exercise submission form here.

Optional Practice: Moment and Interval

The rest of the chapter consists of practice tasks that are voluntary but highly recommended. Take them as an opportunity to build up fluency. If the weekly deadline is ominously close, you might wish to do the other chapters first before returning here. The assignments below will probably take several hours to complete.

Introduction to o1.time

This problem will let you apply the logical operators more extensively and freely than the preceding ones. It also provides practice on a number of other concepts.

Moments and intervals

Imagine we have an application (or several) that needs to represent time intervals: an interval begins at a particular moment of time and continues until another moment. We intend to model these concepts as the classes Moment and Interval. Each Interval object is associated with two Moment objects that represent the interval’s first and last moment.

The Miscellaneous module contains a package o1.time and, therein, a partial implementation for class Moment. Interval is missing entirely.

The given code for Moment is explained below.

import scala.math.abs

class Moment(private val time: Int):

  override def toString = this.time.toString

  def distance(another: Moment) = abs(another.time - this.time)

  def isLaterThan(another: Moment) = this.time > another.time

  def later(another: Moment) = if this.isLaterThan(another) then this else another

  def earlier(another: Moment) = if this.isLaterThan(another) then another else this

end Moment

As we create a Moment instance, we specify the moment in time that it stands for. Our implementation doesn’t commit to any particular unit of time (days, years, nanoseconds, etc.); any integer will do.

A Moment delegates its whole toString method to the integer it stores. Notice that you can call a method on an Int! An Int’s toString method returns the characters that correspond to the number; e.g., for the integer 123 we get the string "123". (We will discuss the methods on Ints further in the next chapter, 5.2.)

There’s a distance method for determining how far apart two moments are.

isLaterThan, later, and earlier compare moments in different ways.

General hint: make use of abstractions

One of this problem’s main themes is to learn to make use of the other methods in the classes that you write. Sure, you’ve already done that in ealier assignments, too, but this one particularly highlights the matter.

If you write the required methods completely independently of each other, you’ll find yourself duplicating code. Don’t do that. Consider what other methods you’ve created that you could call. Try to keep your code DRY. Non-redundant code is easier to modify and less vulnerable to bugs.

In fancier words: Every method is an abstraction (Chapter 1.6). You should build new abstractions atop existing ones. For example, the method isLaterThan is as an abstraction of its concrete implementation, which takes the values of two instance variables and uses an operator to compare the two: this.time > another.time. The other methods that need to compare two Moments can simply use this method and don’t need to deal with the variables directly.

A task in four phases

Implement Interval and Moment so that they meet the specification. Below is a recommended workflow that you can either follow or ignore.

Phase 1 of 4: Getting started with Interval

  1. Read the introductions of both classes in the Scaladocs. Also read the docs for Interval’s methods length and toString, which you’ll implement first.

  2. Create a new file for class Interval.

  3. Define the constructor parameters. Since each parameter needs to be stored in an instance variable, define those, too, in the class header. Should these instance variables be private?

  4. Implement length. Use a method from class Moment.

    • Now and later: resist the temptation to add public members in class Moment beyond those request in the docs. In particular, don’t make the instance variable time public.

  5. Implement toString. It’s a bit more complicated. Here are some hints:

    • Use the methods you have at your disposal.

    • You can use an ifelse structure to cover the three different cases.

    • Remember that a string can be “multiplied”. For instance, "ho" * 3 yields "hohoho".

  6. See the starter code in timeTest. Add commands there as you see fit: create Interval objects and call their methods.

When are two Moments equal?

If you use == or != to compare moments, what you get is an identity comparison (“Do these two references point to the same Moment object?”; see Chapter 3.3.) What you don’t get is a comparison of the Moment objects’ actual contents. These operators won’t tell you whether two distinct Moment objects represent the same moment in time (i.e., whether their time variables have identical values).

In this assignment, identity comparison and those two operators are completely unnecessary. You don’t stand to gain anything from using them. Other methods will serve you better!

Phase 2 of 4: isLaterThan and overloading

The interval assignment was quite a tranformative experience.

I was already well on my way, writing an if statement in each method and not reusing methods, when a couple of formidable and eagle-eyed course assistants, who just happened to be in the room, pointed this out to me.

I didn’t dare defy the wise assistants so I racked my brain, sweating, to figure out how it could even be possible to manage without an if. Once I came up with a solution for isLaterThan(Interval) (helped by a hefty dose of hints from the assistant), I felt like I had just invented the wheel. It was ingenious!

  1. Add isLaterThan in class Interval. Note that the method name has been overloaded (Chapter 4.1): there are two methods with the same name. One takes in a Moment and the other an Interval. Implement the former first. Make use of an existing method.

  2. Implement the second isLaterThan method in Interval. Make use of the method you implemented just before. As you do so, remember a detail from Chapter 4.1: if an overloaded method calls its namesake, Scala requires an explicit type annotation for the calling method’s return type. That means you’ll need to annotate the type on at least one of the isLaterThan methods.

  3. Add more test code to timeTest and run it. Confirm that both isLaterThan methods work as intended.

  4. Moment should also have an isLaterThan method that takes in an Interval parameter. Add that method to the class.

Phase 3 of 4: logical operations

Continue implementing methods on Interval and Moment:

  1. Of the two contains methods of Interval, implement the one that takes in a Moment. Use logical operators in combination with methods that you have previously implemented.

  2. Implement the other contains method. Again, use logical operators and the other methods. Also recall what was just said about overloaded methods.

  3. Implement isIn in class Moment. It essentially does “the same thing in reverse” as another method you already wrote, which gives you a very simple implementation for it.

  4. Implement overlaps in class Interval. Once again, you can find uses for logical operators and existing methods. Make sure you cover all the possible cases.

  5. Add more method calls in timeTest and test your new methods.

Phase 4 of 4: combining instances to produce new ones

The methods union and intersection are still missing from class Interval. Implement them and you’re done.

A+ presents the exercise submission form here.

How private is private?

Student question: How can that Moment class work? time is private!

Let’s take a moment to consider why the given code from the previous assignment doesn’t produce an error message.

class Moment(private val time: Int):
  def isLaterThan(another: Moment) = this.time > another.time

time is private. It’s not available for external use. But what counts as external, exactly?

A Moment object can access its own time.

A Moment object can also access the time of another Moment object: we simply prefix the variable name with an expression that refers to another object of the same class. Here, that expression is the variable name another.

Within a class’s code, the private modified indicates that a member of the class is meant for internal use within that class. The private member isn’t accessible by only a single object but also by the class’s other instances; it is private to them collectively.

Optional Practice: Auctions

Assignment: DutchAuction

Go back to AuctionHouse1. Implement class DutchAuction as specified.

Instructions and hints:

  • First and foremost: make sure you understand what “Dutch auctions” are! The details are in the Scaladocs. Unless you have a clear idea of what the class should do, implementing it will be tough going.

  • As you read the docs, you’ll notice that some parts of DutchAuction are identical to FixedPriceSale. You can copy parts of your earlier solution into your DutchAuction class.

  • You can again use TestApp from o1.auctionhouse.gui to experiment with your class. You can of course also write your own text-based program for testing.

  • If you have an Int and need a Double, you can either

    • do what we did in Chapter 1.3: 1.0 * myInt

    • or do what we’ll do in Chapter 5.2: myInt.toDouble

A+ presents the exercise submission form here.

WETWET?

Are you a bit upset that you had to write the same methods again in class DutchAuction or at least copy them from one file to the other? Outstanding! That means that you’re motivated to reach Chapter 7.3 where you’ll learn about avoiding such code duplication.

Assignment: EnglishAuction

Stay with AuctionHouse1 and implement EnglishAuction.

Instructions and hints:

  • Again, first make sure you understand exactly how “English auctions” are expected to work.

  • A simple Bid has been provided, which you should find useful. There is also a bit of starter code for EnglishAuction.

  • Even though it’s possible to solve the assignment with a buffer that stores all bids placed on an item, you don’t actually need to do that. As it turns out, you don’t need to solve every last bid to fullfill the requirements...

  • Instead, you can use two variables to store just the two highest bids, as shown in the starter code. Notice these two variables’ initial values: when there are no actual bids yet, the variables refer to “empty” Bids equal to the starting price.

  • TestApp will again be of service.

A+ presents the exercise submission form here.

Summary of Key Points

  • Logical operators operate on Boolean values. You can use them to express meanings such as “X and Y” and “X or Y”.

  • Links to the glossary: logical operator; to overload; DRY.

Feedback

Please note that this section must be completed individually. Even if you worked on this chapter with a pair, each of you should submit the form separately.

Credits

Thousands of students have given feedback and so contributed to this ebook’s design. Thank you!

The ebook’s chapters, programming assignments, and weekly bulletins have been written in Finnish and translated into English by Juha Sorva.

The appendices (glossary, Scala reference, FAQ, etc.) are by Juha Sorva unless otherwise specified on the page.

The automatic assessment of the assignments has been developed by: (in alphabetical order) Riku Autio, Nikolas Drosdek, Kaisa Ek, Joonatan Honkamaa, Antti Immonen, Jaakko Kantojärvi, Onni Komulainen, Niklas Kröger, Kalle Laitinen, Teemu Lehtinen, Mikael Lenander, Ilona Ma, Jaakko Nakaza, Strasdosky Otewa, Timi Seppälä, Teemu Sirkiä, Joel Toppinen, Anna Valldeoriola Cardó, and Aleksi Vartiainen.

The illustrations at the top of each chapter, and the similar drawings elsewhere in the ebook, are the work of Christina Lassheikki.

The animations that detail the execution Scala programs have been designed by Juha Sorva and Teemu Sirkiä. Teemu Sirkiä and Riku Autio did the technical implementation, relying on Teemu’s Jsvee and Kelmu toolkits.

The other diagrams and interactive presentations in the ebook are by Juha Sorva.

The O1Library software has been developed by Aleksi Lukkarinen, Juha Sorva, and Jaakko Nakaza. Several of its key components are built upon Aleksi’s SMCL library.

The pedagogy of using O1Library for simple graphical programming (such as Pic) is inspired by the textbooks How to Design Programs by Flatt, Felleisen, Findler, and Krishnamurthi and Picturing Programs by Stephen Bloch.

The course platform A+ was originally created at Aalto’s LeTech research group as a student project. The open-source project is now shepherded by the Computer Science department’s edu-tech team and hosted by the department’s IT services; dozens of Aalto students and others have also contributed.

The A+ Courses plugin, which supports A+ and O1 in IntelliJ IDEA, is another open-source project. It has been designed and implemented by various students in collaboration with O1’s teachers.

For O1’s current teaching staff, please see Chapter 1.1.

Additional credits appear at the ends of some chapters.

a drop of ink
Posting submission...