Пробую Scala

| рубрика: Заметки | автор: st
Метки:

Пробую Scala на простой задачке "[подсчета цыплят]".

Три фермера продавали кур на местном рынке. У одного было 10 кур, у второго — 16, у третьего — 26. Чтобы не конкурировать между собой, они договорились продавать кур по одной цене. К обеду они решили, что продажи идут не так уж хорошо, поэтому они все одинаково понизили цену. К концу дня они продали всех кур. Оказалось, что каждый из фермеров за этот день выручил 35 долларов. Какова была цена за курицу до обеда и после обеда?

Для начала целей будет три:

  • оценить легкость установки и конфигурации среды
  • простота написания достаточно примитивного кода
  • оценка быстродействия в сравнении с Си

Установка и конфигурация

Под Ubuntu 14.04 особенных проблем нет

$ sudo apt-get install scala scala-library

Установка Скалы уже дает возможность работы с интерпретатором в интерактивном режиме (REPL), если запустить его из терминала.

$ scala

Среда Geany понимает синтаксис Скалы, но компиляцию и запуск делать не умеет. Лезем в документацию по Geany, оттуда через примеры запуска компилятора в меню Build -- Set build commands и в окошечке вводим примерно такие параметры.

Всё, среда и компилятор готовы. Время потрачено немного, менее часа, однако ссылок о настройке Geany для Скалы в гугле всего одна, да и та для пакета из 12.04. Сообщество кажется не очень большим.

Пишем текст

Для пробы возьмем что-то посложнее, чем вывод "Привет, мир!". Пусть это будет вычисление корня методом Ньютона из викиучебника по Скале. Для простоты - на русском языке.

Первая проблема - в учебнике отсутствует основополагающее понятие структуры программы. Лезем в гугл за помощью, но уже к англоязычным источникам. Находится "Scala for Java programmers". Вполне подойдет. Находим, что нужно писать все тот же class Main, т.е. те же яйца, но вид в профиль. Значит структура линейного текста будет громоздкой.

object Hello
{
   def square(x: Double) = x * x

   def improve(guess: Double, x: Double) =
     (guess + x / guess) / 2

   def isGoodEnough(guess: Double, x: Double) =
     abs(square(guess) - x) < 0.001

   def sqrtIter(guess: Double, x: Double): Double =
     if (isGoodEnough(guess, x))
        guess
     else
        sqrtIter(improve(guess, x), x)

   def sqrt(x: Double) = sqrtIter(1.0, x)

   def main(args: Array[String])
   {
      var x = 123
      println("Корень из " + x + " is " + sqrt(x))
   }
}

Запускаем, компилятор ругается на отсутствие функции abs. Ну, это как раз очевидно, математические библиотеки в универсальных языках по умолчанию подключаются редко. Снова поиск, добавляем нужный import, все работает.

import math._

Времени потрачено менее часа, приемлемо.

Подсчет цыплят

Переходим к цыплятам. За основу беру Си-шный исходник.

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main()
{
  time_t t1, t2;
  time(&t1);
  int a1, a2, a3;
  int x, y;
  for (x = 1; x < 3500; x++)
    for (y = 1; y < x; y++)
      for (a1 = 0; a1 <= 10; a1++)
        for (a2 = 0; a2 <= 16; a2++)
          for (a3 = 0; a3 <= 26; a3++)
            if ((a1 * x + (10 - a1) * y == 3500) &&
                (a2 * x + (16 - a2) * y == 3500) &&
                (a3 * x + (26 - a3) * y == 3500))
              printf("x = %d, y = %d, a1 = %d, b1 = %d, a2 = %d, b2 = %d, a3 = %d, b3 = %d\n",
                     x, y, a1, 10 - a1, a2, 16 - a2, a3, 26 - a3);
  time(&t2);
  int seconds = difftime(t2, t1);
  printf("Finished. Time elapsed: %d sec\n", seconds);
  return 0;
}

Возникает первая проблема, не относящаяся, собственно, к задаче. Как найти разницу между двумя датами в секундах?

