Статьи

Первые шаги в Scala для начинающих программистов, часть 1

The is the first of several Scala tutorials I’m creating for my Fall 2011 graduate Introduction to Computational Linguistics course at UT Austin, loosely based on similar tutorials that Katrin Erk created for teaching Python in a similar course. These tutorials assume no previous programming background, an assumption which is unfortunately still quite rare in the help-folks-learn-Scala universe, and which more or less necessitates the creation of these tutorials. The one exception I’m aware of is SimplyScala.

Note: if you already know a programming language, this tutorial will probably not be very useful for you. (Though perhaps some of the later ones on functional programming and such that I intend to do will be, so check back.) In the meantime, check out existing Scala learning materials I’ve listed in the links page for the course.

This tutorial assumes you have Scala installed and that you are using some form of Unix (if you use Windows, you’ll want to look into Cygwin). If you are having problems with this, you might try the examples by evaluating them in the code box on SimplyScala.

A (partial) starter tour of Scala expressions, variables, and basic types

We’ll use the Scala REPL for entering Scala expressions and seeing what the result of evaluating them is. REPL stands for read-eval(uate)-print-loop, which means it is a program that (1) reads the expressions you type in, (2) evaluates them using the Scala compiler, (3) prints out the result of the evaluation, and then (4) waits for you to enter further expressions.

Note: it is very important that you actually type the commands given below into the REPL. If you just read them over, it will in many cases look quite obvious (and it is), but someone who is new to programming will generally find many gaps in their understanding by actually trying things out. In particular, programming languages are very exact, so they’ll do exactly what you tell them to do — and you’ll almost surely mess a few things up, and learn from that.

In a Unix shell, type:

scala

You should see something like the following:

Welcome to Scala version 2.9.0.1 (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_26).
Type in expressions to have them evaluated.
Type :help for more information.

scala>

The scala> line is the prompt that the REPL is waiting for you to enter expressions. Let’s enter some.

scala> "Hello world"
res0: java.lang.String = Hello world

scala> 2
res1: Int = 2

The REPL tells us that the first is a String which contains the characters Hello world, that the second is an Int whose value is the integer 2. Strings and Ints are types — this is an easy but important distinction that sometimes takes beginning programmers a while to get used to. This allows Scala to know what to do when you want to use the numerical value 2 (an Int) or the character representing 2 (a String). For example, here is the latter.

scala> "2"
res3: java.lang.String = 2

Scala knows that different actions are afforded by different types. For example, Ints can be added to each other.

scala> 2+3
res4: Int = 5

Scala evaluates the result and prints the result to the screen. No surprise, the result is what you think it should be. With Strings, addition doesn’t make sense, but the + operator instead indicates concatenation of the strings.

scala> "Portis" + "head"
res5: java.lang.String = Portishead

So, if you consider the String “2″ and the String “3″, and use the + operator on them, you don’t get “5″ — instead you get “23″.

scala> "2" + "3"
res6: java.lang.String = 23

We can ask Scala to display the result of evaluating a given expression by using the print command.

scala> print ("Hello world")
Hello world
scala> print (2)
2
scala> print (2 + 3)
5

Note that the result is the action of printing, not a value with a type. For the last item, what happens is:

  1. Scala evaluates 2 + 3, which is 5.
  2. Scala passes that value to the command print.
  3. print outputs “5″

You can think of the print command as a verb, and its parameter (e.g. Hello world or 2) as its object.

We often need to store the result of evaluating an expression to a variable for later use (in fact, programming doesn’t get done with out doing this). Let’s do this trivially to start with, breaking down the print statement above into two steps.

scala> val x = 2 + 3
x: Int = 5

scala> print (x)
5

Here, x is a variable, which we’ve indicate by prefacing it with val, which indicates it is a fixed variable whose value cannot change.

You can choose the names for variables, but they must follow some rules.

  • Variable names may contain: letters, numbers, underscore
  • They must not start with a number
  • They must not be identical to one of the “reserved words”  that Scala has already defined, such as for, if, def, val, and var

Typical names for variables will be strings like x, y1, result, firstName, here_is_a_long_variable_name.

Returning to the variable x, we can now use it for other computations.

scala> x + 3
res12: Int = 8

scala> x * x
res13: Int = 25

And of course, we can assign the results of such computations to other variables.

scala> val y = 3 * x + 2
y: Int = 17

Notice that Scala knows that multiplication takes precedence over addition in such computations. If we wanted to override that, we’d need to use parentheses to indicate it, just like in basic algebra.

scala> val z = 3 * (x + 2)
z: Int = 21

Now, let’s introduce another type, Double, for working with real-valued numbers. The Ints considered thus far are whole numbers, which do fine with multiplication, but will lead to behavior you might not expect when used with division.

scala> 5 * 2
res12: Int = 10

scala> 7/2
res13: Int = 3

You probably expected to get 3.5 for the latter. However, because both 7 and 2 are Ints, Scala returns an Int — specifically it returns the number of times the denominator can go entirely into the numerator. To get the result you’d normally want here, you need to use Doubles such as the following.

