Споры насчет преимуществ и недостатков Scala перед Java напоминают мне споры о C против С++. Плюсы, конечно же, на порядок более сложный язык с огромным количеством способов выстрелить себе в ногу, уронить приложение или написать совершенно нечитабельный код. Но, с другой стороны, C++ проще. Он позволяет делать простым то, что на голом C было бы сложно. В этой статье я попытаюсь рассказать о той стороне Scala, которая сделала этот язык промышленным — о том, что делает программирование проще, а исходный код понятнее.

Дальнейшее сравнение между языками исходит из того, что читатель знаком со следующими вещами:

— Java8. Без поддержки лямбд и говорить не о чем
Lombok Короткие аннотации вместо длинных простыней геттеров, сеттеров, конструкторов и билдеров
Guava Иммутабельные коллекции и трансформации
Java Stream API
— Приличный фреймворк для SQL, так что поддержка multiline strings не так и нужна
flatMap — map, заменяющий элемент на произвольное количество (0, 1, n) других элементов.

Иммутабельность по умолчанию


Наверное, все уже согласны, что иммутабельные структуры данных — это Хорошая Идея. Scala позволяет писать иммутабельный код, не расставляя `final`

Java
@Value
class Model {
    String s;
    int i;
}
public void method(final String a, final int b) {
  final String c = a + b;
}


Scala
case class Model(s: String, i: Int)
def method(a: String, b: Int): Unit = {
  val c: String = a + b
}


Блок кода, условие, switch являются выражением, а не оператором


Т.е. всё вышеперечисленное возвращает значение, позволяя избавиться от оператора return и существенно упрощая код, работающий с иммутабельными данными или большим количеством лямбд.

Java

final String s;
if (condition) {
  doSomething();
  s = "yes";
} else {
  doSomethingElse();
  s = "no"
}


Scala
val s = if (condition) {
  doSomething();
  "yes"
} else {
  doSomethingElse();
  "no"
}


Pattern matching, unapply() и sealed class hierarchies


Вы когда-нибудь хотели иметь switch, работающий с произвольными типами данных, выдающий предупреждение при компиляции, если он охватывает не все возможные случаи, а также умеющий делать выборки по сложным условиям, а не по полям объекта? В Scala он есть!

Scala
  sealed trait Shape  //sealed trait - интерфейс, все реализации которого должны быть объявлены в этом файле
  case class Dot(x: Int, y: Int) extends Shape
  case class Circle(x: Int, y: Int, radius: Int) extends Shape
  case class Square(x1: Int, y1: Int, x2: Int, y2: Int) extends Shape

  val shape: Shape = getSomeShape() //объявляем локальную переменную типа Shape

  val description = shape match {
      //x и x в выражении ниже - это поля объекта Dot
    case Dot(x, y) => "dot(" + x + ", " + y + ")"
      //Circle, у которого радиус равен нулю. А также форматирование строк в стиле Scala
    case Circle(x, y, 0) => s"dot($x, $y)"
      //если радиус меньше 10
    case Circle(x, y, r) if r < 10 => s"smallCircle($x, $y, $r)"
    case Circle(x, y, radius) => s"circle($x, $y, $radius)"
      //а прямоугольник мы выбираем явно по типу
    case sq: Square => "random square: " + sq.toString
  } //если вдруг этот матч не охватывает все возможные значения, компилятор выдаст предупреждение


Java
Даже пытаться не буду повторить это на джаве.

Набор синтаксических фич для поддержки композиции


Если первыми тремя китами ООП являются (говорим хором) инкапсуляция, полиморфизм и наследование, а четвертым агрегация, то пятым китом, несомненно, станет композиция функций, лямбд и объектов.

В чем тут проблема джавы? В круглых скобочках. Если не хочется писать однострочники, то при вызове метода с лямбдой придется заворачивать её дополнительно в круглые скобки вызова метода.

Java

//допустим у нас есть библиотека иммутабельных коллекций с методами map и flatMap. Для другой библиотеки коллекций это будет еще больше кода.
//в collection заменить каждый элемент на ноль, один или несколько других элементов, вычисляемых по алгоритму
collection.flatMap(e -> {
  return getReplacementList(e).map(e -> {
    int a = calc1(e);
    int b = calc2(e);
    return a + b;
  });
});

withLogging("my operation {} {}", a, b, () -> {
  //do something
});


Scala
collection.flatMap { e =>
  getReplacementList(e).map { e =>
    val a = calc1(e)
    val b = calc2(e)
    a + b
  }
}

withLogging("my operation {} {}", a, b) {
  //do something
}


Разница может казаться незначительной, но при массовом использовании лямбд она становится существенной. Примерно как использование лямбд вместо inner classes. Конечно, это требует наличия соответствующих библиотек, рассчитанных на массовое использование лямбд — но они, несомненно, уже есть или скоро появятся.

Параметры методов: именованные параметры и параметры по умолчанию


Scala позволяет явно указывать названия аргументов при вызове методов, а также поддерживает значения аргументов по умолчанию. Вы когда-нибудь писали конверторы между доменными моделями? Вот так это выглядит в скале:

Scala
def convert(do: PersonDataObject): Person = {
  Person(
    firstName = do.name,
    lastName = do.surname,
    birthDate = do.birthDate,
    address = Address(
      city = do.address.cityShort,
      street = do.address.street
    )
  )  


Набор параметров и их типы контролируются на этапе компиляции, в рантайме это просто вызов конструктора. В джаве же приходится использовать или вызов конструктора/фабричного метода (отсутствие контроля за аргументами, перепутал местами два строковых аргумента и привет), или билдеры (почти хорошо, но то, что при конструировании объекта были указаны все нужные параметры, можно проверить только в рантайме).

null и NullPointerException


Скаловский `Option` принципиально ничем не отличается от джавового `Optional`, но вышеперечисленные особенности делают работу с ним легкой и приятной, в то время как в джаве приходится прилагать определенные усилия. Программистам на скале не нужно заставлять себя избегать nullable полей — класс-обертка не менее удобен, чем null.

Scala
val value = optValue.getOrElse("no value") //значение или строка "no value"
val value2 = optValue.getOrElse {  //значение или exception
  throw new RuntimeException("value is missing")
}
val optValue2 = optValue.map(v => "The " + v) //Option("The " + value)
val optValue3 = optValue.map("The " + _) //то же самое, сокращенная форма
val sumOpt = opt1.flatMap(v1 => opt2.map(v2 => v1 + v2)) //Option от суммы значений из двух других Option

val valueStr = optValue match { //Option - это тоже sealed trait с двумя потомками!

  case Some(v) =>  //сделать что-то если есть значение, вернуть строку
    log.info("we got value {}", v)
    "value.toString is " + v

  case None => //сделать что-то если нет значения, вернуть другую строку
    log.info("we got no value")
    "no value"
}


Конечно же, этот список не полон. Более того, каждый пример может показаться незначащим — ну какая, в самом деле, разница, сколько скобочек придется написать при вызове лямбды? Но ключевое преимущество скалы — это код, который получается в результате комбинирования всего вышеперечисленного. Так java5 от java8 не очень отличается в плане синтаксиса, но набор мелких изменений делает разработку существенно проще, в том числе открывая новые возможности в архитектурном плане.

Также эта статья не освещает другие мощные (и опасные) фичи языка, экосистему Scala и ФП в целом. И ничего не сказано о недостатках (у кого их нет...). Но я надеюсь, что джависты получат ответ на вопрос «Зачем нужна эта скала», а скалисты смогут лучше отстаивать честь своего языка в сетевых баталиях )
Поделиться с друзьями
-->