Поиск в гугле "scala date difference" выдает несколько источников, он они оказываются не вполне релевантными. Готовых примеров нет. Обнаруживается дискуссия о порочности "родного" Java API по части обработки дат. Пара примеров для org.jedi.time вроде бы подходит. Но "из коробки" Скала не знает о существовании этих пакетов.

В документации Scala Library API по словам "time" и "date" ничего нужного не находится. Вот так раз, столь базовые уровни - и не предусмотрены.

Хорошо, будем использовать библиотеку Явы. В книжке находится объяснение, как их использовать в Скале. Найденный Ява-пример определения разницы между двумя датами в секундах оказывается выражением. Видимо, за 20 лет не дошли руки у разработчиков добавить функцию. Ладно, не суть.

Начинаем переписывать Си-шный исходник, сказывается отсутствие цикла for (в итоге оказалось, что он есть). Можно оформить его пользовательской функцией? Да, можно, но в другой раз, когда будет более насущная необходимость, а пока возьмем что есть - while.

import java.util.Date

object Chickens
{
   def findAndPrint()
   {
      var x = 1
      while (x < 3500)
      {
     var y = 1
     while (y < x)
     {
            var a1 = 0
        while (a1 <= 10)
        {
               var a2 = 0
               while (a2 <= 16)
               {
              var a3 = 0
                  while (a3 <= 26)
                  {
                     if ((a1 * x + (10 - a1) * y == 3500) &&
                         (a2 * x + (16 - a2) * y == 3500) &&
                         (a3 * x + (26 - a3) * y == 3500))
                        println("x = " + x + ", y = " + y +
                                ", a1 = " + a1 + ", b1 = " + (10 - a1) +
                                ", a2 = " + a2 + ", b2 = " + (16 - a2) +
                                ", a3 = " + a3 + ", b3 = " + (26 - a3))
                     a3 += 1
                  }
                  a2 += 1
               }
               a1 += 1
            }
            y += 1
         }
         x += 1
      }
   }

   def main(args: Array[String])
   {
      val t1 = new Date
      findAndPrint()
      val t2 = new Date
      val seconds = (t2.getTime() - t1.getTime()) / 1000
      println("Finished. Time elapsed " + seconds + " sec")
   }
}

Получается довольно громоздко. С оператором for запись явно лаконичнее.

Небольшой скоростемер

Запускаем из терминала подряд по 3 раза, фиксируем время (компьютер довольно слабенький, но цель в относительном сопоставлении).

Запуск Результат, сек
Скала Си
1 156 76
2 156 75
3 156 75
Среднее 156 75

Из цифр следует, что аналогичная программа на Скале примерно в 2 раза медленнее, но другого я и не ожидал, так что никаких претензий по этому критерию. Ну, медленнее и медленнее.

Выводы по первым пробам

Первое впечатление от языка - эклектичность. Ожидал более стройную структуру. Возможно, Скала создавалась по принципу "Си++ для программистов на Си", т.е. для программистов на Яве добавили новые концепты. Возможно также, что это впечатление вызвано поверхностным знакомством, не настаиваю.

Второй вывод: для полноценной работы со Скалой нужен хороший опыт работы с Явой, точнее даже, с Java API. Здесь, видимо, тот же принцип другой парочки "C# - F#".

Обнаруженные неудобства:

  • отсутствует цикл for (меня поправили, цикл есть)
  • зрительно отличать val и var трудно, const был бы намного лучше
  • собственные библиотеки пока не содержат функций работы с датами
  • не вполне понятна модульность. Опять модулем является класс?
  • сообщество кажется малочисленным

Из достоинств, наличие интерпретатора командной строки, простота установки, и, конечно, бэкграунд Явы - если не хватает функционала, всегда можно вставить костыль на Java API с его 7-милионным сообществом разработчиков.

Задачи, на которых пробовалась Скала не выявили каких-либо преимуществ именно языка. Рекурсивные функции? Это есть и в процедурном программировании, классика жанра - обход дерева или рекурсивный спуск в синтаксическом анализе. Анонимные функции? Есть в расширениях ООП.

Следующей целью должно быть нахождение небольшой задачи, на которой Скала могла бы продемонстрировать свои преимущества прежде всего по лаконичности записи. Вроде 30 строк SQL вместо 300 строк на C#. Тогда станет яснее область применения языка.