Продолжил работу по созданию учебных материалов по 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 | |||
|
|
|
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")) } } |
Продолжение следует…