
Продолжил работу по созданию учебных материалов по Scala.
Выкладываю черновую версию одной из глав.
Сопоставление по образцу (pattern matching). Начало.
В качестве вариантов перевода слова match с английского языка словарь Lingvo приводит следующие варианты:
3)
а) подбирать(под пару, под стать; по цвету, форме)
to be well / ill matched — быть хорошо / плохо подобранным; хорошо / плохо сочетаться
...
б) подходить, соответствовать (под пару; по цвету, форме)
dress with a hat to match — платье с удачно подобранной шляпкой
...
В языке Scala ключевое слово match также используется для того, чтобы подбирать или находить соответствие:
e match {
case p1 => e1
// ...
case pn => en
}
Где e, e1, en - некие выражения (expression) , а p1, pn - шаблоны (pattern) по которыми мы пытаемся найти соответствия.
Такая конструкция напоминает обычный switch / case из языков программирования Java или С/C++.
Сравним примеры:
| Scala | Java | C |
a match { case 0 => print("zero") case 1 => print("one") case _ => print("too much") } |
switch (a) { case 0: System.out.print("zero"); break; case 1: System.out.print("one"); break; default: System.out.print("too much"); break; } |
switch (a) { case 0: printf("zero"); break; case 1: printf("zero"); break; default: printf("too much"); break; } |
Tips. В Scala можно указать сразу несколько условий в case
a match { case 1 | 2 | 3 => println("один, два, или три") }
Рассмотрим подробнее наш пример. В нем объект a "сравнивается" с разными шаблонами.
Для a равному 0, будет будет исполнен код print("zero").
Для а равному 1, будет выполнено print("one").
Для остальных случаев в Scala есть символ-джокер. Он обозначается символом подчёркивания _ . Таким образом case _ сработает в том случае, если другие шаблоны не подошли. Похожее поведение в Java реализуется с помощью слова default .
Tips. Символ-джокер, он же wild-card. По аналогии с карточными играми, например покером, в котором такая "дикая" карта (wild card) может стать другой картой по желанию игрока (например "пятым" тузом).
Простыми примерами таких символов может служить знак * в Unix/Windows скриптах или символ % в SQL-ном LIKE. В Scala wild-card "_" используется не только в case-ах, но и в других конструкциях.
Важной особенностью языка Scala заключается в том, что в отличие от switch/case С или Java, match умеет работать со строками. Более того match можно использовать для сопоставление объектов так называемых case классов, работу с которыми бы подробно рассмотрим в следующей главе.
Tips. Eсли мы попробуем например декомпилировать (javap -c ) указанный выше пример, мы обнаружим что Scala компилятор создаёт не плохой байт-код, аналогичный тому если бы мы писали на Java используя switch/case.
Итак, попробуем использовать полученные знания.
Представим, что у нас есть некоторый набор команд К. Например К = { F, +, - } :
F → пойти прямо
+ → повернуть направо
- → повернуть налево
Напишем программу, которая будет исполнять эти команды:
// полный текст программы будет приведён в конце главы cmd foreach (i => i match { case 'F' => { g.setColor(Color.red) val x2 = (x + length*cos(α)).toInt val y2 = (y + length*sin(α)).toInt g.drawLine(x, y, x2,y2) x = x2; y = y2; } case '+' => α += 0.5*Pi case '-' => α -= 0.5*Pi } )
Здесь cmd - обозначает строку с последовательностью различных команд. Например "F−F++F−F", будет означать - прямо, налево, прямо, направо, направо, прямо, налево, прямо.
Значение переменных x, y, α будут задавать текущие координаты и направление движения нашей системы.
Используем ещё раз силу match и применив какую-нибудь простую L-system-у, напишем программу которая будет рекурсивно создавать нам красивую последовательность команд.
Например, пусть будет следующее правило преобразований:
F → F+F-F-FF+F+F-F
+ → +
- → -
На языке Scala это можно записать следующим образом:
def next(x:String): String = { var result = new StringBuilder() x.foreach(p=> p match { case 'F' => result ++= "F+F-F-FF+F+F-F" case i => result +=i } ) result.toString }
Вызывая рекурсивно несколько раз данную функцию и направив полученную цепочку команд для отрисовки изображения, мы можем получить довольно занятные изображения.
Исходный код. В качестве эксперимента можно таким образом нарисовать салфетку серпинского или снежинку коха.
// Использование программного кода только в учебных целях. // Автор: Виталий Л. vit@programmisty.com import java.awt.Color import java.awt.image.BufferedImage import java.io.File import javax.imageio.ImageIO import scala.math._ object FractalLab { def main(s:Array[String]) { // Пользуемся только стандартным JavaAPI. Никакие процессингы тут не нужны. Учимся все-таки... val w = 512; val h = w; // квадрат 512x512 val image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB) val g = image.getGraphics() var α = 0.0; var x = 0; var y = h/2; // координаты. внимание, α - альфа. сохраняйте исходник в UTF-8. def draw(cmd:String, length: Int) { // Используем match cmd foreach (i => i match { case 'F' => { g.setColor(Color.red) val x2 = (x + length*cos(α)).toInt val y2 = (y + length*sin(α)).toInt g.drawLine(x, y, x2,y2) x = x2; y = y2; } case '+' => α += 0.5*Pi case '-' => α -= 0.5*Pi } ) } // вспомогательная функция генератор цепочки команд def generate(s:String, level:Int, f:(String)=>String ):String = { if (level > 0) generate(f(s), level-1, f) else s } // функция создающая новые команды, на базе старых def next(x:String): String = { var result = new StringBuilder() // используем match x.foreach(p=> p match { case 'F' => result ++= "F+F-F-FF+F+F-F" case i => result +=i } ) result.toString } // рисуем с шагов 2 пикселя цепочку команда 4 поколения. draw(generate("F", 4, next), 2) // сохраняем в файл ImageIO.write(image, "png", new File("koch.png")) } }
Продолжение следует...
■