scala> 7.0/2.0
res14: Double = 3.5

The a: Int portion of the line indicates that the variable a has the type Int. Here are some examples of other variables with different types.

scala> val b: Double = 3.14
b: Double = 3.14

scala> val c: String = "Hello world"
c: String = Hello world

Because Scala already knows these types, it is redundant to specify them in these cases. However, when expressions are more complicated, it is at times necessary to specify types explicitly.

Importantly, we cannot assign the variable a type that conflicts with the result of the expression. Here, we try to assign a Double value to a variable of type Int, and Scala reports an error.

scala> val d: Int = 6.28
<console>:7: error: type mismatch;
found   : Double(6.28)
required: Int
val d: Int = 6.28
^

In many cases, especially with beginning programming, you won’t have to worry about declaring the types of your variables. We’ll see situations where it is necessary as we progress.

In addition to variables declared with val, Scala allows variables to be declared with var — these variable can have their values reassigned. A few examples are the easiest way to see the difference.

scala> val a = 1
a: Int = 1

scala> a = 2
<console>:8: error: reassignment to val
a = 2
^

scala> var b = 5
b: Int = 5

scala> b = 6
b: Int = 6

You can think of a val variable as a sealed glass container into which you can look to see its value, but into which you cannot put anything new, and a var variable as an openable container that allows you both to see the value and to swap a new value in for the old one. We’re going to focus on using vals mostly as they ultimately provide many advantages when combined with functional programming, and because I hope to get you thinking in terms of vals rather than vars while you are starting out.

Functions

Variables are more useful when used in the context of functions in which a variable like x can be injected with different values by the user of a function. Let’s consider converting degrees Fahrenheit to Celsius. To convert 87, 92, and 100 from Fahrenheit to Celcius, we could do the following.

scala> (87 - 32) * 5 / 9.0
res15: Double = 30.555555555555557

scala> (92 - 32) * 5 / 9.0
res16: Double = 33.333333333333336

scala> (100 - 32) * 5 / 9.0
res17: Double = 37.77777777777778

Obviously, there is a lot of repetition here. Functions allow us to specify the common parts of such calculations, while allowing variables to specify the parts that may be different. In the conversion case, the only thing that changes is the temperature reading in Fahrenheit. Here’s how we declare the appropriate function in Scala.

scala> def f2c (x: Double) = (x - 32) * 5/9.0
f2c: (x: Double)Double

Разбивая это, мы имеем:

  • def — ключевое слово Scala, указывающее, что функция определяется
  • f2c — имя, данное функции
  • (x: Double) — параметр функции, представляющий собой переменную с именем x типа Double
  • (x — 32) * 5 / 9.0 — это тело функции, которое примет значение, данное пользователем функции, вычтет из него 32, а затем умножит результат на пять девяток

Использовать функцию просто — дайте имя функции, а затем укажите значение, которое вы передаете в функцию в скобках.

scala> f2c(87)
res18: Double = 30.555555555555557

scala> f2c(92)
res19: Double = 33.333333333333336

scala> f2c(100)
res20: Double = 37.77777777777778

И так далее. Для каждого вызова функция вычисляет выражение для x, равное значению, переданному в функцию. Теперь нам не нужно перепечатывать все обычные вещи снова и снова.

Функции могут иметь несколько аргументов. Например, следующее — это функция, которая берет два целых числа, возводит в квадрат каждое из них и затем добавляет квадратные значения.

scala> def squareThenAdd (x: Int, y: Int) = x*x + y*y
squareThenAdd: (x: Int, y: Int)Int

scala> squareThenAdd(3,4)
res21: Int = 25

Что действительно так же, как делать это явно.

scala> 3*3 + 4*4
res22: Int = 25

Важным аспектом функций является то, что все переменные должны быть связаны. Если нет, мы получаем ошибку.

scala> def badFunctionWithUnboundVariable (x: Int) = x + y
<console>:8: error: not found: value y
def badFunctionWithUnboundVariable (x: Int) = x + y

Функции могут делать гораздо более сложные и интересные вещи, чем то, что я показал здесь, к которому мы вернемся в другом уроке.

Редактирование программ в текстовом редакторе и запуск их в командной строке

REPL очень полезен для опробования выражений Scala и просмотра их оценки в режиме реального времени, но фактическая разработка программы осуществляется путем написания текстового файла, который содержит ряд выражений, которые выполняют интересное поведение. Делать это просто. Откройте текстовый редактор (см. Страницу ссылок на курс для некоторых предложений) и укажите следующую строку в качестве первой строки, ничего больше.

print ("Hello world")

Сохраните этот файл как HelloWorld.scala , убедившись, что он сохраняется только в виде текста. Затем в оболочке Unix перейдите в каталог, в котором сохранен этот файл, и введите следующее.

$ scala HelloWorld.scala

Вы увидите, что Hello world выводится, но приглашение Unix заклинило сразу после него. Вы, возможно, ожидали, что он распечатает и затем оставит приглашение Unix на следующей строке; однако в команде print или в строке, которую мы попросили напечатать , нет ничего, что указывало бы на необходимость использования новой строки. Чтобы это исправить, вернитесь в редактор и измените строку следующим образом.

