Scala программист - "должен ли быть один очевидный способ сделать это" или "более чем один способ сделать это"?

Я занимаюсь Java в течение длительного времени и начал Scala около 6 месяцев назад. Я люблю этот язык. Одна вещь, которую я обнаружил, заключается в том, что есть несколько способов сделать что-то. Я не знаю, было ли это из-за характера языка или из-за того, что он еще молод и развивается, идиомы и лучшие практики не появились.

Меня удивляет, что я всегда был python над человеком perl, где:

"Должен быть один - и желательно только один - простой способ сделать это".

имеет для меня больше смысла, чем

"Там более одного способа это сделать".

Мне интересно узнать, как вы думаете, Scala подходит для этой шкалы и почему?


Цитаты: Python PEP20 и ссылка Perl

Ответы

Ответ 1

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

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

val array = Array(1,2,3,4,5)
var sum = 0
var index = 0
while (index < array.length) {
  sum += array(index)
  index += 1
}
sum

Существуют одинаково общие функциональные подходы, которые работают медленнее при работе с примитивами (это может измениться с помощью @special в 2.8), но при этом вам будет меньше скука:

var sum = 0
Array(1,2,3,4,5).foreach(x => sum += x)
sum

И тогда есть несколько менее общие функциональные конструкции, предназначенные только для такого рода проблем (поскольку это очень много), и это "лучше", если вы хотите чистый компактный код:

Array(1,2,3,4,5).reduceLeft( _ + _ )

И иногда есть очень не общие конструкции, чтобы делать именно то, что вы хотите; в Scala 2.8, например:

Array(1,2,3,4,5).sum

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

Ответ 2

Scala - это многопарадигменный язык, охватывающий множество разных концепций философии и дизайна. К ним относятся

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

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

Какой из них во многом зависит от программиста, хотя я могу дать некоторые общие правила/мысли:

  • Scala - это, в конце концов, функциональный язык. Поэтому я считаю хорошей практикой следовать функциональному стилю программирования (что также приведет к очень сжатым и элегантным решениям).

  • Будьте последовательны! В конце концов, для вас должен быть один способ, но вы можете выбрать, какой из них. Избегайте смешивания побочных эффектов и чистых функций, функций списка, библиотечных функций (карты, фильтра) или функциональности Java. Вместо этого используйте разные парадигмы ортогонально, где они лучше всего служат.

  • Используйте замечательные функции Scala предоставляет вам (а не просто использовать его как Java с хорошим синтаксисом): сопоставление шаблонов, продолжения, генерики и чрезвычайно мощная система типов, функции более высокого порядка.

Ответ 3

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

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

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

И, наконец, акцент на библиотеках. Scala не имеет списков или карт. А массивы задаются только потому, что для обеспечения совместимости с Java это требуется. В этом смысле это похоже на C, а не на BASIC. В C почти все, что вы делаете, кроме математики, было сделано через библиотеки, написанные либо самими C, либо ассемблером. В BASIC даже такие команды, как открытие файлов или отображение текста на экране, являются частью самого языка.

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

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

Например, List имеет оператор append (в Scala 2.8), даже если он является субоптимальным для списков. Он имеет это, потому что List является Seq, и это общая операция, которую вы можете захотеть сделать с ними.

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

Ответ 4

Самое очевидное место, где scala предлагает совершенно разные способы достижения того же самого, находится в области for понятий:

val ls = List(1, 2, 3, 4)
for (l <- ls) println(l) //for comprehension
ls.foreach(println(_))   //underlying form 

Это распространяется на flatMap, filter и т.д. Мой личный выбор - придерживаться вызовов метода, хотя в более сложных примерах, таких как:

for (i <- 1 to 10;
     j <- 1 to 20 if (j % 2 == 0)) ...

Я вижу, что они проще и интуитивно понятны в форме for. Для чего это стоит, я ценю способность выбирать свой стиль, потому что люди из разных слоев населения предпочитают разные вещи.

Добавление конструкций управления к 2.8, например Exceptions, является примером: исходя из фона Java, я не получил сначала силу эти абстракции, но если бы я отправился в функциональную землю, я бы, наверное, сразу понял.

Мой главный bugbear в scala - отсутствие разумных предикатных понятий в классе опций. Хотя для некоторого предиката p:

opt.isEmptyOr( p ) //does not exist
opt.forall( p )

Является эквивалентным, мне не нравится тот факт, что первое не существует с точки зрения читаемости. Точно так же:

opt.notEmptyAnd( p ) //does not exist       
opt.map(p).getOrElse(false)

По-моему, вторая форма излишне нечитаема! Дублирование для достижения удобочитаемости несомненно положительно!

Ответ 5

Все это понятие настолько расплывчато, что почти бессмысленно. Но я подозреваю, что если вы работаете с языком, на котором действительно существует только один способ сделать любую вещь, вы будете плакать и скрежещать зубы не менее 50% времени.

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

Ответ 6

Отказ от ответственности: я понятия не имею, о чем думают авторы Scala, я просто выражаю свое мнение как разработчик Scala (и python и ruby).

Просматривая API Scala, вы не видите много способов сделать то же самое. Например, Mutable и неизменяемые версии объектов имеют один и тот же интерфейс. Такие вещи дают Scala некоторую согласованность.

Будучи гибридным языком, обычно существует два способа решения проблемы: функциональный и процедурный (foreach vs for). Конечно, если вы хотите использовать процедурный способ, вы просто используете Java.

Если вы посмотрите на функциональные языки, такие как Lisp и Haskell, обычно есть один способ сделать это для каждого используемого вами подхода (например, вы можете решить проблему в рекурсивном или контекстном представлении).

Наконец, я считаю, что любой код должен быть очевидным для чтения. И очевидно писать. Вы можете быть очень гибкими (perl/ruby) или strict (python). Scala является гибким (подумайте о синтаксисе использования "." и скобок в вызовах методов, но вы можете сделать его настолько строгим, насколько хотите. Лично мне нравятся вещи строгие, поэтому я напишу свой собственный код, что Я считаю это очевидным.