Почему Scala реализуется как замыкание? - PullRequest
5 голосов
/ 05 декабря 2011

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

Каковы причины этого дизайнерского решения, в отличие от "примитива" в стиле C или Java - то есть, который будет превращен в простой цикл?

(здесь я делаю различие между Java for и ее конструкцией "foreach", поскольку последняя включает неявный итератор).

Более подробно, следуя указаниям Питера. Этот бит Scala:

  object ScratchFor {
    def main(args : Array[String]) : Unit = {
      for (val s <- args) {
        println(s)
      }
    }
  }

создает 3 класса: ScratchFor $$ anonfun $ main $ 1.class ScratchFor $ .class ScratchFor.class

ScratchFor :: main просто пересылает объект-компаньон ScratchFor $ .MODULE $ :: main, который раскручивает ScratchFor $$ anonfun $ main $ 1 (который является реализацией AbstractFunction1).

Именно в методе apply () этого анонимного внутреннего имплика AbstractFunction1 живет реальный код, который фактически является телом цикла.

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

Ответы [ 5 ]

7 голосов
/ 05 декабря 2011

Традиционные циклы for неуклюжи, многословны и подвержены ошибкам.Я думаю, это является достаточным доказательством того, что циклы «для каждого» добавляются в Java, C # и C ++, но если вы хотите получить более подробную информацию, вы можете проверить пункт 46 Effective Java .

Теперь циклы for-each по-прежнему намного быстрее, чем Scala для понимания, но они также гораздо менее мощны (и более неуклюжи), поскольку не могут возвращать значения.Если вы хотите преобразовать или отфильтровать коллекцию (или сделать обе для группы коллекций), вам все равно придется обрабатывать все механические детали построения коллекции результатов в дополнение для вычисления значений.Не говоря уже о том, что в нем неизбежно используется изменяемое состояние.

Наконец, хотя циклы for-each достаточно для коллекций, они не подходят для других монадических классов (из которых коллекции являются подмножеством).

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

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

Теперь было бы возможно для Scala, чтобы иметь общие для циклов или для каждого цикла как особые случаи специальнонацелены на проблемы с производительностью (так как для понимания может делать все, что они делают).Однако это нарушает два принципа, которыми руководствуется Scala:

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

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

3 голосов
/ 05 декабря 2011

Понимание for в Scala - это мощная универсальная конструкция цикличности и сопоставления с образцом. Посмотрите, что он может сделать:

case class Person(first: String, last: String) {}
val people = List(Person("Isaac","Newton"), Person("Michael","Jordan"))
val lastfirst = for (Person(f,l) <- people) yield l+", "+f
for (n <- lastfirst) println(n)

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

Первый for выделяет лишь небольшую часть возможностей конструкции; это и чрезвычайно мощный и чрезвычайно общий. Чтобы поддерживать эту силу, for должен быть способен превращаться во что-то очень общее, что означает замыкания. Тогда возникает вопрос: вводите ли вы также специальные случаи, которые оперируют известными коллекциями простыми способами с улучшенной производительностью? Ответ до сих пор был , в основном без , вместо этого предпочтение отдавалось решениям, которые оптимизируют общие методы взятия замыканий, в которые for превращается.

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

Тем не менее, попробуйте -optimize. В наши дни это часто помогает ускорить простое понимание.

2 голосов
/ 05 декабря 2011

для понимания - это гораздо больше, чем простой цикл.

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

Итак, поскольку понимание не является простым циклом, я надеюсь, вы понимаете, что он не скомпилирован допростая петля.

2 голосов
/ 05 декабря 2011

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

Скажем, в Java это то же самое, что написать

public static void main(String... args) {
    for_loop(args, new Function<String>() {
        public void apply(String s) {
            System.out.println(s);
        }
    });
}

interface Function<T> {
    void apply(T s);
}

public static <T> void for_loop(T... ts, Function<T> tFunc) {
    for(T t: ts) tFunc.apply(t);
}

Это довольно легко встроить (если вы человек). Что удивительно, так это то, что Scala не имеет встроенной функции для оптимизации, чтобы устранить необходимость в новом объекте. Конечно, JIT мог бы сделать это в теории, но на практике это может занять некоторое время, прежде чем он решит этот конкретный случай.

0 голосов
/ 31 декабря 2011

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

В Python, например:

ls = [None] * 3
for i in [0, 1, 2]:
    ls[i] = lambda: i

print(ls[0]())
print(ls[1]())
print(ls[2]())

Это печатает 2 2 2, потому что i имеет более длительный срок службы, чем цикл for. Я постоянно сталкиваюсь с этой ловушкой в ​​Python и R.

Таким образом, даже в самом простом случае важно, чтобы for в Scala был реализован с использованием анонимной функции, поскольку он создает среду для хранения переменных.

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