print ("Hello world\n")

Когда вы запускаете это, ваше приглашение Unix появляется в строке, следующей за Hello world . Такие символы, как ‘ \ n ‘, являются метасимволами, которые указывают выходные данные, отличные от стандартных символов, таких как буквы, цифры и символы.

Теперь вы могли бы достичь того же результата, написав.

println ("Hello world")

Функции print и println одинаковы, за исключением того, что последний всегда добавляет новую строку в конце своего вывода — то, что часто желательно и, таким образом, упрощает жизнь программиста. Тем не менее, нам все еще часто приходится использовать символ новой строки и другие символы при выводе строк. Например, поместите следующее в HelloWorld.scala и запустите его снова.

println("Hello world\nHere is a list:\n\t1\n\t2\n\t3")

Из вывода должно быть совершенно ясно, что означает « \ t ». Обратите внимание, что нет необходимости ставить ‘ \ n ‘ после последних 3, потому что println использовался вместо print .

Это тривиальная программа, но в целом они, как правило, становятся достаточно сложными. Здесь комментарии кода пригодятся. Вы можете указать, что строка должна игнорироваться Scala в качестве комментария, используя две косые черты. Комментарии могут использоваться, чтобы указать, кто является автором программы, какая лицензия на нее, документация, чтобы помочь другим (и вашей будущей личности) понять, что делают различные части кода, и комментировать строки кода, которые вы надеваете не хотите стирать, но вы хотите временно неактивны.

Вот немного более длинная программа с комментариями и определениями функций и использованием этих функций наряду с печатью.

// Author: Jason Baldridge ([email protected])

// This is a trivial program for students learning to program with Scala.

// This is a comment. The next line defines a function that squares
// its argument.
def sq (x: Int) = x * x

// The next line prints the result of calling sq with the argument 3.
println("3 squared = " + sq(3))

// The next line is commented out, so even though it is a valid Scala
// expression, it won't be evaluated by Scala.
// println("4 squared = " + sq(4))

// Now, we define a function that uses the previously defined sq
// (rather than using x*x and y*y as before).
def squareThenAdd (x: Int, y: Int) = sq(x) + sq(y)

// Now we use it.
println("Squaring 3 and 4 and adding the results = "
        + squareThenAdd(3,4))

Сохраните это как ScalaFirstStepsPart1.scala и запустите его с исполняемым файлом Scala. Вы должны увидеть следующие результаты.

$ scala ScalaFirstStepsPart1.scala
3 squared = 9
Squaring 3 and 4 and adding the results = 25

Выглядит хорошо, правда? Но что происходит с этими печатными заявлениями? Ранее мы видели, что 2 + 3 оценивается как 5, но что «2 ″ +» 3 ″ оценивается как «23», и здесь мы использовали + для String и Int. Разве это не должно привести к ошибке? Scala автоматически конвертирует Int в строку для нас, что значительно упрощает вывод результатов. Это означает, что мы можем сделать что-то вроде следующего (вернуться к использованию REPL).

scala> println("August " + 22 + ", " + 2011)
August 22, 2011

That seems a bit pointless because we could have just written “August 22, 2011″, but here’s an example where it is a bit more useful: we can name tomorrow’s day by using an Int for today’s and adding one to it.

scala> val dayOfTheMonthToday = 22
dayOfTheMonthToday: Int = 22

scala> println("Today is August " + dayOfTheMonthToday + " and tomorrow is August " + (dayOfTheMonthToday+1))
Today is August 22 and tomorrow is August 23

Note that the (dayOfTheMonthToday+1) part is actually Int addition, and the result of that is converted to a String that is concatenated with the rest of the string. This example is still fairly contrived (and obviously doesn’t deal with the end of the month and all), but this autoconversion gets used a lot when you start working with more complex programs. And, perhaps even more obviously, we might reasonably want to add an Int and a Double.

scala> 2 + 3.0
res27: Double = 5.0

Here, the result is a Double, since it is the more general type than Int. This kind of autoconversion happens a lot, and often you won’t even realize it is going on.

Another thing to note is that the last print statement went over multiple lines. We’ll be seeing more about what the rules are for statements that run over multiple lines, but this example shows perhaps the easiest one to remember: when you open a parenthesis with “(“, you can keep on going multiple lines until its partner “)” is encountered. So, for example, we could have done the following very spread-out statement.

println(
  "Squaring 3 and 4 and adding the results = "

  +

  squareThenAdd(3,4)
)

As well as being used for boxing in a bunch of code that is an argument to a function, as above, parentheses are quite useful for indicating the order of precedence of combining multiple items, such as indicating that an addition should be done before a multiplication, or that an Int addition should be done before a String concatenation, as shown earlier in the tutorial. Basically, parentheses are often optional, but if you can’t remember what the default rules for expressions being grouped together are, then you can group them explicitly with parentheses.

From http://bcomposes.wordpress.com/2011/08/22/first-steps-in-scala-for-first-time-programmers-part-1/