9 апреля, 4:50
Анонимные функции
Анонимные функции есть практически во всех современных языках программирования, за исключением Java, в которую анонимные функции, возможно, будут добавлены в 8 версии.
Анонимная функция — это функция без имени на которую есть только ссылка. Для определения анонимной функции в Scala используется запись =>, которая читается как «переходит в». В левой части записи указываются параметры функции, а в правой части определяют ее тело:
В этом примере была определена анонимная функция принимающая целое число и возвращающая число возведенное во вторую степень.
Анонимная функция может принимать несколько параметров:
А может быть без параметров:
Функция которая принимает в качестве аргумента другую функцию, называется функцией высшего порядка. Функции высшего порядка активно используются в Scala Collection API:
Как было сказанно выше, в Java (а значит и в JVM) нет анонимных функций. Любая анонимная функция в Scala во время компиляции трансформируется в объект анонимного класса с методом apply, который рассматривался в предыдущей заметке:
Разработчики Scala добавили синтаксический сахар везде где это только возможно и анонимные функции не стали исключением. У анонимных функций есть краткий синтаксис:
Функция высшего порядка reduceLeft принимает на вход функцию с двумя параметрами, которая рекурсивно применяется к каждому элементу последовательности. В первом параметре находится результат выполнения функции из предыдущего шага, а во втором текущий элемент коллекции.
Запись (_ * _) является короткой формой определения функции с двумя параметрами, которая возвращает произведение аргументов.
На первый взгляд такая запись выглядит странно, но поверьте, через некоторое время вы будете не понимать, как раньше писали код без такого удобного шотката.
Анонимная функция — это функция без имени на которую есть только ссылка. Для определения анонимной функции в Scala используется запись =>, которая читается как «переходит в». В левой части записи указываются параметры функции, а в правой части определяют ее тело:
val sqr= (i: Int) => i * i
В этом примере была определена анонимная функция принимающая целое число и возвращающая число возведенное во вторую степень.
Анонимная функция может принимать несколько параметров:
val r = (s:String, times: Int) => s * times
println(r("*", 5)) // *****
println(r("-", 3)) // ---
А может быть без параметров:
val curTime = () => java.util.Calendar.getInstance().getTime() println(curTime())
Функция которая принимает в качестве аргумента другую функцию, называется функцией высшего порядка. Функции высшего порядка активно используются в Scala Collection API:
var mySortFunc = (name1: String, name2: String) => name1 < name2
var sortedFriends = List("Dmitry", "Konstantin", "Arkady", "Sergey")
.sort(mySortFunc)
println(sortedFriends) // List(Arkady, Dmitry, Konstantin, Sergey)
val filtered = sortedFriends.filter(s => s.length > 6)
println(filtered) // List(Konstantin)
Как было сказанно выше, в Java (а значит и в JVM) нет анонимных функций. Любая анонимная функция в Scala во время компиляции трансформируется в объект анонимного класса с методом apply, который рассматривался в предыдущей заметке:
new Function1[Int, Int] {
def apply(x: Int): Int = x + 1
}
Разработчики Scala добавили синтаксический сахар везде где это только возможно и анонимные функции не стали исключением. У анонимных функций есть краткий синтаксис:
val numbers = Array(1, 2, 3, 4, 5)
val prod = numbers.reduceLeft[Int](_*_)
println("The factorial of five is " + prod) // 120
Функция высшего порядка reduceLeft принимает на вход функцию с двумя параметрами, которая рекурсивно применяется к каждому элементу последовательности. В первом параметре находится результат выполнения функции из предыдущего шага, а во втором текущий элемент коллекции.
Запись (_ * _) является короткой формой определения функции с двумя параметрами, которая возвращает произведение аргументов.
На первый взгляд такая запись выглядит странно, но поверьте, через некоторое время вы будете не понимать, как раньше писали код без такого удобного шотката.
2 комментария20 марта, 3:46
Apply, Update и компаньон
Методы apply и update имеют в Scala особый смысл:
Weather("London") во время компиляции будет заменен на вызов Weather.apply("London"), а temperature(city) станет temperature.apply(city).
Этим объясняется почему к элементам массива в Scala нужно обращаться через arr(index), вместо arr[index]. Компилятор автоматически преобразует вызов в <array instance>.apply(index).
Внутри JMV, partially applied function оказывается объектом с методом apply.
Этот синтаксис был добавлен в язык для реализации концепции объекта компаньона и паттерна factory method. Компаньон — это синглетон, который определен в одном файле с каким-либо классом и имеет точно такое же имя. Обратите внимание, что для создания объекта класса Book не нужно указывать ключевое слово new:
Метод update:
names(0) = "Janet" — это короткая форма для names.update(0, "test").
Во всем остальном, методы apply и update ничем не отличается от других методов, которые можно наследовать, переопределять и явно вызывать.
object Weather {
val temperature = scala.collection.mutable.Map[String, Int]()
def apply(city: String) = temperature(city)
def update(city: String, temp: Int) = temperature(city) = temp
}
Weather("London") = 20
Weather("New York") = 17
println("Current weather")
println(" - London: %s" format Weather("London")) // 20
println(" - New York: %s" format Weather("New York")) // 17
Weather("London") во время компиляции будет заменен на вызов Weather.apply("London"), а temperature(city) станет temperature.apply(city).
Этим объясняется почему к элементам массива в Scala нужно обращаться через arr(index), вместо arr[index]. Компилятор автоматически преобразует вызов в <array instance>.apply(index).
Внутри JMV, partially applied function оказывается объектом с методом apply.
Этот синтаксис был добавлен в язык для реализации концепции объекта компаньона и паттерна factory method. Компаньон — это синглетон, который определен в одном файле с каким-либо классом и имеет точно такое же имя. Обратите внимание, что для создания объекта класса Book не нужно указывать ключевое слово new:
class Book(title: String, author: String) {
override def toString = "%s is author of %s ".format(author, title)
}
object Book {
def apply(title: String, author: String) = new Book(title, author)
}
val book = Book("Programming in Scala: A Comprehensive Step-by-step Guide", "Martin Odersky")
println(book)
Метод update:
val names = Array[String]("John", "Jane", "Jordan") // -> Array.apply("John", "Jane", "Jordan")
val name = names(0) // names.apply(0)
names(0) = "Janet"
names.foreach(println _) // Janet, Jane, Jordan
names(0) = "Janet" — это короткая форма для names.update(0, "test").
Во всем остальном, методы apply и update ничем не отличается от других методов, которые можно наследовать, переопределять и явно вызывать.
19 марта, 5:15
Частичное применение функции
При вызове любой функции, к ней применяются аргументы. Например, функция сложения трех целых чисел может быть определена следующим образом:
В этом примере, к функции sum применяются аргументы (1, 2, 3).
Scala позволяет зафиксировать значения некоторых параметров с помощью техники частичного применения функции (partially applied function). Для этого, в Scala используется специальный символ _:
Переменная addToFive содержит ссылку на функцию у которой первый аргумент равен 5, второй равен 0, а для третьего аргумента задан только тип Int.
Параметры могут быть зафиксированы в любом порядке. Например, первый и последний:
Если нужна ссылка на функцию, то скобки опускаются:
Типичный пример использования:
Техники каррирования функций и частичного применения являются популярными подходами в функциональном стиле программирования. На первый взгляд эти два подхода могут показаться идентичными, но между ними существует разница:
def sum(a: Int, b: Int, c: Int) = a + b + c val x = sum(1, 2, 3) println(x) // 6
В этом примере, к функции sum применяются аргументы (1, 2, 3).
Scala позволяет зафиксировать значения некоторых параметров с помощью техники частичного применения функции (partially applied function). Для этого, в Scala используется специальный символ _:
val addToFive = sum(5, 0, _: Int) println(addToFive(1)) // 6
Переменная addToFive содержит ссылку на функцию у которой первый аргумент равен 5, второй равен 0, а для третьего аргумента задан только тип Int.
Параметры могут быть зафиксированы в любом порядке. Например, первый и последний:
val addWithTwo = sum(_:Int, 2, _:Int) println(addWithTwo(1, 3)) // 6
Если нужна ссылка на функцию, то скобки опускаются:
val mySum = sum _
Типичный пример использования:
import scala.math._ val numbers = List[Double](-1.3, 2.6, 3.7, 4.2, 5.0) println(numbers.map(round _)) // List(-1, 3, 4, 4, 5 println(numbers.map(abs _)) // List(1.3, 2.6, 3.7, 4.2, 5.0) numbers.foreach(println) // the same as println _
Техники каррирования функций и частичного применения являются популярными подходами в функциональном стиле программирования. На первый взгляд эти два подхода могут показаться идентичными, но между ними существует разница:
- Каррирование — это преобразование функции с множеством аргументов к последовательной цепочке функций принимающих один аргумент.
- Частичное применение — это создание функции на базе существующей, у которой определены некоторые параметры.
10 марта, 20:49
Синтаксические возможности с использованием каррирования
Каррирование — это преобразование функции принимающей несколько параметров, к цепочке функций принимающих по одному параметру. Каррирование является еще одной техникой повторного использования кода.
Пример сложениях двух чисел на JavaScript с применением техники каррирования. Найдем сумму 2 + 8:
В Scala функция которая возвращает сумму двух чисел выглядит так:
Версия после каррирования:
Синтаксический сахар добавленый в Scala для каррирования, предоставляет разработчику богатые возможности в улучшении читаемости кода.
Например, вызов каррированной функций applyDiscount, которая расчитывает скидку для покупателя на товар может выглядеть так:
Параметры каррированной функции могут использовать в качестве значения по-умолчанию производные значение от предыдущих параметров:
Каррирование можно использовать в varargs функциях:
Говоря простым языком: вызвали функцию с одним параметром, получили новую функцию. Вызвали новую функцию со следующим параметром из списка и опять получили функцию принимающую только один параметр. Функции возвращаются до тех пор, пока не будет получено итоговое значение.
Пример сложениях двух чисел на JavaScript с применением техники каррирования. Найдем сумму 2 + 8:
function curry_sum(x){
return function(y){
return x + y;
}
}
var a = curry_sum(2); // a - это ссылка на функцию
a(8); // => 10
В Scala функция которая возвращает сумму двух чисел выглядит так:
scala> def sum(x: Int, y: Int) = x + y scala> sum(2, 8) res0: Int = 10
Версия после каррирования:
scala> def curry_sum(x: Int)(y: Int) = x + y scala> curry_sum(2)(8) res1: Int = 10
Синтаксический сахар добавленый в Scala для каррирования, предоставляет разработчику богатые возможности в улучшении читаемости кода.
Например, вызов каррированной функций applyDiscount, которая расчитывает скидку для покупателя на товар может выглядеть так:
def applyDiscount(price: Double)(discountPct: Double) =
price - (price * discountPct / 100)
// клиентский код
val total = applyDiscount(200) {
val pstmt = conn.prepareStatement("SELECT discount FROM discounts WHERE user_id = ?")
pstmt.setInt(1, user_id)
val rs = pstmt.executeQuery()
rs.next
rs.getDouble(1)
}
Параметры каррированной функции могут использовать в качестве значения по-умолчанию производные значение от предыдущих параметров:
def sum(x: Int, y: Int = x * 2) = x + y // неправильная конструкция
def sum(x: Int)(y: Int = x * 2) = x + y // правильная конструкция
def touch( file: java.io.File )( modDate: Long = file.lastModified ) { ... }
Каррирование можно использовать в varargs функциях:
def foo(as: Int*)(bs: Int*)(cs: Int*) = as.sum * bs.sum * cs.sum println(foo(1, 2, 3)(4, 5)(6, 7, 8, 9)) // 1620
Говоря простым языком: вызвали функцию с одним параметром, получили новую функцию. Вызвали новую функцию со следующим параметром из списка и опять получили функцию принимающую только один параметр. Функции возвращаются до тех пор, пока не будет получено итоговое значение.
24 февраля, 9:23
Класс Either
Контейнер Option возвращает результат обернутый в объект класса Some или объект None, который означает отсутствие результата. Этого может быть мало, если необходима дополнительная информация, а не только тот факт, что результат отсутствует. Например, краулер который скачивает веб-страницы, может возвращать HTML в случае Status: 200, что будет является положительным результатом, а может вернуть отличный от 200 статус код и это будет означать неудачный запрос.
В библиотеке Scala есть класс Either, который представляет два возможных значения: объект класса Left (по соглашению означает неудачу) и Right.
Псевдо-краулер:
Результат:
URL: http://ya.ru/scala returns 404 code
URL: http://google.com/scala returns 404 code
В библиотеке Scala есть класс Either, который представляет два возможных значения: объект класса Left (по соглашению означает неудачу) и Right.
def toBool(s: String): Either[String, Boolean] = {
try {
Right(s.toBoolean)
}
catch {
case e => Left("Value '%s' is not a boolean".format(s))
}
}
var res0 = toBool("TRUE")
var res1 = toBool("false")
var res2 = toBool("yes")
println(res0) // Right(true)
println(res1) // Right(false)
println(res2) // Left(Value 'yes' is not a boolean)
println
println(res0.isRight) // true
println(res1.isLeft) // false
println(res2.isLeft) // true
Псевдо-краулер:
def fetchUrl(pageUrl: String): Either[Int, String] = {
val url = new URL(pageUrl)
val conn: HttpURLConnection = url.openConnection.asInstanceOf[HttpURLConnection]
conn.getResponseCode match {
case 200 => Right("// fetched content of html page //")
case code => Left(code)
}
}
var sites = List("http://ya.ru",
"http://google.com",
"http://ya.ru/scala",
"http://google.com/scala"
)
var sites404 = sites map (url => (url, fetchUrl(url))) filter(_._2.isLeft)
sites404.foreach {
r => println("URL: %s returns %d code".format(r._1, r._2.left.get))
}
Результат:
URL: http://ya.ru/scala returns 404 code
URL: http://google.com/scala returns 404 code
20 февраля, 8:31
Задача про математиков
Решение на Scala задачи про математиков:
Шаг 2. Найти номер троллейбуса
Сгруппируем получившиеся списки по суммам элементов:
Шаг 3. Найти дубликаты
После того как второй математик нашел возможные номера троллейбуса, ему потребовалось еще одно условие, значит существует повторяющаяся сумма, найдем ее:
Номер троллейбуса оказался 14, возможные возраста 1, 5, 8 или 2, 2, 10
Шаг 4. Найти младшего
Ответ
Возраст детей: 1, 5, 8
Встречаются два математика, не видевшиеся много лет.Шаг 1. Разложить 40 на множители
- Дети-то есть?
- Да, трое сыновей.
- И сколько же им лет?
- Ну ты же математик, попробуй сам рассчитать. Если перемножить их возрасты, то получится 40.
- Недостаточно информации.
- Сумма их возрастов равна номеру вон того троллейбуса.
- Опять недостаточно информации.
- Младший — рыжий.
- Теперь всё понятно!
Назовите возраст детей.
val m = for (x <- 1 to 40 toList;
y <- x to 40 toList;
z <- y to 40 toList;
if x * y * z == 40
) yield(x, y, z)
// List((1,1,40), (1,2,20), (1,4,10), (1,5,8), (2,2,10), (2,4,5))
Шаг 2. Найти номер троллейбуса
Сгруппируем получившиеся списки по суммам элементов:
val g = m.groupBy(e => e._1 + e._2 + e._3) // Map(42 -> List((1,1,40)), 14 -> List((1,5,8), (2,2,10)), 11 -> List((2,4,5)), 23 -> List((1,2,20)), 15 -> List((1,4,10)))
Шаг 3. Найти дубликаты
После того как второй математик нашел возможные номера троллейбуса, ему потребовалось еще одно условие, значит существует повторяющаяся сумма, найдем ее:
val k = g.filter(_._2.length >= 2) // Map(14 -> List((1,5,8), (2,2,10)))
Номер троллейбуса оказался 14, возможные возраста 1, 5, 8 или 2, 2, 10
Шаг 4. Найти младшего
val ages = k.values.flatten.filter(age => age._1 < age._2) // List((1,5,8))
Ответ
Возраст детей: 1, 5, 8
20 февраля, 8:00
App vs. Application trait
Точкой входа в Scala программу является метод main. Типичная реализация Hello World:
Исключительно для демонстрационных целей в стандартную библиотеку добавили trait Application, который позволял быстро обернуть объект в исполняемую программу. Application предоставлял реализацию метода main, а пользовательский код находящийся внутри object MyApp extends Application { .....} после компиляции попадал в static initialization blocks класса. Такой подход внес следующие ограничения:
App наследуется от DelayedInit:
Объект унаследованный от App рекомендуется использовать как точку входа в программу.
object MyApp {
def main(args: Array[String]): Unit = {
// ...
}
}
Исключительно для демонстрационных целей в стандартную библиотеку добавили trait Application, который позволял быстро обернуть объект в исполняемую программу. Application предоставлял реализацию метода main, а пользовательский код находящийся внутри object MyApp extends Application { .....} после компиляции попадал в static initialization blocks класса. Такой подход внес следующие ограничения:
- невозможно обратиться к параметрам командной строки,
т. к. на момент выполнения кода, метод main еще не был вызван внутри JVM. - невозможно работать с другими потоками
object MyApp extends App {
print("Args: ")
args foreach { print _ }
}
// scala MyApp 1 2 3 4 5
// Args: 12345
App наследуется от DelayedInit:
Classes and traits inheriting the DelayedInit marker trait will have their initialization code rewritten as follows.[Code] becomes delayedInit([Code]) Initialization code comprises all statements and all value definitions that are executed during initialization.Теперь клиентский код вызывается в методе App.main, а не в момент инициализации класса. Так же в App доступны аргументы командной строки через метод args.
Объект унаследованный от App рекомендуется использовать как точку входа в программу.
19 февраля 2012, 12:35
Аннотация Elidable
Вызовы методов отмеченных аннотацией elidable игнорируются компилятором Scala (начиная с версии 2.8). Параметр компиляции -Xelide-below задает уровень компиляции, словно уровень логгирования объекта Logger.

