The latest instance of the course can be found at: O1: 2024
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. Myös suomenkielisessä materiaalissa käytetään ohjelmaprojektien koodissa englanninkielisiä nimiä kurssin alkupään johdantoesimerkkejä lukuunottamatta.
Voit vaihtaa kieltä A+:n valikon yläreunassa olevasta painikkeesta. Tai tästä: Vaihda suomeksi.
Chapter 2.7: A Complete Scala Application
About This Page
Questions Answered: How do I write and store an entire Scala program? Where does a Scala application’s execution begin? How can I read input from the user’s keyboard? Now that I’ve created a conceptual model of a game world, how can I define a GUI that displays the world?
Topics: App
objects. Keyboard input with readLine
. The basics
of graphical user interfaces with o1.View
.
What Will I Do? Mostly program, guided by the text.
Rough Estimate of Workload:? Over an hour.
Points Available: A60.
Related Projects: Ave (which we’ll create from scratch), Odds, IntroApps (new), FlappyBug.
Introduction
You have created objects. You have called their methods in the REPL, an environment that is well suited for experimentation.
However, the commands that you’ve entered in the REPL don’t form a unified whole that is stored for later use, that can be lauched again at will, that can be readily edited, and that is easily copied for someone else to run. To achieve these things, you’ll need to store your application in a file.
Let’s take a stab at doing just that.
A Traditional Program
Already the ancient Romans started the study of each new programming language or software technology by using it to create a so-called “Hello, World” program. Such a program simply displays a message and does nothing else.
Let’s create and store a program that contains a couple of Scala commands and that we can launch as may times as we like. These print commands will serve:
println("Ave, Munde!")
println("Well, ave at thee, too!")
Create a new Eclipse project for this experiment:
- Make sure (with the buttons in the top-right corner) that you are in Eclipse’s Scala perspective and not, say, the Java perspective.
- File ‣ New ‣ Scala Project
- Name the project Ave, for instance.
- Tick Create separate folders for sources and class files, if it’s not already ticked.
- Hit Finish. The Ave project shows up in Eclipse’s Package Explorer.
Create a new Scala code file:
- Right-click the
src
folder within the Ave project and select New ‣ File. (In case that option isn’t available, select New ‣ Other ‣ General ‣ File.) - Find the File name field and enter
Ave.scala
- Hit Finish. The file shows up within the project and also opens up in Eclipse’s editor.
Write the program:
- Enter the two
println
commands in the empty file you just created. Save the file. - Witness: Eclipse lights up your code like it’s Christmas. The Problems tab elaborates: Errors: expected class or object definition. What’s wrong?
The underlying problem is that we haven’t set up our application as a proper object-oriented program. When we write a Scala application, we need to define a special “app object” that provides a starting point for program execution. This object can then activate (call) other program components as needed.
The GoodStuff project has an app object named GoodStuff
, which you’ve activated before
to run the application. The Pong project contains an app object named PongApp
. Our Ave
application, however, lacks such an object.
Writing an App Object
Here’s a template for an app object:
object MyOwnApplication extends App {
// This is an app object.
// The commands you enter here will be executed when the app runs.
}
App
data type.
The object now represents our application as a whole and enables
us to run the program.At this stage of O1, feel free to think of extends App
as simply a phrase that marks
an app object. In later chapters (e.g., 7.2) it will turn out that the extends
keyword
has many other uses as well.
A Tiny But Complete Scala Program
A larger program may consist of dozens, hundreds, or even more classes and singleton objects. However, for our tiny greeting program, we need just a single app object where we place the print commands.
Edit Ave.scala
to contain the following:
A tip for indenting
A handy way to indent code in Eclipse is to select the lines
you wish to indent and press the Tab
key. Unindenting
works the same way with Shift+Tab
.
object Ave extends App {
println("Ave, Munde!")
println("Well, ave at thee, too!")
}
Now you can run the program by selecting first Ave.scala
, then Run
‣ Run As ‣ Scala Application in the menu. Try it. Notice that the output appears in
Eclipse’s Console tab, which is down by the REPL.
(That was also how you started GoodStuff and Pong back in Chapter 1.2. For alternative ways to select and run an app in Eclipse, see that chapter.)
You have now written a complete Scala application. Submit it via this form.
A+ presents the exercise submission form here.
Reading Keyboard Input
The Ave app produces the same output each time it’s run, and the user has no way to affect what the program does as it runs. In constrast, most meaningful applications take in some sort of input (syöte) from their users, either directly or indirectly. Here are some examples:
- The program has a graphical user interface. The user can click on it to indicate what they wish the program to do.
- The program operates on data that it loads from files stored on the computer’s hard drive.
- The program interacts with the user in the text console. It pauses to wait for the user to enter lines of text as input.
We’ll get started with graphical user interfaces later in this chapter. Working with files will be discussed in Chapter 12.2. But first, let’s explore the third form of user interaction.
A text-based app in the console
Let’s write a program that works in Eclipse’s Console tab as shown below.
Halt! Who is it? Pechkin the Postmaster Ave, Pechkin the Postmaster!
Enter
.The readLine
function
The library function readLine
receives, or “reads”, a single line of user input.
Calling readLine
suspends the program until the input has been received. The function
returns the input as a String
.
The interactive program described above can be implemented with readLine
:
import scala.io.StdIn._
object GreetingApp extends App {
println("Halt! Who is it?")
val name = readLine()
println("Ave, " + name + "!")
}
readLine
is to import
it first.
StdIn
is short for “standard input”, which here essentially
means input that the user enters through a text console.Type in (or copy–paste) the above program in Eclipse. You can either edit the Ave
object or create a new file for this second app.
Run the program. See what it prints in the console and answer the program’s request for input. (N.B. Due to Scala IDE’s limitations, you may need to click the text console before you can use the keyboard to enter input.)
It’s also good to know that you can pass a string parameter to readLine
. If you do,
the function first prints the string as a prompt and then reads the user’s input off the
same line. Here’s an example:
val name = readLine("Halt! Who is it? ")
println("Ave, " + name + "!")
This produces interactions like the one below.
Halt! Who is it? Pechkin the Postmaster Ave, Pechkin the Postmaster!
readLine
in Scala IDE’s REPL
Scala IDE’s built-in REPL is not yet the finished article. It has
some shortcomings, one of which is that reading input doesn’t work
as one might hope. For instance, entering a readLine
command in the
REPL produces an error message.
Assignment: Odds (Part 5 of 9)
Let’s write a small test program for the Odds project from Chapters 2.4 and 2.5.
Our program will use keyboard input to create Odds
objects, call the objects’
methods, and print a report of the return values.
Task description
Two of the files in project Odds are relevant to this assignment. Odds.scala
you already
know; it defines class Odds
. Now take a look at OddsTest1.scala
. It defines an app
object, but the definition is incomplete and the app doesn’t actually use class Odds
at
all.
Flesh out the app object so that it produces an output that exactly matches the following example. (Of course, the user might well enter numbers other than the ones shown.)
Please enter the odds of an event as two integers on separate lines. For instance, to enter the odds 5/1 (one in six chance of happening), write 5 and 1 on separate lines. 7 2 The odds you entered are: In fractional format: 7/2 In decimal format: 4.5 Event probability: 0.2222222222222222 Reverse odds: 2/7 Odds of happening twice: 77/4 Please enter the size of a bet: 50.0 If successful, the bettor would claim 225.0 Please enter the odds of a second event as two integers on separate lines. 10 1 The odds of both events happening are: 97/2 The odds of one or both happening are: 70/29
Instructions and hints
- You can start by running
OddsTest1
as given. It reads in some input but doesn’t produce the correct output. - In
OddsTest1
, add the commands to createOdds
objects and call their methods.- Do not edit class
Odds
or copy any of it intoOddsTest1.scala
. Use the class as it is. - It’s important that you sequence the commands
right. Pay attention to where within
OddsTest1
you create newOdds
objects. You can create an instance only after you’ve read the required inputs; on the other hand, you need to create it before you can call any methods on it.
- Do not edit class
- Since
OddsTest1
andOdds
are in the same package, you can instantiateOdds
inOddsTest1
withoutimport
ing anything. - You’ll notice that the given code calls functions named
readInt
andreadDouble
. These two work likereadLine
, above, differing from it only in that they interpret user inputs as numbers. For example,readInt
returns a value of typeInt
, not aString
. - Hint: you can use
both
to compute the odds of an event occurring twice. (E.g., to roll a six twice is to roll a six and to roll another six.)
Submission form
A+ presents the exercise submission form here.
Optional assignment: eliminate redundant code with abstraction
OddsTest1
features two pieces of code that do essentially the same thing: they
read in two numbers and use them as constructor parameters for a new Odds
object.
Avoid this unnecessary repetition with a different implementation:
- In
OddsTest1
, add a method calledrequestOdds
that takes no parameters. This effectful method reads in two integers, uses them to create anOdds
instance, and returns a reference to the new object. - Call
requestOdds
(twice).
A+ presents the exercise submission form here.
An Application with a GUI
Recap: An application program operates on some problem domain. The programmer creates a model of that domain. A user interface presents the model to the end user in some form, usually as images and/or text. Many user interfaces also let the user interact with the model, affecting the model’s state. Many applications have a graphical user interface (GUI) that consists of various visual elements and is typically displayed in a separate window.
A toy example of a model
The domain of the FlappyBug game is the two-dimensional game world; in the previous
chapter, we wrote the classes Bug
, Game
, and Obstacle
, which together model this
domain. Before we write a GUI for that model, let’s consider a simpler model and a GUI
that displays it.
Our example model consists of just a single class, Block
:
class Block(val size: Int, val location: Pos, val color: Color) {
override def toString = this.color + " block at " + this.location
}
Here’s a usage example:
import o1._, o1.block._import o1._ import o1.block._ val model = new Block(20, new Pos(300, 50), Gray)model: Block = Gray block at (300.0,50.0)
Now let’s write a GUI that displays a view of a single Block
object set against a
solid background.
Quick recap of custom methods
Remember the Person
class from the end of Chapter 2.4? We created several instances of
it, including a Superman person that was created with this command:
val superman = new Person("Clark") { def fly = "WOOSH!" }
That object is an instance of the person class but also has a fly
method. In just a
moment, we’ll find a more concrete use for defining a method on a single instance.
Displaying a GUI window: o1.View
Fortunately, programmers don’t have to build a GUI for every application from scratch, pixel by pixel. Instead, they find a software library suitable for their purposes.
There are many GUI libraries. One well-known one is called Swing; we’ll use Swing in Chapter 12.3 to work with buttons, text fields, and the like. Right now, we’ll instead use O1’s own GUI library, which is particularly useful for building small apps whose GUI consists of geometric shapes. (This library is also compatible with Swing.)
Package o1
provides a class called View
. As its name suggests, this class can be used
to display a view to an object that models the app’s domain. Let’s use the following
Block
object as our model:
val model = new Block(20, new Pos(300, 50), Gray)model: Block = Gray block at (300.0,50.0)
Now to create the View
object. This object represents a single GUI window that displays
a single object of type Block
.
val viewOfBlock = new View(model) { def makePic = { val background = rectangle(500, 500, Black) val blockPic = rectangle(model.size, model.size, model.color) background.place(blockPic, model.location) } }viewOfBlock: o1.gui.mutable.ViewFrame[Block] = view of Block
View
. As a constructor
parameter, we pass in a reference to what the view will display.View
object
can do, our particular View
instance has an effect-free method
makePic
that forms an image of the model. The image is of type
Pic
, and we form it using the Pic
tools familiar from earlier
chapters.Nothing graphical actually showed up when we entered that command in the REPL. This is
because we didn’t yet start up our GUI. Every View
object has an effectful method
that starts it. The following command displays our primitive GUI. Try it.
viewOfBlock.start()
Any View
object is capable of drawing itself onscreen and showing the image produced by
its makePic
method. So what we get is a window that contains the image of a block set
against a black background. The window also has the customary controls for minimizing and
closing it; that little bit of interactivity is automatically provided by View
without
us having to do anything about it.
An App
with a View
Let’s combine what we’ve covered in this chapter to create an app object that fires up a GUI view.
object BlockApp extends App {
val background = rectangle(500, 500, Black)
val block = new Block(20, new Pos(300, 50), Gray)
val viewOfBlock = new View(block, "An uninteractive test app") {
def makePic = {
val blockPic = rectangle(block.size, block.size, block.color)
background.place(blockPic, block.location)
}
}
viewOfBlock.start()
}
View
we construct.start
to make the GUI
visible onscreen. After that, all the code within our app object
has been executed but our application will keep running in its
separate window until the user signals otherwise.You can find this mini-app in the IntroApps project, under o1.block
.
It is ready to run.
Side note: makePic
as an abstract method
Question: what happens if I remove makePic
from the above app?
Answer: you’ll receive a compile-time error message informing
you that you can’t create a View
object that doesn’t have a
makePic
method.
Explanation: the View
class has been defined so that even though
it doesn’t actually implement a makePic
method, it requires
such an implementation to exist on all objects of type View
.
In more technical terms, makePic
is an abstract method in
class View
. More on abstract methods in Chapter 7.2.
That sure was a boring app. On to FlappyBug.
Assignment: FlappyBug (Part 3 of 16: The Beginnings of a GUI)
Introduction
Here’s some starter code for an app object. You can also find this code in FlappyBugApp.scala
.
package o1.flappy.gui
import o1._
import o1.flappy._
object FlappyBugApp extends App {
val sky = rectangle(ViewWidth, ViewHeight, LightBlue)
val ground = rectangle(ViewWidth, GroundDepth, SandyBrown)
val trunk = rectangle(30, 250, SaddleBrown)
val foliage = circle(200, ForestGreen)
val tree = trunk.onto(foliage, TopCenter, Center)
val rootedTree = tree.onto(ground, BottomCenter, new Pos(ViewWidth / 2, 30))
val scenery = sky.place(rootedTree, BottomLeft, BottomLeft)
val bugPic = Pic("ladybug.png")
def rockPic(obstacle: Obstacle) = circle(obstacle.radius * 2, Black)
// INSERT YOUR OWN CODE BELOW.
}
scenery
stores the end result. It isn’t necessary that you understand
precisely how the background was constructed, but you can find an
explanation in the optional material at the end of Chapter 2.5bugPic
stores an image that we’ll use to depict
the bug in the GUI.rockPic(5)
gives you a pebble and
rockPic(150)
an almighty chunk of rock.Reminder: please don’t forget to ask for a hint on our forums or at the labs if you’re stuck. You really don’t need to do everything alone.
We’ve split this task in two phases:
Phase 1 of 2: displaying the background
- Create a new object that will serve as the application’s domain
model. A
Game
object is a suitable model for our FlappyBug app.- A simple
new Game
will do to create the object; you don’t need to pass in any constructor parameters (Chapter 2.6). - Also create a variable that refers to the game object. Name it as you please.
- A simple
- Create a
View
that depicts theGame
in a GUI window.- Use "FlappyBug" as the title.
- The basic idea is very similar to the block
app, but now the domain model isnt a
Block
but aGame
object. When you create theView
, pass in two constructor parameters: a reference to yourGame
object and the window title as a string. - Define a variable that refers to your
View
object (likeviewOfBlock
earlier). Normally, you could name such a variable freely, but please use the namegui
here to ensure that automatic assessment works properly for this assignment.
- Write a
makePic
method on the view. For now, just have it return the background picture (scenery
). You’ll add the bug and the obstacle in just a bit. - Add a
start
command to start the GUI.
Now when your run the program, you should see a simple scenery in a window titled
"FlappyBug". The result isn’t much different from what we’ve already been able to produce
with the show
function. Yet.
Phase 2 of 2: placing the bug and the obstacle
Improve the makePic
method. It should construct and return an image of the game’s current
state by combining the scenery, the bug’s image, and an image of an obstacle so that the
bug and the obstacle have been place
d against the scenery in their correct positions.
A few hints:
- Use the images given as
scenery
andbugPic
. UserockPic
. - Given what you did in Chapter 2.6, the
Game
object should have two variables:obstacle
andbug
. You can use them to access the game’s bug and the obstacle. You’ll need to do so.- E.g.,
myGameObject.bug
- E.g.,
- You can access the positions of the game’s bug and obstacle via
their
pos
attributes (Chapter 2.6).- E.g.,
myGameObject.bug.pos
- E.g.,
- Place the bug and the obstacle at their initial locations as indicated
by their
pos
es.- Use something in the vein of
scenery.place(referenceToObstacle, positionOfObstacle)
. - Cf. the
Block
example above.
- Use something in the vein of
Run the program. You should see both the bug and the obstacle in their initial positions. They don’t move. Yet.
Submission form
A+ presents the exercise submission form here.
Optional activity: write prettier code
In the given FlappyBugApp
, the background image is
constructed with a sequence of commands that use multiple
temporary variables (e.g., foliage
, ground
), each of
which stores a partial image but has no purpose once the
final scenery has been constructed. We can make our code
a bit more elegant by structuring it differently. Try the
following.
Can you restructure that part of the code so that
scenery
isn’t a variable but a parameterless function that returns the scenic image? Like this:def scenery = { // place the code that constructs the image here }
In this solution, temporaries such as
foliage
become local variables as you define them withinscenery
’s body.How about changing
def scenery
toval scenery
in the modified solution? Does that work? Can you tell what difference this makes, if any?
Summary of Key Points
- A Scala application is launched through an app object. You can write
an app object by including
extends App
in the definition of a singleton object.- You may not need to write any methods on an app object. The commands in the object’s body are executed one by one when the application is launched.
- In a larger application, the app object activates other program components.
- The Scala library provides functions for reading keyboard input from the user through the text console.
- A typical application’s program code features a domain model and a user interface that depicts and operates on the model.
- There are various software libraries that help programmers build
graphical user interfaces.
- One GUI library is provided as part of
package
o1
. A key component of this library is theView
class, whose instances represent GUI windows.
- One GUI library is provided as part of
package
- Links to the glossary: app object; input, I/O; model, user interface, graphical user interface (GUI).
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 that has contributed to this ebook’s design. Thank you!
Weeks 1 to 13 of the ebook, including the assignments and weekly bulletins, have been written in Finnish and translated into English by Juha Sorva.
Weeks 14 to 20 are by Otto Seppälä. That part of the ebook isn’t available during the fall term, but we’ll publish it when it’s time.
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 programmed by Riku Autio, Jaakko Kantojärvi, Teemu Lehtinen, Timi Seppälä, Teemu Sirkiä, 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 have done 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 and Juha Sorva. Several of its key components are built upon Aleksi’s SMCL library.
The pedagogy of using tools from O1Library (such as Pic
) for simple graphical programming
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+ has been created by Aalto’s LeTech research group and is largely developed by students. The current lead developer is Jaakko Kantojärvi; many other students of computer science and information networks are also active on the project.
For O1’s current teaching staff, please see Chapter 1.1.
Additional credits appear at the ends of some chapters.
extends
keyword as “is a kind of”.