Дальнейшее сравнение между языками исходит из того, что читатель знаком со следующими вещами:
— Java8. Без поддержки лямбд и говорить не о чем
— Lombok Короткие аннотации вместо длинных простыней геттеров, сеттеров, конструкторов и билдеров
— Guava Иммутабельные коллекции и трансформации
— Java Stream API
— Приличный фреймворк для SQL, так что поддержка multiline strings не так и нужна
— flatMap — map, заменяющий элемент на произвольное количество (0, 1, n) других элементов.
Иммутабельность по умолчанию
Наверное, все уже согласны, что иммутабельные структуры данных — это Хорошая Идея. Scala позволяет писать иммутабельный код, не расставляя `final`
@Value
class Model {
String s;
int i;
}
public void method(final String a, final int b) {
final String c = a + b;
}
case class Model(s: String, i: Int)
def method(a: String, b: Int): Unit = {
val c: String = a + b
}
Блок кода, условие, switch являются выражением, а не оператором
Т.е. всё вышеперечисленное возвращает значение, позволяя избавиться от оператора return и существенно упрощая код, работающий с иммутабельными данными или большим количеством лямбд.
final String s;
if (condition) {
doSomething();
s = "yes";
} else {
doSomethingElse();
s = "no"
}
val s = if (condition) {
doSomething();
"yes"
} else {
doSomethingElse();
"no"
}
Pattern matching, unapply() и sealed class hierarchies
Вы когда-нибудь хотели иметь switch, работающий с произвольными типами данных, выдающий предупреждение при компиляции, если он охватывает не все возможные случаи, а также умеющий делать выборки по сложным условиям, а не по полям объекта? В 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
} //если вдруг этот матч не охватывает все возможные значения, компилятор выдаст предупреждение
Набор синтаксических фич для поддержки композиции
Если первыми тремя китами ООП являются (говорим хором) инкапсуляция, полиморфизм и наследование, а четвертым агрегация, то пятым китом, несомненно, станет композиция функций, лямбд и объектов.
В чем тут проблема джавы? В круглых скобочках. Если не хочется писать однострочники, то при вызове метода с лямбдой придется заворачивать её дополнительно в круглые скобки вызова метода.
//допустим у нас есть библиотека иммутабельных коллекций с методами 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
});
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 позволяет явно указывать названия аргументов при вызове методов, а также поддерживает значения аргументов по умолчанию. Вы когда-нибудь писали конверторы между доменными моделями? Вот так это выглядит в скале:
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.
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)
stokker
11.11.2016 21:53-27У Java и Scala разные ниши (Java — аутсорс, малый и местный бизнес, Scala — глобальные продуктовые компании), поэтому пофичечное сравнение не очень корректно.
sleeply4cat
11.11.2016 21:56+21Вы точно не поменяли сферы применения местами? 0_о
stokker
12.11.2016 00:30-9Scala — малый и местный бизнес? Действительно смешно… Про Scala в аутсорсе просто не верю… Значит это ниши Java ))
Scala позволяет писать большие и сложные проекты ОДНОЙ командой быстрее, чем Java (хотя раньше за неимением такой альтернативы Java была практически монополистом). Поэтому продуктовая IT-контора. И глобальная потому что иначе и смысла нет напрягаться.
Я не против минусов, но хотелось бы и доводы услышать…potan
12.11.2016 19:25Я работаю в небольшой компании (примерно 50 человек), в отделе, который разрабатывает облачную PLM-систему. Вполне себе средний бизнес. Практически все наши сервисы написаны на Scala.
Scf
11.11.2016 21:57+6Как человек, видевший и стартапы на скале, и глобальные продуктовые компании на джаве, не соглашусь. Разве что Scala-аутсорс — это звучит странно.
stokker
12.11.2016 00:32-2А разве «стартапы на скале» противоречат моему высказыванию? А «глобальные продуктовые компании на джаве» — это скорее след из прошлого…
gkislin
12.11.2016 01:23+3Вы посмотрите статистику продуктовых проектов на scala и на java и поймете, почему наминусили. А что "Java — аутсорс, малый и местный бизнес" это вообще трудно откоментировать так, чтобы не обидеть.
stokker
12.11.2016 01:49-1Постарайтесь… Добавлю только, что про ниши я говорил в некоторой перспективе (думал, что и так понятно).
Sirikid
11.11.2016 21:58-2Ну почему вы не хотите писать однострочники мистер Андерсон? http://pastebin.com/Db9kYmLr
Scf
11.11.2016 22:04Потому, что однострочники не масштабируются — при усложнении кода они становятся… ммм… менее читабельными. Пока лямбду можно уложить в поток стримов, это хорошо. А когда нужно будет её усложнить? Код на стримах придется переписывать.
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; }
дальше видимо можно не читать...
Scf
11.11.2016 22:11-3Спасибо, сейчас поправлю :-) Два года на скале сказываются — начал подзабывать уже.
UbuRus
11.11.2016 22:54+11Если это все что хочется от Скалы, то можно взять Котлин
potan
12.11.2016 19:28+2В Котлине таки сделали pattern matching?
UbuRus
12.11.2016 21:52+1Того что уже есть в языке хватает.
saksmt
15.11.2016 10:33Ещё нет, но в 1.1 обещали ограниченные продолжения и нормальную деструктуризацию, вот тогда будет как раз хватать :)
UbuRus
15.11.2016 17:47Речь была о control-flow.
Ну и «продолжения» для меня диковато звучат. «Континуации» наверно не лучше, зато есть принятые «корутины». Так что да, будут Корутины в Котлин.
grobitto
11.11.2016 23:07+9Все эти Java vs Scala, Coffeescript vs Javascript итд к сожалению обычно заканчиваются печально для тех, кто выбирает молодые языки.
Рано или поздно под давлением комьюнити все нужные фичи появляются в базовых языках и через какое-то время конкуренты начинают умирать.
О Скала последнее время слышно все меньше и меньше, на мой взгляд она уже прошла свой пик, и дальше будет только хуже. Не знаю, имеет ли стратегический смысл вкладываться в Скала.
Вы не подумайте, что я считаю ее плохой — она действительно СЕЙЧАС удобнее Java. Но за долгие годы карьеры мне приходилось несколько раз ставить «не на ту лошадь», что выливалось в серьезные проблемы впоследствии.Scf
11.11.2016 23:20+2Вы правы, но я специально подобрал для статьи фичи, которые не так-то просто добавить в существующий синтаксис Java и существующую идеологию Java. К тому же, Java развивается ОЧЕНЬ медленно — оракл не может себе позволить добавить в язык что-то трендовое и не проверенное временем.
Если lightbend не разорится, на 5-10 лет активного развития языка рассчитывать можно.
grobitto
11.11.2016 23:28+2А дело не в том, можно или нет добавить какие-то фичи. Просто в какой-то момент «фич» Скала станет недостаточно, чтобы привлекать новых людей в комьюнити, и язык начнет умирать.
AndreyRubankov
12.11.2016 00:14+1Языковые фичи (синтаксический сахар) — это самый плохой повод выбирать тот или иной язык программирования.
Вот, к примеру, Go — языковых фич в нем минимум! и это невероятно круто!
Чем меньше фич, чем меньше долбанной магии под капотом — тем лучше будет проектироваться и работать вся система в целом.
В java довольно много фич, действительно лишних фич: strictfp, assert, instanceOf, labels (да-да, типа таких как для goto). Если бы Оракл вводил все подряд на уровень языка — java уже давно умерла бы. Даже сейчас для новичком Java — это тяжелый язык (и платформа), если бы в ней было бы больше фич — этот язык был бы фактически неподъемным.Sirikid
12.11.2016 00:40+1Почему вы считаете instanceof «лишней фичей» и что предлагаете взамен? (метод с тем же смыслом в Object? Class#isInstance?) Тот же вопрос с метками.
AndreyRubankov
12.11.2016 00:57+3instanceOf — плохой оператор в целом. Вариант через API на много лучше. НО!
Я имел ввиду, что instanceOf — это в принципе устаревшая конструкция. С появлением дженериков в ней пропала необходимость.
Если нужно завязать логику на конкретный тип — используйте либо дженерики, либо перегрузку методов.
99% задач не требуют проверки типа. Тем более, это проверка не на конкретный тип, а на принадлежность иерархии.
Единственная причина использовать instanceOf — это переопределение equals метода в иерархии классов. Но это в принципе плохой подход к построение архитектуры. Иерархия data-классов ни к чему хорошему еще не приводила, тем более с переопределенным equals.guai
12.11.2016 15:31+1Дык, эээ…
Вы о явавских дженериках?
Переусложненных, испаряющихся в рантайме, use-site variance, и даже при своей переусложненности не покрывающих всех кейсов.
Они не могут на 100% отменить необходимость в instanceOf
Но тем не менее назвать их плохим подходом к построению архитектуры нельзя. Просто когда их делали, на принятие решений очень сильно влиял фактор совместимости. А делали их офигенные инженеры, архитектура там из возможной самая оптимальная.AndreyRubankov
12.11.2016 16:40Вообще-то я говорил, что instanceOf — это признак плохо спроектированного кода, а не дженерики.
> Они не могут на 100% отменить необходимость в instanceOf
Дженерики — не покрывают, это известный факт, но ведь еще есть и перегрузка методов.
Был бы рад услышать пример, когда instanceOf действительно необходим, может я уперся в стену и не вижу всей картины.guai
12.11.2016 17:12перегрузка методов — одно из наиболее неудачных решений в дизайне явы.
из-за перегрузки рефлекшеном пользоваться крайне неудобно и трудноотлавливаемые косяки с подменой методов при эволюции апи.
ну а instanceof необходим, очевидно, в рантайме, где никаких дженериков не остаётся.AndreyRubankov
12.11.2016 18:39С другой стороны reflection — это уже способ вылезти за приделы языка, не так ли?
Код с рефлексией толком не оптимизаруется через jit.
Теряется практически весь смысл строгой типизации — ошибок на этапе компиляции не будет (если не через annotation processing), зато будут проблемы в рантайме.
Reflection — это не то, что стоит использовать повсеместно.
А на счет instanceof, честно, не могу придумать примера для адекватного его использования, даже с дженериками — проверять параметризированный тип через instanceof?))
ps: если перегрузка — это плохо для рефлексии, то если будут дефолтные параметры (вместе с перегрузкой) — можно будет сразу повеситься =)guai
12.11.2016 19:10+2я не вижу способа не иметь instanceof и иметь настолько же богатые возможности, как в яве. в любой известной мне системе типов можно прикастить объект к своему родителю или к интерфейсу. и потом надо как-то получать его реальный тип.
иначе любая потеря информации о типе будет распространяться, нет возможности ее восстановить. ну и писать код так, чтобы ее не терять — это надо быть нечеловечески внимательным. и любой затык с апи будет приводить к тому, что или идем к авторам апи и просим его поменять, или всё, облом, нужную работу мы не сделаем.
а еще, думаю, будут образовываться такие дженерики дженериков дженериков, что повеситься.
сериализация, подозреваю, вообще не будет возможна, в таком случае.AndreyRubankov
12.11.2016 20:18-1> и потом надо как-то получать его реальный тип.
В этой строчке кроется корень проблемы. Если вы передаете куда-то наследника какого-то класса, то по принципу Лисков (liskov substitution, L из SOLID) система должна будет работать так же.
Если вкратце, любой код, который принимает какой-то базовый класс не должен никоим образом завязываться на конкретную имплементацию этого базового класса.
Для сериализации instanceof не подойдет, как минимум перебирать все возможные типы данных — это глупо. Да и instanceof показывает принадлежность к целой иерархии типов, а не к конкретному классу. Для сериализации используют стандартную java сериализацию (Serializable или Externalizable) или Reflection API.guai
12.11.2016 21:02А можно пример такой платформы, в которой бы не было бы инстансоф, но было всё остальное, что есть в яве?
Ну я пока не придумал кейсов, которые не реализуемы вообще без инстансоф, но такие, где всё становится проще, — пришли на ум.
Пример: пайплайн объектов, состоящий из неких продюсеров данных, фильтров, размножалок, консюмеров и т.д. И я хочу их соединять в цепи в рантайме.
Можно на дженериках попытаться замутить, но я не уверен, что не уткнусь в косяки реализации явавских дженериков. Плюс в рантайме они пропадут. А так просто ходят объекты неизвестного типа. Каждый элемент пайплайна, если ему важна внутренняя кухня объекта, тестит его тип. Умеет с ним работать — работает, нет — скипает, или ругается. Можно не задумываться, какие там типы вообще ходят, мне главное поймать свои и обработать.AndreyRubankov
12.11.2016 21:16-1В текущем треде вы уже который раз перекручиваете мои слова.
Я говорил, что instanceof — плохо, дженерики — хорошо.
Вы перекрутили так, будто я говорил обратное. Не нужно так =)
На счет пайплайна.
Если ваши обработчики должны принимать какие-то решения в зависимости от того типа данных, который к ним приходит — просто заведите в интерфейсе какой-то Enum / EnumSet атрибут, по которому будете принимать решение. Это будет более явный и правильный подход к обработке, чем завязка на тип реализации.guai
12.11.2016 21:22Ну то есть инфа о типе всё-равно нужна, только вы за то, чтобы ее неявно пропихивать рядом, когда это надо, а не иметь всегда? Тут я ничего не перекрутил?
Ну профита я в этом пока не увидел.AndreyRubankov
12.11.2016 21:32Не перекрутили, но смысл не полностью уловили.
Вместо того, чтобы завязываться на какой-то конкретный тип реализации вы будете завязываться на публичный API. Это идеологически разные подходы.guai
12.11.2016 21:47Да вроде уловил…
Только мне не хочется зависеть от качества апи. Решит дизайнер апи так же, как вы, что тест на тип не нужон, и останусь я куковать.
dougrinch
13.11.2016 02:21Я говорил, что instanceof — плохо, дженерики — хорошо.
Но, подождите, в рефлекшене есть методы getClass, getSuperclass, getInterfaces. Что мешает мне руками написать instanceOf(Object, Class)?
guai
12.11.2016 21:13вот event bus еще, например:
Подписался я на эвенты и реагирую только на какие-то нужные, ну да, можно не на тип теститься, а дополнительный суррогат типа пропихивать вместе с эвентом. Но нафига, если можно просто тестануться на тип.
Чем хуже с истансоф, я не понимаю, и зачем отказываться от него из-за какого-то абстрактного высшего блага — тоже.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 обеспечил бы безопасность типов, но если кто-то унаследуется от вашего класса, и кинет сообщение, то вы будете обрабатывать события, которые не должны были обрабатывать и ошибка в этом случае не вывалится (а это еще хуже, чем сразу свалиться).
Зачем кому-то наследоваться от вашего события?
Да просто потому, что там есть такие же поля (для тех, кто не знает принципиальной разницы между Абстрактным классом и Интерфейсом — это нормально дело).
potan
12.11.2016 19:31А зачем пользоваться рефлекшеном?
AndreyRubankov
12.11.2016 19:57К примеру, чтобы обрабатывать аннотации.
На этапе компиляции можно по аннотациям сгенерировать дополнительные методы, обертки или кучу всяких классных (или не очень) вещей. Так делают lombok — позволяет уменьшить количество бойлерплейт кода; и mapstruct — маппер одних бинов на другие.
Так же в рантайме, для добавления гибкости. Spring, Hibernate, JPA, JAX-RS, Guice и множество других.
Или для написания фреймворков с контрактом именования методов. Spring Data, некоторые OSGi фреймворки, junit 3.x и много других.
guai
12.11.2016 20:04Ну затем, что есть целый пласт проблем, который им решается вполне успешно и довольно просто
Scf
12.11.2016 21:58- рефлекшн. в частности, сериализация
- тот самый случай, когда проще написать instanceof чем добавлять методы в интерфейс/городить паттерн Визитор.
AndreyRubankov
12.11.2016 01:11+2По поводу меток тоже не сильно далеко все ушло.
Мекти в java есть только в двух операторах break и continue.
Предназначены, чтобы выйти из большой вложенности циклов.
Но ведь большая вложенность циклов — это уже плохо спроектированный код. А с метками этот код станет только хуже!
Но если так исторически сложилось и нужно досрочно выйти из большой вложенности циклов, можно же эти циклы обернуть в один метод и просто выйти из него через return. Это не сильно ударит по производительности, но код будет лучше читаться.
ps: я еще ни разу не встречал метки в реальных java проектах, зато видел много java разработчиков, которые даже не знают про их существование.Sirikid
12.11.2016 01:16Все, защитано. Я метки применял один раз, правда там была просто головоломная задачка.
IIvana
12.11.2016 02:52-2Наличие instanceOf делает Java (как и C#) лично мне на порядок удобнее — добавляет языкам некоторые свойства динамических при сохранении статического контроля типов где надо. Я не претендую на знатока языка, но в моем единственном проекте на Java половина типов — Object. Вот так незамысловато получаем динамическую типизацию, полиморфные контейнеры и вообще полную свободу действий. Вы скажете — что это овноархитектура и овнокот и что надо на дженериках/интерфейсах или что там еще? Но мне так гораздо удобнее.
AndreyRubankov
12.11.2016 03:46Что ж, если вы строите решение на Object + instanceOf и говорите, что это удобно, возможно так оно и есть. Люди разные, кто-то на гвоздях спит, кто-то стекло есть, кто-то Object + instanceOf повсеместно использует. У каждого своя правда!
ps: тред про instanceOf был немного выше.
APXEOLOG
12.11.2016 12:10+4Не совсем понятно как большая вложенность циклов обязательно означает плохо спроектированный код. Есть много алгоритмов в которых никак не уйти от вложенных циклов. Можно конечно обернуть все вокруг в методы, постоянно проверять результаты их работы и т.д. но зачем городить лапшу, когда достаточно проставить break label.
Я не говорю что у меток широкий спектр применения, но иногда они делают ваш код понятнее, чем если бы вы их не использовали.AndreyRubankov
12.11.2016 12:40Да, вы правы! Совсем упустил этот момент. Не часто приходится сталкиваться с реализацией сложных алгоритмов.
Но на счет лапши из вызова методов — это спорный вопрос, чаще это дает возможность легче читать код. Хотя в случае тех же сложных алгоритмов, вызов методов будет просаживать производительность.
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.
andr1983
12.11.2016 01:05+1Лично я, в последнее время, слышу о скала как раз достаточно много, и вижу, что язык развивается и достаточно быстро. Недавно вышла Scala 2.12, есть Scala Native, ScalaJS и прочие. Ошибочно относиться к скале, как к Java++. Это совершенно другой язык, со своей философией и подходом к разработке. Конечно, можно писать код так, как писали бы его на джава, но это, в итоге, будет причинять только боль. И потом будут появляться статьи из серии, почему я бросил скалу и вернулся на джаву.
Так что, сколько бы плюшек в джаву не добавили — это не имеет значения, так как языки по своей природе разные.grobitto
12.11.2016 01:25Каждый язык живет, пока люди им пользуются. Мало того, что Java становится приятнее, так еще появляются другие конкуренты, тот же Kotlin.
Churn никуда не деть, и если люди уходят активнее, чем приходят — это стагнацияandr1983
12.11.2016 02:19+1А кто сказал, что со скалы люди уходят? Мне, кажется, наоборот, за последний год появилось достаточно много новых вакансий. По крайней мере на LinkedIn отбоя от рекрутеров нет и, периодически, попадаются предложения от серьезных кампаний. В сфере бигдаты скала уже практически стандарт, благодаря Spark и Flink.
Я вот не считаю, что Kotlin прямой конкурент Scala. Как раз он более походит на Java++ и у него значительно больше шансов пойти дорогой Coffeescript.xhumanoid
12.11.2016 13:55+2spark — усиленно подтягивает 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 отбоя от рекрутеров нет и, периодически, попадаются предложения от серьезных кампаний.
Систематическая ошибка внимания и Феномен Баадера-Майнхоф
=) сам склонен к этим симптомам
я больше склонен к позиции: больше людей начинают учить скалу
но нужно учесть, что наличие и знание других языков не означает повседневное его использование, так как многие вещи учишь только ради расширения своих знаний
DarkEld3r
12.11.2016 23:57+3Все эти Java vs Scala, Coffeescript vs Javascript итд к сожалению обычно заканчиваются печально для тех, кто выбирает молодые языки.
Может, конечно, смотрю со своей колокольни, но не особо понимаю. Ну то есть, если пишешь на скале, то ведь и с джавой худо-бедно ознакомишься. Опять же, о ком мы говорим? Если это "молодой специалист", который толком опыта не имеет и ему удалось работу на скале найти, то сомневаюсь, что он пропадёт. А если это человек с опытом решил переквалифицироваться, то тем более.
Или предполагается, что человек без опыта хочет стать программистом и усиленно занимается изучением "молодого языка"? В этом случае, и правда стоит посмотреть на окружающие условия, но как по мне, это несколько особый случай.
eXTreMeHawk
13.11.2016 09:37+1> Рано или поздно под давлением комьюнити все нужные фичи появляются в базовых языках и через какое-то время конкуренты начинают умирать.
В Java фитчи из FP языков, например таких как Scala, не могут появится by design. Это даже теоретически невозможно без тотального слома обратной совместимости. Это как сделать из запорожца реактивный самолёт последнего поколения.
> О Скала последнее время слышно все меньше и меньше,
Да что вы говорите!? Читаю эти строки вернувшись пару часов назад с конференции Scala By The Bay, которая сейчас проходит в San Francisco :-) :-) Огромное кол-во компаний: от гигантов индустрии до стартапов…
rkfg
13.11.2016 12:14Выбирать немейнстрим для чего-то крупного или потенциально крупного — это всегда большой риск. Через годы тренды сменятся, а кодовая база останется, и поддерживать её может оказаться некому. Всякое случается. Тут мне сразу вспомнилась вот эта статья, потому что как-то раньше не приходилось читать про элемент языка, который вообще непонятно что делает. Это забавно, но только в том случае, если не тебе приходится разгребать ситуацию. С выводом в статье о переходе на Go я лично, впрочем, не согласен, но это уже чисто моё мнение. Сам сделал ставку на Java и C++, которые точно никуда не денутся в обозримом будущем, и языковая/библиотечная мощность которых меня на данный момент полностью устраивает.
Из JVM-языков часто упоминают Kotlin, но почему-то совсем ничего не слышно про Ceylon, который тоже очень-очень неплох. Делается RedHat'ом и имеет поддержку в Eclipse, что логично, тогда как у Kotlin приоритетна IDEA.
Scf
13.11.2016 12:49+2Это не элемент языка. Это элемент дизайна языка, позволяющий создавать операторы из произвольного набора символов пунктуации и элемент библиотеки, которая этим злоупотребляет.
rkfg
13.11.2016 13:26Безусловно, как и перегрузка операторов в C++. Это мощная концепция, которую можно употребить очень неправильно, в итоге запутав код и сделав его неподдерживаемым. Мне кажется, этим и отличаются языки мощные/крутые/красивые от языков промышленных (C++ под этот критерий не очень подходит, согласен, но всё же является промышленным) — в последних дизайн языка требует писать скучно, уныло и абсолютно однозначно. Не в чем запутаться, код не блещет изящными решениями и потому может поддерживаться годами без особых усилий. Конечно, это лишь моё видение.
Saffron
14.11.2016 20:34+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 переходили, а такую глупость пишите.monolithed
12.11.2016 00:07Даже я обратил на это внимание, хотя хотя мое знакомство с Java очень поверхностное.
zoonman
12.11.2016 02:56+2Может мне кто-нибудь объяснить, за что люди так любят иммутабельность?
AndreyRubankov
12.11.2016 04:07+1Если по простому — это сейчас модно!
Идет в поставке вместе с чистыми функциями, лямбдами и монадами =)
преимущество: ты его не можешь изменить, значит можешь свободно расшарить между потоками и не бояться, что у тебя возникнут проблемы синхронизации или гонки. Единственный способ изменить объект — создать новый. А это удобно для отладки и т.д. (так говорят).
недостаток: но теперь, чтобы изменить значение нужно создать копию объекта с 1 измененным полем;
чтобы заполнить один более-менее большой объект с данными нужно будет создать десяток копий, которые будут лежать на стеке и не будут собираться gc, пока стек не развернется (или создать изменяемый объект-билдер, который позволит создавать большие неизменяемые объекты).ExplosiveZ
12.11.2016 07:52-1Какой-то добровольный выстрел в ногу… А потом сами жалуются, что jvm много ОЗУ требует.
AndreyRubankov
12.11.2016 13:07Иммутабильность — это не только в jvm.
А вот нападки на jvm потому, что она расходует памяти на что-то «левое». На мониторинг, на выравнивание памяти, на jit статистику, на gc (в некоторых крайних случаях, лабораторки в универе, gc может требовать памяти больше, чем сама программа).
Все это требует потребления большего количества памяти, но в результате дает возможность увеличить производительность всей системы (в долгосрочной перспективе).
Source
12.11.2016 12:25но теперь, чтобы изменить значение нужно создать копию объекта с 1 измененным полем;
чтобы заполнить один более-менее большой объект с данными нужно будет создать десяток копийНе могу сказать как это в Scala реализовано, но в большинстве функциональных языков копирования не происходит, а общая часть структуры просто переиспользуется для второго объекта.
AndreyRubankov
12.11.2016 13:17Так это и есть копирование. Копируется не все дерево объектов, копируется ссылка на поддерево (или значение для примитивов).
Source
12.11.2016 22:40Так в чём копирование то? Если остальные поля объекта остались как есть, там где были, а изменённое поле стало существовать в двух ипостасях — старое значение и текущее значение.
Имхо, слово "копирование" подразумевает дубликацию одних и тех же значений, а этого как раз не происходит.AndreyRubankov
12.11.2016 23:09-1Копирование это не дублирование.
Есть объект A правим какое-то значение, получаем новый объект A' (копируем все поля из A в A', одно заменяем), это в разы дешевле, чем глубокое копирование, но все же тоже копирование.
23derevo
13.11.2016 16:05а теперь мы вспоминаем слова «многопоточность», «shared state» и «инвариант» и пытаемся понять, что делать со всей этой хренью в случае нескольких потоков. Кроме того, вспоминая слово «locality» мы понимаем, что вся локальность в вашей схеме теряется, все становится менее cache-friendly, начинаются походы в память, перфоманс начинает проседать и т.д. и т.п.
Реально рецепт один — надо мерять. Что дороже — блокировки на изменяемых объектах или схема на CAS-loop'ах — в общем случае совершенно непонятно.
potan
12.11.2016 19:40Билдер обычно реализуют с помощью цепочки вызовов. При этом на стеке ни чего не накапливается.
AndreyRubankov
12.11.2016 20:03Но ведь билдер — это уже изменяемая структура данных.
И это единственный адекватный способ создать большой неизменяемый объект избежав лишнего копирования.
Flux
12.11.2016 06:44+2Потому что концепция иммутабельности выглядит как волшебная палочка позволяющая забыть о проблемах многопоточности и «неконсистентности» данных, если не обращать внимания на то что все это реализуется на существующем сегодня железе, а значит является обеткой над мутабельной памятью.
Еще, если заняться интенсивным аутотренингом, можно убедить себя что мы живем в stateless мире к которому иммутабельные данные очень подходят.
В итоге это обычно выливается в reverse массива за O(n * log n) или грязные хаки, когда данные вообще иммутабельные, но если сильно нужно — мутабельные.
В общем, выглядит замечательно пока думаешь о структурах с двумя полями, и жутко неудобно в реальном мире.
Scf
12.11.2016 11:35+3Прежде всего за упрощение кода. Допустим у нас есть два одинаковых класса — один иммутабельный (все его поля заполняются через конструктор, для модификации нужно создать другой экземпляр класса), а второй — мутабельный, т.е. его состояние можно менять через сеттеры. Теперь смотрите разницу:
- поля класса часто бывают зависимы, к примеру, в нашем классе должно быть заполнено или поле А, или поле B. В мутабельном классе невозможно гарантировать этот инвариант — для смены состояния потребуется два вызова сеттеров. Можно сделать отдельный метод, можно написать в сеттерах код, проверяющий инварианты, можно, как в спринге, сделать отдельный метод инициализации, который будет проверять инварианты. Но всё это значительно сложнее, чем конструктор с простой проверкой
- Атомарность внесения изменений. Для иммутабельных структур данных изменения "применяются" только тогда, когда новый объект будет вставлен вместо старого. Помимо уже упоминавшейся многопоточности, это позволяет оставить программу в неразрушенном состоянии при возникновении исключения в процессе внесения изменений
- Иммутабельный код проще понимать. Когда ты знаешь, что эти переменные final и не могут быть изменены, можно рассматривать меньше вариантов поведения программы при чтении и "вычитке" кода
- Модульность и тестирование. Если состояние какого-то модуля иммутабельное, то для смены этого состояния нужно построить новое дерево объектов, что, как правило, делается одним методом или конструктором. Куда можно вписать логгирование, который можно вызывать из тестов для воспроизведения нужного состояния и т.п.
- Хорошо работает с чистыми функциями(результат выполнения которых зависит только от входных параметров). Чистые функции легче писать, понимать, тестировать и поддерживать.
stokker
15.11.2016 19:56А теперь объясните мне пожалуйста, зачем в Java добавили сеттеры и геттеры. По-моему, это откат к процедурному программированию, а значит ухудшение кода (усиление связанности -> уменьшение надежности). Я тут просто Егора Бугаенко yegor256 наслушался, теперь сомневаюсь в стратегии, куда идет язык…
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
potan
12.11.2016 19:37Она очень упрощает жизнь. И программисту, и компилятору, и системе сборки мусора.
mezastel
12.11.2016 11:38-2Наверное, все уже согласны, что иммутабельные структуры данных — это Хорошая Идея.
Нет, не согласны. С чего это? Немутамебльные структуры являются хорошей идеей только тогда, когда подразумеваются сценарии многопоточного доступа к данным и вы действительно можете себе позволить забрасывать управляемую кучу новыми порожденными объектами на каждый чих. На практике, иммутабельные структуры — это попытка убежать от примитивов синхронизации и прочих штук, которые специально созданы для многопоточности, потому что они, видите ли, сложны в использовании.
Иммутабельные структуры — не панацея, и в «бытовом» программировании они мало кому нужны.Akon32
12.11.2016 12:19+7Иммутабельные структуры — это попытка убежать от изменения состояния объекта.
Если объект мутабельный, для использования его методов может быть необходимо сначала выставить подходящее состояние объекта. Проще простого забыть это сделать (даже в однопоточной среде). В многопоточной среде это может быть или невозможно (если нет синхронизации), или медленно (если синхронизация есть). Иммутабельность неплохо решает эти проблемы.
(Поэтому я считаю, что архитектура с иммутабельными объектами обычно лучше, даже если поток один).
Однако, когда нужна высокая производительность при изменении больших объёмов данных в памяти, от иммутабельности (и многих фич из ФП) лучше отказаться.
lgorSL
12.11.2016 16:03+1Иммутабельные структуры — это хорошая идея просто потому, что они проще. Они ближе к математике. У них не могут испортиться инварианты, их можно спокойно шарить между разными объектами. Допустим, десять объектов ссылаются на одну и ту же строку, это экономит память и не усложняет код. Какие альтернативы? (Предположим, что "бытовой" string стал изменяемым)
- Хранить ссылки на общий объект и надеяться, что его никто не изменит. Вариант плохой, в java нет слова const и нельзя запретить менять объект.
- Хранить в каждом объекте копию изменяемого объекта — расходует память.
- Сделать обёртку над классом, которая не даст изменить объект. Хм. Где-то мы это уже видели…
Причём неизменяемый объект лучше, чем обёртка над изменяемым. Иммутабельность утверждает, что объект не изменится в течении всего времени существования — это упрощает понимание программы.
AndreyRubankov
12.11.2016 17:10+1> Предположим, что «бытовой» string стал изменяемым
Мы получим Ruby, PHP, C/C++ и много других. Не самые плохие из языков, довольно даже популярные))
Но в целом согласен с тем, что неизменяемые структуры — это хорошо! Но не стоит их использовать повсеместно, фанатизм еще ни к чему хорошему не приводил.
Akon32
12.11.2016 12:05+1Обычно scala критикуют за высокий порог входа, необходимый для использования этих (полезных) фич. Поэтому задам типичный вопрос: у вас сколько человек в команде?
Если больше одного, ещё пара вопросов:
- Удобно ли вести командную разработку проекта на scala, когда приходится понимать и переписывать чужой код?
- Хотели бы вы писать на scala следующий проект?
Scf
12.11.2016 12:21-2Высокий порог входа в Scala — это, скажем так, заблуждение. Берет человек книжку programming in scala, дочитывает до описания системы типов и у него сворачиваются мозги. Отсюда и высокий порог. Изучать скалу с нуля тяжело просто потому, что в скале много языковых фич, полезных только разработчикам библиотек и апологетам ФП. В промышленном же программировании используется небольшое подмножество, не слишком усложняющее код.
Вот хороший пример очень большого, но понятного проекта, написанного на скале: https://github.com/twitter/finagle
И рекомендации по написанию качественного кода: http://twitter.github.io/effectivescala/
по вопросам:
- маленькие команды, но это не связано с языком.
- да, если все члены команды уже переболели "давайте напишем этот метод как можно короче". Скала дает много способов написания нечитаемого кода, так что это язык для сеньоров.
- да.
AndreyRubankov
12.11.2016 13:25+3> Скала… это язык для сеньоров
Ага-ага, а java — для жалких джунов, которые даже с монадами не могут разобраться :-)Scf
12.11.2016 13:28+1Java — это язык для всех.
И у меня для вас новость — java.util.Optional является монадой. Так что не так страшны монады, как их малюют :-)guai
12.11.2016 15:52с каких пор Optional — монада?
монадой был бы код, трансформирующий некий набор функций, ничего не знающих об Optional, в такой, который знает и принимает их.
Очевидно, такого в яве нет.Scf
12.11.2016 16:00+3guai
12.11.2016 19:25сдаётся мне, приведенные там законы монад — это необходимые, но не достаточные условия. К монаде, по идее, должны быть применимы те же операции, что и к исходному типу, чего нет у явавских Optional
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.guai
12.11.2016 23:18-1это не монада, это тупо боксинг
ChShersh
13.11.2016 02:35+1Чтобы утверждать, будто «нечто» _НЕ_ является монадой, неплохо бы для начала определиться с тем, что такое монада. К сожалению, очень мало программистов понимают, что это такое на самом деле. Почему так произошло, и откуда пошли слухи, что «монада» — лютый зверь, мне неизвестно. Это всё происходило, когда меня ещё не было.
Самым точным определением монады является формальное математическое определение из теории категорий: Монада — моноид в категории эндофукторов. А только потом данную концепцию перенесли в языки программирования, потому что она оказалась полезной. К сожалению, с пониманием этого определения среди программистов возникает другая проблема: очень мало людей среди них разбираются в теории категорий. И многие слышали про монады через какие-то слухи, мнения других, свой опыт, подобные комментарии на хабре, etc. И в итоге выходит, что люди спорят ниочём: либо тролли, либо показывают своё невежество, либо кормильцы троллей, либо пытающиеся образумить невежд.
В итоге получается, что многие программисты пытаются объяснить монады через какие-то примеры, куски кода, конкретные подходы, частные случаи. Всё это выглядит в большинстве случаев как попытка натянуть слона за яйца на глобус (или как-то так, не помню точного оборота). С этой проблемой сталкиваюсь и я, когда пытаюсь объяснить монады студентам на лекциях в универе, ибо они теорию категорий тоже не проходили, к сожалению. Но, к счастью, объяснить монады всё же возможно через какие-то достаточно понятные, прикладные и знакомые концепции. А каждый частный пример не даёт достаточно полного понимания, либо содержит какие-то неувязочки. Вроде того, что «Optional в Java — монада». И у разных людей такие высказывания вызывают реакцию «Вроде бы да, но...» с разной степенью агрессивности :)Scf
13.11.2016 02:37Можно тогда ваше мнение о том, что такое монада? Монада в джаве, допустим, это:
public class Monad<T> { private T value; //методы, которые делают этот класс монадой }
Пожалуйста, дополните этот код, чтобы класс Monad можно было назвать монадой.
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); } } }
ChShersh
13.11.2016 03:47Немного поторопился :(
Вместо
return new MyOptional<>(null);
надо писать
return new MyOptional<>();
Была бы очень неприятная ошибка. Ну, могу сказать, что какой-нибудь хороший язык вроде Haskell не дал бы её совершить уже на этапе компиляции :)
roman_kashitsyn
13.11.2016 02:57+2Помню, ко мне пришло осознание через аналогию с теорией групп. Людям непросто объяснить, что такое группа, потому что это понятие абстрактное. А на деле найди множество и операцию, проверь законы. Выполняются? Группа. Получи пачку теорем и алгоритмов бесплатно.
Ну и с монадами та же примерно история. Найди правильные функции, проверь законы. Сложнее всего начать видеть такие абстрактные концепции в конкретных вещах.
sshikov
13.11.2016 10:56Дело просто в том, что вы описываете выше монады с точки зрения математика.
Для прикладного программиста (не изучавшего теорию категорий) в знании того, что "монада это моноид и т.д." обычно очень мало толку. Для него польза от монад состоит в том, что они дают удобные средства композиции программы из функций. И вот в этом-то смысле Optional в Java все-таки монада (хотя и «да, но» имеют место — потому что законы мы допустим проверить не можем). Почему такие объяснение имеют место? Да потому что имено в этом, в принципах построения программы при помощи монад, и состоит та польза, которую почти каждый может от них получить.
Source
13.11.2016 00:42А мне сдаётся, что Вы путаете аппликативные функторы и монады :-)
roman_kashitsyn
13.11.2016 01:16Вы путаете аппликативные функторы и монады :-)
Не путаю. Каждая монада по определению является функтором. И аппликативным функтором (
pure == return
). Покажите мнеjoin :: m (m a) -> m a
у аппликативного функтора.Source
13.11.2016 02:07Каждая монада по определению является функтором. И аппликативным функтором
Функтором — да, аппликативным функтором — как правило, да, но не по определению.
Да и какой смысл принуждать реализовывать bind через join fmap? Где Вы такое определение монад вычитали?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#монады
potan
12.11.2016 19:53Расскажу про себя. Над Scala-проектами (микросервисами) работают 6 разработчиков и 2 тестера (использующих питон). Двое разработчиков еще участвуют в проектах на C#.
1. Удобно. Недавно к нам пришли 2 джависта и освоили Scala до уровня «исправить баг» очень быстро. Самая большая проблема была с фьючами — в автотестах, которые пишут сами разработчики мы слишком ими увлеклись и начинающим было трудно в них разобраться.
2 Да. Мы даже постепенно переписываем старые Java-сервисы.
kungfu
13.11.2016 10:56+1Java или Scala в целом без разницы. Язык возможно критичен для кодера, но не нормального Программиста с большой буквы, у которого время затрачиваемое на разработку по большей части расходуется на грамотное продумывание дизайна классов, интерфейсов, алгоритмов, а написать сам код как правило не составляет труда на любом языке в сходной весовой категории, ну напишет он метод на Java на 2 минуты дольше — ничего страшного, да хоть на 30 минут дольше, главное чтобы руки росли из нужного места и это всё потом работало качественно и долго.
Saffron
15.11.2016 12:44+1Если язык не важен, то почему вы ООП программируете на джаве, а не на фортране?
AndreyRubankov
15.11.2016 13:41+3> время затрачиваемое на разработку по большей части расходуется на грамотное продумывание дизайна классов, интерфейсов, алгоритмов
Это без сомнения так, но дизайн классов и интерфейсов, а так же выбранные алгоритмы уже непосредственно будет зависеть от языка и его философии.
sshikov
А можете пояснить, как это 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 на переменную — и статический анализ этого кода компилятором станет невозможным.
Scf
Да, в общем случае статический анализ невозможен. Привел в статье неудачный пример, очень уж хотелось показать вариант с if-ом. Но для простого матчинга по типам и параметрам кейс классов компилятору вполне можно доверять.
sshikov
Ну, самый-то простой случай без статической проверки полноты уже вполне съедобен даже на Java 8 :) При наличии javaslang например. Выглядит кривовато, но не слишком.
saksmt
Не слишком кривовато до тех пор, пока вы не пишете что-то уж очень завязанное на сообщения (akka)
sshikov
Ну, да. Я бы даже сказал — до тех пор, пока не попробуете скомбинировать несколько таких switch в единое целое. В текущем проекте я довольно активно javaslang использую, и вполне себе заметно, что с нарастанием сложности оно регулярно ломается во вполне предсказуемом месте — на слабостях системы типов Java.
potan
По моему, проверяется что все покрыто безусловными паттернами. Даже если условие в if не выполнится, найдется подходящий образец.
alexeyrom
Увы, нет. На данный момент не проверяется никак.
alexeyrom
На самом деле никак: как только условия появляются, предупреждения не выдаются: https://issues.scala-lang.org/browse/SI-5365. Был pull request, чтобы переключиться на пессимистический вариант, но в 2.12.0 не попал (см. последний комментарий по ссылке).