Комментарии (124)


  1. sshikov
    11.11.2016 21:35

    А можете пояснить, как это switch выдает предупреждения, если внутри есть произвольные условия? Мне кажется это невозможно в общем случае. Вот смотрите:


    case Circle(x, y, r) if r < 10 => s"smallCircle($x, $y, $r)"
    case Circle(x, y, radius) => s"circle($x, $y, $radius)"


    замените тут 10 на переменную — и статический анализ этого кода компилятором станет невозможным.


    1. Scf
      11.11.2016 21:51

      Да, в общем случае статический анализ невозможен. Привел в статье неудачный пример, очень уж хотелось показать вариант с if-ом. Но для простого матчинга по типам и параметрам кейс классов компилятору вполне можно доверять.


      1. sshikov
        11.11.2016 21:59

        Ну, самый-то простой случай без статической проверки полноты уже вполне съедобен даже на Java 8 :) При наличии javaslang например. Выглядит кривовато, но не слишком.


        1. saksmt
          11.11.2016 23:24

          Не слишком кривовато до тех пор, пока вы не пишете что-то уж очень завязанное на сообщения (akka)


          1. sshikov
            13.11.2016 10:34

            Ну, да. Я бы даже сказал — до тех пор, пока не попробуете скомбинировать несколько таких switch в единое целое. В текущем проекте я довольно активно javaslang использую, и вполне себе заметно, что с нарастанием сложности оно регулярно ломается во вполне предсказуемом месте — на слабостях системы типов Java.


    1. potan
      12.11.2016 19:21

      По моему, проверяется что все покрыто безусловными паттернами. Даже если условие в if не выполнится, найдется подходящий образец.


      1. alexeyrom
        15.11.2016 17:43

        Увы, нет. На данный момент не проверяется никак.


    1. alexeyrom
      15.11.2016 17:42
      +1

      На самом деле никак: как только условия появляются, предупреждения не выдаются: https://issues.scala-lang.org/browse/SI-5365. Был pull request, чтобы переключиться на пессимистический вариант, но в 2.12.0 не попал (см. последний комментарий по ссылке).


  1. stokker
    11.11.2016 21:53
    -27

    У Java и Scala разные ниши (Java — аутсорс, малый и местный бизнес, Scala — глобальные продуктовые компании), поэтому пофичечное сравнение не очень корректно.


    1. sleeply4cat
      11.11.2016 21:56
      +21

      Вы точно не поменяли сферы применения местами? 0_о


      1. stokker
        12.11.2016 00:30
        -9

        Scala — малый и местный бизнес? Действительно смешно… Про Scala в аутсорсе просто не верю… Значит это ниши Java ))
        Scala позволяет писать большие и сложные проекты ОДНОЙ командой быстрее, чем Java (хотя раньше за неимением такой альтернативы Java была практически монополистом). Поэтому продуктовая IT-контора. И глобальная потому что иначе и смысла нет напрягаться.

        Я не против минусов, но хотелось бы и доводы услышать…


        1. sleeply4cat
          12.11.2016 15:26
          +2

          1. stokker
            13.11.2016 06:07

            Так мы же про ниши говорим, а не то, насколько их удается освоить.


        1. potan
          12.11.2016 19:25

          Я работаю в небольшой компании (примерно 50 человек), в отделе, который разрабатывает облачную PLM-систему. Вполне себе средний бизнес. Практически все наши сервисы написаны на Scala.


    1. Scf
      11.11.2016 21:57
      +6

      Как человек, видевший и стартапы на скале, и глобальные продуктовые компании на джаве, не соглашусь. Разве что Scala-аутсорс — это звучит странно.


      1. stokker
        12.11.2016 00:32
        -2

        А разве «стартапы на скале» противоречат моему высказыванию? А «глобальные продуктовые компании на джаве» — это скорее след из прошлого…


        1. saksmt
          12.11.2016 14:17
          +1

          В таком случае я работаю в "следе из прошлого"


          1. stokker
            12.11.2016 15:20
            -2

            Это у всех так. В будущее смотреть скорее надо для планирования.


    1. gkislin
      12.11.2016 01:23
      +3

      Вы посмотрите статистику продуктовых проектов на scala и на java и поймете, почему наминусили. А что "Java — аутсорс, малый и местный бизнес" это вообще трудно откоментировать так, чтобы не обидеть.


      1. stokker
        12.11.2016 01:49
        -1

        Постарайтесь… Добавлю только, что про ниши я говорил в некоторой перспективе (думал, что и так понятно).


  1. Sirikid
    11.11.2016 21:58
    -2

    Ну почему вы не хотите писать однострочники мистер Андерсон? http://pastebin.com/Db9kYmLr


    1. Scf
      11.11.2016 22:04

      Потому, что однострочники не масштабируются — при усложнении кода они становятся… ммм… менее читабельными. Пока лямбду можно уложить в поток стримов, это хорошо. А когда нужно будет её усложнить? Код на стримах придется переписывать.


    1. saksmt
      11.11.2016 23:26

      Так или иначе, даже если писать однострочники, скала выиграет 2 строки :)


  1. bsideup
    11.11.2016 22:09
    +3

    читатель знаком со следующими вещами
    Lombok
    иммутабельный код
    @Data
    class Model {
    private final String s;
    private final int i;
    }

    Для immutable классов в Lombok-е используется @Value: https://projectlombok.org/features/Value.html


    @Value
    class Model {
        String s;
        int i;
    }

    дальше видимо можно не читать...


    1. Scf
      11.11.2016 22:11
      -3

      Спасибо, сейчас поправлю :-) Два года на скале сказываются — начал подзабывать уже.


  1. UbuRus
    11.11.2016 22:54
    +11

    Если это все что хочется от Скалы, то можно взять Котлин


    1. potan
      12.11.2016 19:28
      +2

      В Котлине таки сделали pattern matching?


      1. UbuRus
        12.11.2016 21:52
        +1

        Того что уже есть в языке хватает.


        1. saksmt
          15.11.2016 10:33

          Ещё нет, но в 1.1 обещали ограниченные продолжения и нормальную деструктуризацию, вот тогда будет как раз хватать :)


          1. UbuRus
            15.11.2016 17:47

            Речь была о control-flow.
            Ну и «продолжения» для меня диковато звучат. «Континуации» наверно не лучше, зато есть принятые «корутины». Так что да, будут Корутины в Котлин.


  1. grobitto
    11.11.2016 23:07
    +9

    Все эти Java vs Scala, Coffeescript vs Javascript итд к сожалению обычно заканчиваются печально для тех, кто выбирает молодые языки.

    Рано или поздно под давлением комьюнити все нужные фичи появляются в базовых языках и через какое-то время конкуренты начинают умирать.

    О Скала последнее время слышно все меньше и меньше, на мой взгляд она уже прошла свой пик, и дальше будет только хуже. Не знаю, имеет ли стратегический смысл вкладываться в Скала.

    Вы не подумайте, что я считаю ее плохой — она действительно СЕЙЧАС удобнее Java. Но за долгие годы карьеры мне приходилось несколько раз ставить «не на ту лошадь», что выливалось в серьезные проблемы впоследствии.


    1. Scf
      11.11.2016 23:20
      +2

      Вы правы, но я специально подобрал для статьи фичи, которые не так-то просто добавить в существующий синтаксис Java и существующую идеологию Java. К тому же, Java развивается ОЧЕНЬ медленно — оракл не может себе позволить добавить в язык что-то трендовое и не проверенное временем.


      Если lightbend не разорится, на 5-10 лет активного развития языка рассчитывать можно.


      1. grobitto
        11.11.2016 23:28
        +2

        А дело не в том, можно или нет добавить какие-то фичи. Просто в какой-то момент «фич» Скала станет недостаточно, чтобы привлекать новых людей в комьюнити, и язык начнет умирать.


      1. AndreyRubankov
        12.11.2016 00:14
        +1

        Языковые фичи (синтаксический сахар) — это самый плохой повод выбирать тот или иной язык программирования.

        Вот, к примеру, Go — языковых фич в нем минимум! и это невероятно круто!
        Чем меньше фич, чем меньше долбанной магии под капотом — тем лучше будет проектироваться и работать вся система в целом.

        В java довольно много фич, действительно лишних фич: strictfp, assert, instanceOf, labels (да-да, типа таких как для goto). Если бы Оракл вводил все подряд на уровень языка — java уже давно умерла бы. Даже сейчас для новичком Java — это тяжелый язык (и платформа), если бы в ней было бы больше фич — этот язык был бы фактически неподъемным.


        1. Sirikid
          12.11.2016 00:40
          +1

          Почему вы считаете instanceof «лишней фичей» и что предлагаете взамен? (метод с тем же смыслом в Object? Class#isInstance?) Тот же вопрос с метками.


          1. AndreyRubankov
            12.11.2016 00:57
            +3

            instanceOf — плохой оператор в целом. Вариант через API на много лучше. НО!

            Я имел ввиду, что instanceOf — это в принципе устаревшая конструкция. С появлением дженериков в ней пропала необходимость.
            Если нужно завязать логику на конкретный тип — используйте либо дженерики, либо перегрузку методов.
            99% задач не требуют проверки типа. Тем более, это проверка не на конкретный тип, а на принадлежность иерархии.

            Единственная причина использовать instanceOf — это переопределение equals метода в иерархии классов. Но это в принципе плохой подход к построение архитектуры. Иерархия data-классов ни к чему хорошему еще не приводила, тем более с переопределенным equals.


            1. guai
              12.11.2016 15:31
              +1

              Дык, эээ…
              Вы о явавских дженериках?
              Переусложненных, испаряющихся в рантайме, use-site variance, и даже при своей переусложненности не покрывающих всех кейсов.
              Они не могут на 100% отменить необходимость в instanceOf
              Но тем не менее назвать их плохим подходом к построению архитектуры нельзя. Просто когда их делали, на принятие решений очень сильно влиял фактор совместимости. А делали их офигенные инженеры, архитектура там из возможной самая оптимальная.


              1. AndreyRubankov
                12.11.2016 16:40

                Вообще-то я говорил, что instanceOf — это признак плохо спроектированного кода, а не дженерики.

                > Они не могут на 100% отменить необходимость в instanceOf

                Дженерики — не покрывают, это известный факт, но ведь еще есть и перегрузка методов.

                Был бы рад услышать пример, когда instanceOf действительно необходим, может я уперся в стену и не вижу всей картины.


                1. guai
                  12.11.2016 17:12

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


                  1. AndreyRubankov
                    12.11.2016 18:39

                    С другой стороны reflection — это уже способ вылезти за приделы языка, не так ли?
                    Код с рефлексией толком не оптимизаруется через jit.
                    Теряется практически весь смысл строгой типизации — ошибок на этапе компиляции не будет (если не через annotation processing), зато будут проблемы в рантайме.

                    Reflection — это не то, что стоит использовать повсеместно.

                    А на счет instanceof, честно, не могу придумать примера для адекватного его использования, даже с дженериками — проверять параметризированный тип через instanceof?))

                    ps: если перегрузка — это плохо для рефлексии, то если будут дефолтные параметры (вместе с перегрузкой) — можно будет сразу повеситься =)


                    1. guai
                      12.11.2016 19:10
                      +2

                      я не вижу способа не иметь instanceof и иметь настолько же богатые возможности, как в яве. в любой известной мне системе типов можно прикастить объект к своему родителю или к интерфейсу. и потом надо как-то получать его реальный тип.
                      иначе любая потеря информации о типе будет распространяться, нет возможности ее восстановить. ну и писать код так, чтобы ее не терять — это надо быть нечеловечески внимательным. и любой затык с апи будет приводить к тому, что или идем к авторам апи и просим его поменять, или всё, облом, нужную работу мы не сделаем.
                      а еще, думаю, будут образовываться такие дженерики дженериков дженериков, что повеситься.
                      сериализация, подозреваю, вообще не будет возможна, в таком случае.


                      1. AndreyRubankov
                        12.11.2016 20:18
                        -1

                        > и потом надо как-то получать его реальный тип.

                        В этой строчке кроется корень проблемы. Если вы передаете куда-то наследника какого-то класса, то по принципу Лисков (liskov substitution, L из SOLID) система должна будет работать так же.
                        Если вкратце, любой код, который принимает какой-то базовый класс не должен никоим образом завязываться на конкретную имплементацию этого базового класса.

                        Для сериализации instanceof не подойдет, как минимум перебирать все возможные типы данных — это глупо. Да и instanceof показывает принадлежность к целой иерархии типов, а не к конкретному классу. Для сериализации используют стандартную java сериализацию (Serializable или Externalizable) или Reflection API.


                        1. guai
                          12.11.2016 21:02

                          А можно пример такой платформы, в которой бы не было бы инстансоф, но было всё остальное, что есть в яве?
                          Ну я пока не придумал кейсов, которые не реализуемы вообще без инстансоф, но такие, где всё становится проще, — пришли на ум.
                          Пример: пайплайн объектов, состоящий из неких продюсеров данных, фильтров, размножалок, консюмеров и т.д. И я хочу их соединять в цепи в рантайме.
                          Можно на дженериках попытаться замутить, но я не уверен, что не уткнусь в косяки реализации явавских дженериков. Плюс в рантайме они пропадут. А так просто ходят объекты неизвестного типа. Каждый элемент пайплайна, если ему важна внутренняя кухня объекта, тестит его тип. Умеет с ним работать — работает, нет — скипает, или ругается. Можно не задумываться, какие там типы вообще ходят, мне главное поймать свои и обработать.


                          1. AndreyRubankov
                            12.11.2016 21:16
                            -1

                            В текущем треде вы уже который раз перекручиваете мои слова.
                            Я говорил, что instanceof — плохо, дженерики — хорошо.

                            Вы перекрутили так, будто я говорил обратное. Не нужно так =)

                            На счет пайплайна.
                            Если ваши обработчики должны принимать какие-то решения в зависимости от того типа данных, который к ним приходит — просто заведите в интерфейсе какой-то Enum / EnumSet атрибут, по которому будете принимать решение. Это будет более явный и правильный подход к обработке, чем завязка на тип реализации.


                            1. guai
                              12.11.2016 21:22

                              Ну то есть инфа о типе всё-равно нужна, только вы за то, чтобы ее неявно пропихивать рядом, когда это надо, а не иметь всегда? Тут я ничего не перекрутил?
                              Ну профита я в этом пока не увидел.


                              1. AndreyRubankov
                                12.11.2016 21:32

                                Не перекрутили, но смысл не полностью уловили.

                                Вместо того, чтобы завязываться на какой-то конкретный тип реализации вы будете завязываться на публичный API. Это идеологически разные подходы.


                                1. guai
                                  12.11.2016 21:47

                                  Да вроде уловил…
                                  Только мне не хочется зависеть от качества апи. Решит дизайнер апи так же, как вы, что тест на тип не нужон, и останусь я куковать.


                            1. dougrinch
                              13.11.2016 02:21

                              Я говорил, что instanceof — плохо, дженерики — хорошо.

                              Но, подождите, в рефлекшене есть методы getClass, getSuperclass, getInterfaces. Что мешает мне руками написать instanceOf(Object, Class)?


                        1. guai
                          12.11.2016 21:13

                          вот event bus еще, например:
                          Подписался я на эвенты и реагирую только на какие-то нужные, ну да, можно не на тип теститься, а дополнительный суррогат типа пропихивать вместе с эвентом. Но нафига, если можно просто тестануться на тип.
                          Чем хуже с истансоф, я не понимаю, и зачем отказываться от него из-за какого-то абстрактного высшего блага — тоже.


                          1. AndreyRubankov
                            12.11.2016 22:06

                            Это замечательный пример!

                            С instanceof вы проходите по всему списку подписчику и каждому даете сообщение на обработку (прям Chain of Responsibility), каждый уже сам решает обрабатывать ему или нет. Сложность обработки одного события O(N), где N — это общее количество всех подписчиков на все события.

                            И тут есть 2 кейса:
                            1. Вы хотите подписаться на конкретный тип события, никакие другие не хотите слушать.

                            Map<Class<? extends Event>, List<EventHandler<? extends Event>> handlers;
                            В этом случае вы можете использовать java.util.Map и сделать обработку за O(M),
                            где M — это количество обработчиков подписанных на конкретный тип сообщения (M ? N).

                            Плюсы:
                            — вы имеете строгую типизацию без проверок и приведения типов в рантайме.
                            — вы обрабатываете только те события, которые вам интересны (без лишних вызовов).

                            Минус:
                            — в этом простом примере вы теряете возможность подписаться на все дочерние события (подписаться на Event и получать вообще все события — не получится; но нужно ли это?).

                            2. Вы хотите подписаться на целую иерархию типов:
                            Event > MyEvent > MySuperEvent
                            — Вы подписываетесь на Event, и получаете вообще все события на обработку.

                            Этот кейс в безопасном виде решить будет сложнее:

                            Map<String, List<EventHandler<? extends Event>> handlers;
                            — вы подписываетесь на конкретное сообщение (String | Enum), и ожидаете, что тот, кто будет кидать сообщение с этим типом будет кидать его в нужном типе.

                            Плюс:
                            — Вы сохраняете возможность получать всю иерархию в ваш обработчик
                            — Вы по прежнему обрабатываете только те события, которые интересны
                            Минус:
                            — Теряется безопасность типов (в рантайме можно выхватить класс каст эксепшн)

                            Да, в этом случае instanceof обеспечил бы безопасность типов, но если кто-то унаследуется от вашего класса, и кинет сообщение, то вы будете обрабатывать события, которые не должны были обрабатывать и ошибка в этом случае не вывалится (а это еще хуже, чем сразу свалиться).

                            Зачем кому-то наследоваться от вашего события?
                            Да просто потому, что там есть такие же поля (для тех, кто не знает принципиальной разницы между Абстрактным классом и Интерфейсом — это нормально дело).


                  1. potan
                    12.11.2016 19:31

                    А зачем пользоваться рефлекшеном?


                    1. AndreyRubankov
                      12.11.2016 19:57

                      К примеру, чтобы обрабатывать аннотации.
                      На этапе компиляции можно по аннотациям сгенерировать дополнительные методы, обертки или кучу всяких классных (или не очень) вещей. Так делают lombok — позволяет уменьшить количество бойлерплейт кода; и mapstruct — маппер одних бинов на другие.

                      Так же в рантайме, для добавления гибкости. Spring, Hibernate, JPA, JAX-RS, Guice и множество других.

                      Или для написания фреймворков с контрактом именования методов. Spring Data, некоторые OSGi фреймворки, junit 3.x и много других.


                    1. guai
                      12.11.2016 20:04

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


                1. Scf
                  12.11.2016 21:58

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


          1. AndreyRubankov
            12.11.2016 01:11
            +2

            По поводу меток тоже не сильно далеко все ушло.
            Мекти в java есть только в двух операторах break и continue.
            Предназначены, чтобы выйти из большой вложенности циклов.

            Но ведь большая вложенность циклов — это уже плохо спроектированный код. А с метками этот код станет только хуже!
            Но если так исторически сложилось и нужно досрочно выйти из большой вложенности циклов, можно же эти циклы обернуть в один метод и просто выйти из него через return. Это не сильно ударит по производительности, но код будет лучше читаться.

            ps: я еще ни разу не встречал метки в реальных java проектах, зато видел много java разработчиков, которые даже не знают про их существование.


            1. Sirikid
              12.11.2016 01:16

              Все, защитано. Я метки применял один раз, правда там была просто головоломная задачка.


            1. IIvana
              12.11.2016 02:52
              -2

              Наличие instanceOf делает Java (как и C#) лично мне на порядок удобнее — добавляет языкам некоторые свойства динамических при сохранении статического контроля типов где надо. Я не претендую на знатока языка, но в моем единственном проекте на Java половина типов — Object. Вот так незамысловато получаем динамическую типизацию, полиморфные контейнеры и вообще полную свободу действий. Вы скажете — что это овноархитектура и овнокот и что надо на дженериках/интерфейсах или что там еще? Но мне так гораздо удобнее.


              1. AndreyRubankov
                12.11.2016 03:46

                Что ж, если вы строите решение на Object + instanceOf и говорите, что это удобно, возможно так оно и есть. Люди разные, кто-то на гвоздях спит, кто-то стекло есть, кто-то Object + instanceOf повсеместно использует. У каждого своя правда!

                ps: тред про instanceOf был немного выше.


                1. euggie
                  12.11.2016 14:23

                  instanceof (в java) пишется в lowcase


            1. APXEOLOG
              12.11.2016 12:10
              +4

              Не совсем понятно как большая вложенность циклов обязательно означает плохо спроектированный код. Есть много алгоритмов в которых никак не уйти от вложенных циклов. Можно конечно обернуть все вокруг в методы, постоянно проверять результаты их работы и т.д. но зачем городить лапшу, когда достаточно проставить break label.
              Я не говорю что у меток широкий спектр применения, но иногда они делают ваш код понятнее, чем если бы вы их не использовали.


              1. AndreyRubankov
                12.11.2016 12:40

                Да, вы правы! Совсем упустил этот момент. Не часто приходится сталкиваться с реализацией сложных алгоритмов.

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


      1. xhumanoid
        12.11.2016 00:27
        +2

        >> Если lightbend не разорится, на 5-10 лет активного развития языка рассчитывать можно.

        а чтобы не разориться он начал вкладываться и в java, а не только надеяться на scala

        >> К тому же, Java развивается ОЧЕНЬ медленно — оракл не может себе позволить добавить в язык что-то трендовое и не проверенное временем.

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

        Простой пример: есть ваш продукт X на scala 2.11 который использует библиотеку Y обязательно версии 2.11, попытка перейти на версию 2.12 вызовет необходимость патчить сразу свой код, а потом продолжать делать это и для библиотеки Y. Особенно интересной ситуация выглядит когда вы стартуете новый проект и пытаетесь использовать свежую скалу 2.10, но видите что одна из ваших зависимостей собрана только под 2.10, а другая заброшена на уровне 2.9, после этого часть пытаетесь сами перевести, часть накидать костылей.

        Параллели java/scala и c/c++ некорректны хотя бы в совместимости, посмотрите сколько говна за собой тянет c++ ради обратной совместимости (да, не всегда получается, но зачастую об этом хоть думают). Не может язык который плевать хотел на свой же код и собранные библиотеки всего двухлетней давности стать промышленным стандартом, дальше стартапов где «написал-потыкал-выкинул-переписал» его не пустят.

        Скала самому нравится, но с таким академическим подходом «сегодня добавим посмотрим очередную фичу, завтра поменяем, послезавтра вообще выпилим» у меня она остается на всяких генераторах тестовых данных или PoC.


    1. andr1983
      12.11.2016 01:05
      +1

      Лично я, в последнее время, слышу о скала как раз достаточно много, и вижу, что язык развивается и достаточно быстро. Недавно вышла Scala 2.12, есть Scala Native, ScalaJS и прочие. Ошибочно относиться к скале, как к Java++. Это совершенно другой язык, со своей философией и подходом к разработке. Конечно, можно писать код так, как писали бы его на джава, но это, в итоге, будет причинять только боль. И потом будут появляться статьи из серии, почему я бросил скалу и вернулся на джаву.

      Так что, сколько бы плюшек в джаву не добавили — это не имеет значения, так как языки по своей природе разные.


      1. grobitto
        12.11.2016 01:25

        Каждый язык живет, пока люди им пользуются. Мало того, что Java становится приятнее, так еще появляются другие конкуренты, тот же Kotlin.

        Churn никуда не деть, и если люди уходят активнее, чем приходят — это стагнация


        1. andr1983
          12.11.2016 02:19
          +1

          А кто сказал, что со скалы люди уходят? Мне, кажется, наоборот, за последний год появилось достаточно много новых вакансий. По крайней мере на LinkedIn отбоя от рекрутеров нет и, периодически, попадаются предложения от серьезных кампаний. В сфере бигдаты скала уже практически стандарт, благодаря Spark и Flink.

          Я вот не считаю, что Kotlin прямой конкурент Scala. Как раз он более походит на Java++ и у него значительно больше шансов пойти дорогой Coffeescript.


          1. xhumanoid
            12.11.2016 13:55
            +2

            spark — усиленно подтягивает java интерфейсы, в 2.0 все что есть по api в scala есть и в java

            flink — для себя использует в первую очередь java, scala там как таковое в 2х местах table-api (но все обвязки и на java имеются) и в flink-ml (большинство алгоритмов внутри на scala сделаны)

            apache beam — чистая java (а с ним вы получаете полную связку google dataflow/spark/flink/apex/gearpump), хотя spotify и прикрутил уже сбоку обвязку в виде scio

            >> По крайней мере на LinkedIn отбоя от рекрутеров нет и, периодически, попадаются предложения от серьезных кампаний.

            Систематическая ошибка внимания и Феномен Баадера-Майнхоф

            =) сам склонен к этим симптомам

            я больше склонен к позиции: больше людей начинают учить скалу
            но нужно учесть, что наличие и знание других языков не означает повседневное его использование, так как многие вещи учишь только ради расширения своих знаний


    1. DarkEld3r
      12.11.2016 23:57
      +3

      Все эти Java vs Scala, Coffeescript vs Javascript итд к сожалению обычно заканчиваются печально для тех, кто выбирает молодые языки.

      Может, конечно, смотрю со своей колокольни, но не особо понимаю. Ну то есть, если пишешь на скале, то ведь и с джавой худо-бедно ознакомишься. Опять же, о ком мы говорим? Если это "молодой специалист", который толком опыта не имеет и ему удалось работу на скале найти, то сомневаюсь, что он пропадёт. А если это человек с опытом решил переквалифицироваться, то тем более.


      Или предполагается, что человек без опыта хочет стать программистом и усиленно занимается изучением "молодого языка"? В этом случае, и правда стоит посмотреть на окружающие условия, но как по мне, это несколько особый случай.


    1. fly_style
      13.11.2016 00:12
      +1

      Сейчас действительно удобен Kotlin.


    1. eXTreMeHawk
      13.11.2016 09:37
      +1

      > Рано или поздно под давлением комьюнити все нужные фичи появляются в базовых языках и через какое-то время конкуренты начинают умирать.


      В Java фитчи из FP языков, например таких как Scala, не могут появится by design. Это даже теоретически невозможно без тотального слома обратной совместимости. Это как сделать из запорожца реактивный самолёт последнего поколения.

      > О Скала последнее время слышно все меньше и меньше,
      Да что вы говорите!? Читаю эти строки вернувшись пару часов назад с конференции Scala By The Bay, которая сейчас проходит в San Francisco :-) :-) Огромное кол-во компаний: от гигантов индустрии до стартапов…


    1. rkfg
      13.11.2016 12:14

      Выбирать немейнстрим для чего-то крупного или потенциально крупного — это всегда большой риск. Через годы тренды сменятся, а кодовая база останется, и поддерживать её может оказаться некому. Всякое случается. Тут мне сразу вспомнилась вот эта статья, потому что как-то раньше не приходилось читать про элемент языка, который вообще непонятно что делает. Это забавно, но только в том случае, если не тебе приходится разгребать ситуацию. С выводом в статье о переходе на Go я лично, впрочем, не согласен, но это уже чисто моё мнение. Сам сделал ставку на Java и C++, которые точно никуда не денутся в обозримом будущем, и языковая/библиотечная мощность которых меня на данный момент полностью устраивает.


      Из JVM-языков часто упоминают Kotlin, но почему-то совсем ничего не слышно про Ceylon, который тоже очень-очень неплох. Делается RedHat'ом и имеет поддержку в Eclipse, что логично, тогда как у Kotlin приоритетна IDEA.


      1. Scf
        13.11.2016 12:49
        +2

        Это не элемент языка. Это элемент дизайна языка, позволяющий создавать операторы из произвольного набора символов пунктуации и элемент библиотеки, которая этим злоупотребляет.


        1. rkfg
          13.11.2016 13:26

          Безусловно, как и перегрузка операторов в C++. Это мощная концепция, которую можно употребить очень неправильно, в итоге запутав код и сделав его неподдерживаемым. Мне кажется, этим и отличаются языки мощные/крутые/красивые от языков промышленных (C++ под этот критерий не очень подходит, согласен, но всё же является промышленным) — в последних дизайн языка требует писать скучно, уныло и абсолютно однозначно. Не в чем запутаться, код не блещет изящными решениями и потому может поддерживаться годами без особых усилий. Конечно, это лишь моё видение.


    1. Saffron
      14.11.2016 20:34
      +1

      > Рано или поздно под давлением комьюнити все нужные фичи появляются в базовых языках и через какое-то время конкуренты начинают умирать.

      Экспериментальный язык всегда будет на шаг впереди. Пока основной язык заимствует проверенные многолетней практикой наработки экспериментального, экспериментальный придумывает новые. Ну а если они в конце-концов дойдут до идеала и идентичности, то не всё ли равно, каким языком пользоваться? Замените ключевые слова на их синонимы — и вот вы знаете оба языка, и не потеряли ничего.


  1. AndreyRubankov
    11.11.2016 23:11
    +5

    > Наверное, все уже согласны, что иммутабельные структуры данных — это Хорошая Идея. Scala позволяет писать иммутабельный код, не расставляя `final`
    >
    > public void method(final String a, final int b) {
    > final String c = a + b;
    > }

    Да ладно? причет тут иммутабильность к final?
    final в данном контексте — это всего лишь val, не более того;
    Вы же писали, что с java переходили, а такую глупость пишите.


    1. monolithed
      12.11.2016 00:07

      Даже я обратил на это внимание, хотя хотя мое знакомство с Java очень поверхностное.


  1. zoonman
    12.11.2016 02:56
    +2

    Может мне кто-нибудь объяснить, за что люди так любят иммутабельность?


    1. AndreyRubankov
      12.11.2016 04:07
      +1

      Если по простому — это сейчас модно!
      Идет в поставке вместе с чистыми функциями, лямбдами и монадами =)

      преимущество: ты его не можешь изменить, значит можешь свободно расшарить между потоками и не бояться, что у тебя возникнут проблемы синхронизации или гонки. Единственный способ изменить объект — создать новый. А это удобно для отладки и т.д. (так говорят).

      недостаток: но теперь, чтобы изменить значение нужно создать копию объекта с 1 измененным полем;
      чтобы заполнить один более-менее большой объект с данными нужно будет создать десяток копий, которые будут лежать на стеке и не будут собираться gc, пока стек не развернется (или создать изменяемый объект-билдер, который позволит создавать большие неизменяемые объекты).


      1. ExplosiveZ
        12.11.2016 07:52
        -1

        Какой-то добровольный выстрел в ногу… А потом сами жалуются, что jvm много ОЗУ требует.


        1. AndreyRubankov
          12.11.2016 13:07

          Иммутабильность — это не только в jvm.

          А вот нападки на jvm потому, что она расходует памяти на что-то «левое». На мониторинг, на выравнивание памяти, на jit статистику, на gc (в некоторых крайних случаях, лабораторки в универе, gc может требовать памяти больше, чем сама программа).

          Все это требует потребления большего количества памяти, но в результате дает возможность увеличить производительность всей системы (в долгосрочной перспективе).


      1. Source
        12.11.2016 12:25

        но теперь, чтобы изменить значение нужно создать копию объекта с 1 измененным полем;
        чтобы заполнить один более-менее большой объект с данными нужно будет создать десяток копий

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


        1. AndreyRubankov
          12.11.2016 13:17

          Так это и есть копирование. Копируется не все дерево объектов, копируется ссылка на поддерево (или значение для примитивов).


          1. Source
            12.11.2016 22:40

            Так в чём копирование то? Если остальные поля объекта остались как есть, там где были, а изменённое поле стало существовать в двух ипостасях — старое значение и текущее значение.
            Имхо, слово "копирование" подразумевает дубликацию одних и тех же значений, а этого как раз не происходит.


            1. AndreyRubankov
              12.11.2016 23:09
              -1

              Копирование это не дублирование.
              Есть объект A правим какое-то значение, получаем новый объект A' (копируем все поля из A в A', одно заменяем), это в разы дешевле, чем глубокое копирование, но все же тоже копирование.


            1. 23derevo
              13.11.2016 16:05

              а теперь мы вспоминаем слова «многопоточность», «shared state» и «инвариант» и пытаемся понять, что делать со всей этой хренью в случае нескольких потоков. Кроме того, вспоминая слово «locality» мы понимаем, что вся локальность в вашей схеме теряется, все становится менее cache-friendly, начинаются походы в память, перфоманс начинает проседать и т.д. и т.п.

              Реально рецепт один — надо мерять. Что дороже — блокировки на изменяемых объектах или схема на CAS-loop'ах — в общем случае совершенно непонятно.


      1. potan
        12.11.2016 19:40

        Билдер обычно реализуют с помощью цепочки вызовов. При этом на стеке ни чего не накапливается.


        1. AndreyRubankov
          12.11.2016 20:03

          Но ведь билдер — это уже изменяемая структура данных.
          И это единственный адекватный способ создать большой неизменяемый объект избежав лишнего копирования.


    1. Flux
      12.11.2016 06:44
      +2

      Потому что концепция иммутабельности выглядит как волшебная палочка позволяющая забыть о проблемах многопоточности и «неконсистентности» данных, если не обращать внимания на то что все это реализуется на существующем сегодня железе, а значит является обеткой над мутабельной памятью.

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

      В итоге это обычно выливается в reverse массива за O(n * log n) или грязные хаки, когда данные вообще иммутабельные, но если сильно нужно — мутабельные.

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


    1. Scf
      12.11.2016 11:35
      +3

      Прежде всего за упрощение кода. Допустим у нас есть два одинаковых класса — один иммутабельный (все его поля заполняются через конструктор, для модификации нужно создать другой экземпляр класса), а второй — мутабельный, т.е. его состояние можно менять через сеттеры. Теперь смотрите разницу:


      • поля класса часто бывают зависимы, к примеру, в нашем классе должно быть заполнено или поле А, или поле B. В мутабельном классе невозможно гарантировать этот инвариант — для смены состояния потребуется два вызова сеттеров. Можно сделать отдельный метод, можно написать в сеттерах код, проверяющий инварианты, можно, как в спринге, сделать отдельный метод инициализации, который будет проверять инварианты. Но всё это значительно сложнее, чем конструктор с простой проверкой
      • Атомарность внесения изменений. Для иммутабельных структур данных изменения "применяются" только тогда, когда новый объект будет вставлен вместо старого. Помимо уже упоминавшейся многопоточности, это позволяет оставить программу в неразрушенном состоянии при возникновении исключения в процессе внесения изменений
      • Иммутабельный код проще понимать. Когда ты знаешь, что эти переменные final и не могут быть изменены, можно рассматривать меньше вариантов поведения программы при чтении и "вычитке" кода
      • Модульность и тестирование. Если состояние какого-то модуля иммутабельное, то для смены этого состояния нужно построить новое дерево объектов, что, как правило, делается одним методом или конструктором. Куда можно вписать логгирование, который можно вызывать из тестов для воспроизведения нужного состояния и т.п.
      • Хорошо работает с чистыми функциями(результат выполнения которых зависит только от входных параметров). Чистые функции легче писать, понимать, тестировать и поддерживать.


      1. stokker
        15.11.2016 19:56

        А теперь объясните мне пожалуйста, зачем в Java добавили сеттеры и геттеры. По-моему, это откат к процедурному программированию, а значит ухудшение кода (усиление связанности -> уменьшение надежности). Я тут просто Егора Бугаенко yegor256 наслушался, теперь сомневаюсь в стратегии, куда идет язык…


        1. Scf
          15.11.2016 20:08
          +1

          Что значит "добавили"? Геттеры и сеттеры — это часть очень старой спецификации JavaBeans. Подробнее: JRE package java.beans
          http://docs.oracle.com/javase/tutorial/javabeans/
          http://www.oracle.com/technetwork/articles/javaee/spec-136004.html


          1. Scf
            15.11.2016 20:14

            гм, ссылка на tutorial немного не о том.


    1. potan
      12.11.2016 19:37

      Она очень упрощает жизнь. И программисту, и компилятору, и системе сборки мусора.


  1. mezastel
    12.11.2016 11:38
    -2

    Наверное, все уже согласны, что иммутабельные структуры данных — это Хорошая Идея.

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

    Иммутабельные структуры — не панацея, и в «бытовом» программировании они мало кому нужны.


    1. betsuni
      12.11.2016 12:04
      +3

      нет, они не мало кому нужны. с чего бы это?


    1. Akon32
      12.11.2016 12:19
      +7

      Иммутабельные структуры — это попытка убежать от изменения состояния объекта.


      Если объект мутабельный, для использования его методов может быть необходимо сначала выставить подходящее состояние объекта. Проще простого забыть это сделать (даже в однопоточной среде). В многопоточной среде это может быть или невозможно (если нет синхронизации), или медленно (если синхронизация есть). Иммутабельность неплохо решает эти проблемы.
      (Поэтому я считаю, что архитектура с иммутабельными объектами обычно лучше, даже если поток один).


      Однако, когда нужна высокая производительность при изменении больших объёмов данных в памяти, от иммутабельности (и многих фич из ФП) лучше отказаться.


    1. lgorSL
      12.11.2016 16:03
      +1

      Иммутабельные структуры — это хорошая идея просто потому, что они проще. Они ближе к математике. У них не могут испортиться инварианты, их можно спокойно шарить между разными объектами. Допустим, десять объектов ссылаются на одну и ту же строку, это экономит память и не усложняет код. Какие альтернативы? (Предположим, что "бытовой" string стал изменяемым)


      1. Хранить ссылки на общий объект и надеяться, что его никто не изменит. Вариант плохой, в java нет слова const и нельзя запретить менять объект.
      2. Хранить в каждом объекте копию изменяемого объекта — расходует память.
      3. Сделать обёртку над классом, которая не даст изменить объект. Хм. Где-то мы это уже видели…
        Причём неизменяемый объект лучше, чем обёртка над изменяемым. Иммутабельность утверждает, что объект не изменится в течении всего времени существования — это упрощает понимание программы.


      1. AndreyRubankov
        12.11.2016 17:10
        +1

        > Предположим, что «бытовой» string стал изменяемым

        Мы получим Ruby, PHP, C/C++ и много других. Не самые плохие из языков, довольно даже популярные))

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


    1. 23derevo
      13.11.2016 16:09

      в точку. Люто, бешено плюсую.


  1. Akon32
    12.11.2016 12:05
    +1

    Обычно scala критикуют за высокий порог входа, необходимый для использования этих (полезных) фич. Поэтому задам типичный вопрос: у вас сколько человек в команде?


    Если больше одного, ещё пара вопросов:


    1. Удобно ли вести командную разработку проекта на scala, когда приходится понимать и переписывать чужой код?
    2. Хотели бы вы писать на scala следующий проект?


    1. Scf
      12.11.2016 12:21
      -2

      Высокий порог входа в Scala — это, скажем так, заблуждение. Берет человек книжку programming in scala, дочитывает до описания системы типов и у него сворачиваются мозги. Отсюда и высокий порог. Изучать скалу с нуля тяжело просто потому, что в скале много языковых фич, полезных только разработчикам библиотек и апологетам ФП. В промышленном же программировании используется небольшое подмножество, не слишком усложняющее код.


      Вот хороший пример очень большого, но понятного проекта, написанного на скале: https://github.com/twitter/finagle
      И рекомендации по написанию качественного кода: http://twitter.github.io/effectivescala/


      по вопросам:


      • маленькие команды, но это не связано с языком.
      • да, если все члены команды уже переболели "давайте напишем этот метод как можно короче". Скала дает много способов написания нечитаемого кода, так что это язык для сеньоров.
      • да.


      1. Akon32
        12.11.2016 12:27

        Сколько людей в команде вы имеете в виду под "маленькие команды"? Можете указать числовой диапазон?


        1. Scf
          12.11.2016 12:28

          2-4


      1. AndreyRubankov
        12.11.2016 13:25
        +3

        > Скала… это язык для сеньоров

        Ага-ага, а java — для жалких джунов, которые даже с монадами не могут разобраться :-)


        1. Scf
          12.11.2016 13:28
          +1

          Java — это язык для всех.
          И у меня для вас новость — java.util.Optional является монадой. Так что не так страшны монады, как их малюют :-)


          1. stokker
            12.11.2016 15:25

            Всё декларативное программирование больше для сеньоров…


          1. guai
            12.11.2016 15:52

            с каких пор Optional — монада?
            монадой был бы код, трансформирующий некий набор функций, ничего не знающих об Optional, в такой, который знает и принимает их.
            Очевидно, такого в яве нет.


            1. Scf
              12.11.2016 16:00
              +3

              1. guai
                12.11.2016 19:25

                сдаётся мне, приведенные там законы монад — это необходимые, но не достаточные условия. К монаде, по идее, должны быть применимы те же операции, что и к исходному типу, чего нет у явавских Optional


                1. roman_kashitsyn
                  12.11.2016 23:07

                  монаде, по идее, должны быть применимы те же операции, что и к исходному типу
                  монадой был бы код, трансформирующий некий набор функций

                  Сдаётся мне, вы не понимаете, что такое монада. Монада — это параметризованный тип M[T], снабжённый операциями (pure(T): M[T], map(T => V): M[T] => M[V], join(M[M[T]]): M[T]), которые удовлетворяют законам.
                  Ничто не мешает использовать монады в java.


                  1. guai
                    12.11.2016 23:18
                    -1

                    это не монада, это тупо боксинг


                    1. ChShersh
                      13.11.2016 02:35
                      +1

                      Чтобы утверждать, будто «нечто» _НЕ_ является монадой, неплохо бы для начала определиться с тем, что такое монада. К сожалению, очень мало программистов понимают, что это такое на самом деле. Почему так произошло, и откуда пошли слухи, что «монада» — лютый зверь, мне неизвестно. Это всё происходило, когда меня ещё не было.

                      Самым точным определением монады является формальное математическое определение из теории категорий: Монада — моноид в категории эндофукторов. А только потом данную концепцию перенесли в языки программирования, потому что она оказалась полезной. К сожалению, с пониманием этого определения среди программистов возникает другая проблема: очень мало людей среди них разбираются в теории категорий. И многие слышали про монады через какие-то слухи, мнения других, свой опыт, подобные комментарии на хабре, etc. И в итоге выходит, что люди спорят ниочём: либо тролли, либо показывают своё невежество, либо кормильцы троллей, либо пытающиеся образумить невежд.

                      В итоге получается, что многие программисты пытаются объяснить монады через какие-то примеры, куски кода, конкретные подходы, частные случаи. Всё это выглядит в большинстве случаев как попытка натянуть слона за яйца на глобус (или как-то так, не помню точного оборота). С этой проблемой сталкиваюсь и я, когда пытаюсь объяснить монады студентам на лекциях в универе, ибо они теорию категорий тоже не проходили, к сожалению. Но, к счастью, объяснить монады всё же возможно через какие-то достаточно понятные, прикладные и знакомые концепции. А каждый частный пример не даёт достаточно полного понимания, либо содержит какие-то неувязочки. Вроде того, что «Optional в Java — монада». И у разных людей такие высказывания вызывают реакцию «Вроде бы да, но...» с разной степенью агрессивности :)


                      1. Scf
                        13.11.2016 02:37

                        Можно тогда ваше мнение о том, что такое монада? Монада в джаве, допустим, это:


                        public class Monad<T> {
                            private T value;
                            //методы, которые делают этот класс монадой
                        }

                        Пожалуйста, дополните этот код, чтобы класс Monad можно было назвать монадой.


                        1. ChShersh
                          13.11.2016 03:18
                          +1

                          Во-первых, по некоторым политическим и религиозным соображением в Java не определить монаду в полном смысле. Можно лишь некоторое приближение. Потому что как минимум система типов в Java недостаточно мощная, чтобы дать все гарантии на этапе компиляции, которые выполняются для монады. Поэтому реализация будет достаточно точной проекцией с учётом несовершенства системы типов в Java.

                          Во-вторых, класс монад в Java должен быть interface, а не class. Сама по себе «монада» не говорит, что в ней хранится. Она лишь говорит, что с ней можно делать. А уже конкретная реализация описывает, с какими данными предстоит работать. Следовательно, интерфейс монады с достаточно близким приближением можно описать подобным образом в Java:

                          public interface Monad<T> {
                              <R> Monad<R> bind(Function<T, Monad<R>> f);
                          }
                          


                          Вообще говоря, монада определяет ещё метод pure следующего вида:

                          public <T> Monad<T> pure(T x);
                          


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

                          Минимальная реализация чего-то похоже на Optional выглядела бы следующим образом:

                          public class MyOptional<T> implements Monad<T> {
                              private T value;
                          
                              public MyOptional() {}
                              public MyOptional(T value) {
                                  this.value = Objects.requireNonNull(value);
                              }
                          
                              public <R> Monad<R> bind(Function<T, Monad<R>> f) {
                                  if (value == null) {
                                      return new MyOptional<>(null);
                                  } else {
                                      return f.apply(value);
                                  }
                              }
                          }
                          


                          1. ChShersh
                            13.11.2016 03:47

                            Немного поторопился :(
                            Вместо

                            return new MyOptional<>(null);
                            


                            надо писать

                            return new MyOptional<>();
                            


                            Была бы очень неприятная ошибка. Ну, могу сказать, что какой-нибудь хороший язык вроде Haskell не дал бы её совершить уже на этапе компиляции :)


                      1. roman_kashitsyn
                        13.11.2016 02:57
                        +2

                        Помню, ко мне пришло осознание через аналогию с теорией групп. Людям непросто объяснить, что такое группа, потому что это понятие абстрактное. А на деле найди множество и операцию, проверь законы. Выполняются? Группа. Получи пачку теорем и алгоритмов бесплатно.
                        Ну и с монадами та же примерно история. Найди правильные функции, проверь законы. Сложнее всего начать видеть такие абстрактные концепции в конкретных вещах.


                      1. sshikov
                        13.11.2016 10:56

                        Дело просто в том, что вы описываете выше монады с точки зрения математика.


                        Для прикладного программиста (не изучавшего теорию категорий) в знании того, что "монада это моноид и т.д." обычно очень мало толку. Для него польза от монад состоит в том, что они дают удобные средства композиции программы из функций. И вот в этом-то смысле Optional в Java все-таки монада (хотя и «да, но» имеют место — потому что законы мы допустим проверить не можем). Почему такие объяснение имеют место? Да потому что имено в этом, в принципах построения программы при помощи монад, и состоит та польза, которую почти каждый может от них получить.


                  1. Source
                    13.11.2016 00:42

                    А мне сдаётся, что Вы путаете аппликативные функторы и монады :-)


                    1. roman_kashitsyn
                      13.11.2016 01:16

                      Вы путаете аппликативные функторы и монады :-)

                      Не путаю. Каждая монада по определению является функтором. И аппликативным функтором (pure == return). Покажите мне join :: m (m a) -> m a у аппликативного функтора.


                      1. Source
                        13.11.2016 02:07

                        Каждая монада по определению является функтором. И аппликативным функтором

                        Функтором — да, аппликативным функтором — как правило, да, но не по определению.
                        Да и какой смысл принуждать реализовывать bind через join fmap? Где Вы такое определение монад вычитали?


                        1. roman_kashitsyn
                          13.11.2016 02:45
                          +1

                          Да и какой смысл принуждать реализовывать bind через join fmap?

                          Кого принуждать? Я вроде никого не принуждаю, просто привожу одно из возможных определений. С моей точки зрения, join и fmap гораздо проще для понимая, чем bind, который, кстати, в Scala зовётся flatMap, что буквально означает композицию join и fmap.
                          Более того, в теории категорий монада обычно определяется через join и map, это адекватно отражает моноидальную структуру.


                          Где Вы такое определение монад вычитали?

                          Да где угодно, хоть википедию откройте:
                          https://en.wikipedia.org/wiki/Monad_(functional_programming)#fmap_and_join
                          https://en.wikipedia.org/wiki/Monad_(category_theory)


                          Ну и вот тоже занятное чтиво
                          http://anton-k.github.io/ru-haskell-book/book/15.html#монады


    1. potan
      12.11.2016 19:53

      Расскажу про себя. Над Scala-проектами (микросервисами) работают 6 разработчиков и 2 тестера (использующих питон). Двое разработчиков еще участвуют в проектах на C#.

      1. Удобно. Недавно к нам пришли 2 джависта и освоили Scala до уровня «исправить баг» очень быстро. Самая большая проблема была с фьючами — в автотестах, которые пишут сами разработчики мы слишком ими увлеклись и начинающим было трудно в них разобраться.

      2 Да. Мы даже постепенно переписываем старые Java-сервисы.


  1. kungfu
    13.11.2016 10:56
    +1

    Java или Scala в целом без разницы. Язык возможно критичен для кодера, но не нормального Программиста с большой буквы, у которого время затрачиваемое на разработку по большей части расходуется на грамотное продумывание дизайна классов, интерфейсов, алгоритмов, а написать сам код как правило не составляет труда на любом языке в сходной весовой категории, ну напишет он метод на Java на 2 минуты дольше — ничего страшного, да хоть на 30 минут дольше, главное чтобы руки росли из нужного места и это всё потом работало качественно и долго.


    1. Saffron
      15.11.2016 12:44
      +1

      Если язык не важен, то почему вы ООП программируете на джаве, а не на фортране?


    1. AndreyRubankov
      15.11.2016 13:41
      +3

      > время затрачиваемое на разработку по большей части расходуется на грамотное продумывание дизайна классов, интерфейсов, алгоритмов

      Это без сомнения так, но дизайн классов и интерфейсов, а так же выбранные алгоритмы уже непосредственно будет зависеть от языка и его философии.