Уровни elidable соответствуют константам класса java.util.logging.Level:
Если откомпилировать данный код с параметром scalac -Xelide-below 900, то вызов printInfoMsg будет пропущен.
Методы assert и assume из объекта Predef промаркированы как elidable и их вызовы удаляются при уровне компиляции выше ASSERTION

Уровни elidable соответствуют константам класса java.util.logging.Level:
- ALL
- FINEST
- FINER
- FINE
- CONFIG
- INFO
- WARNING
- SEVERE
- ASSERTION
- OFF
import scala.annotation.elidable
import scala.annotation.elidable._
object ElidableTest {
@elidable(INFO)
def printInfoMsg(msg: String) {
println("MSG: " + msg);
}
def main(args: Array[String]): Unit = {
printInfoMsg("Test")
}
}
Если откомпилировать данный код с параметром scalac -Xelide-below 900, то вызов printInfoMsg будет пропущен.
Методы assert и assume из объекта Predef промаркированы как elidable и их вызовы удаляются при уровне компиляции выше ASSERTION
19 февраля 2012, 9:56
Кортежи в Scala

Кортеж (Tuple) последовательность из фиксированного числа элементов, в том числе отличных по типу:
scala> var p = Tuple5("John Doe", 100, 12.3, true, {})
Tuple5 — это case class для хранения пяти элементов. Стандартная библиотека Scala предоставляет 21 класс для хранения от 2 до 22 элементов: Tuple2 — Tuple22.
Разработчику нет необходимости считать количество элементов, создавать кортежи можно с помощью синтаксического сахара:
scala> var p = ("John Doe", 100, 12.3, true, {})
p: (java.lang.String, Int, Double, Boolean, Unit) = (John Doe,100,12.3,true,())
Кортеж состоящий из двух элементов можно создать с помощью метода ->:
scala> 18 -> 97 res1: (Int, Int) = (18,97) scala> 18 -> 97 -> 22 res2: ((Int, Int), Int) = ((18,97),22)
Кортежи индексируются с единицы, доступ к первому элементу осуществляется через ._1:
println(p._1) // John Doe println(p._2) // 100 println(p._3) // 12.3 println(p._4) // true println(p._5) // ()
Кортеж можно распаковывать в переменные:
scala> val (x, y, z) = (1, 2, 3) x: Int = 1 y: Int = 2 z: Int = 3
или так:
val (_, y, _) = (1, 2, 3) y: Int = 2
Кортеж может быть возвращаемым значением метода:
scala> def x(s: String) = (s.toLowerCase, s.toUpperCase)
scala> val (lower, upper) = x("Hello World")
lower: java.lang.String = hello world
upper: java.lang.String = HELLO WORLD
Поскольку Tuple в отличие от коллекций List, Set, Map может содержать элементы любых типов, применять операции map, filter, filter, foreach, etc можно только после того как кортеж будет приведен к типу Iterator[Any]:
scala> ("John Doe", 10, true).productIterator foreach { println _ }
John Doe
10
true
scala> ("John Doe", 10, true).productIterator map { _.toString } mkString (";")
res3: String = John Doe;10;true
Кортеж в Scala является неизменяемой стуктурой данных: нельзя добавить элементы к существующему кортежу, нельзя изменять значения элементов созданного кортежа. Заглянем в код библиотеки Scala, что бы понять почему так произодит:
// from file Product2.scala
trait Product2 /*.... */ {
/** A projection of element 1 of this Product.
* @return A projection of element 1.
*/
def _1: T1
/** A projection of element 2 of this Product.
* @return A projection of element 2.
*/
def _2: T2
}
Опуская детали, видно, что каждый элемент кортежа объявлен как метод, а не переменная.
В языке Java не существует кортежей, поэтому кортеж из пяти элементов после компиляции в class файл выглядит как метод tuple() возвращающий объект типа scala.Tuple5:
Compiled from "TupleExample.scala"
public final class TupleExample extends java.lang.Object{
public static final void main(java.lang.String[]);
public static final scala.Tuple5 tuple();
}
Кортежи безусловно очень удобны, особенно в ситуации когда метод должен возвращать несколько значений. Однако не стоит злоупотреблять ими: не используйте кортежи как заменитель классов.
14 февраля 2012, 8:22
public static final
Возможно, что вместо генерируемых аксессоров могут понадобиться статические паблик поля в class файле, например, public static final int VERSION = 1.
Такой возможности пока нет, хотя разработчики языка обсуждают добавление аннотации @static, которая будет генерировать поле вместо геттеров и сеттеров.
Такой возможности пока нет, хотя разработчики языка обсуждают добавление аннотации @static, которая будет генерировать поле вместо геттеров и сеттеров.