Functional thinking: Thinking functionally, Часть 1

http://www.ibm.com/developerworks/java/library/j-ft1/index.html
  • Перевод
Давайте на мгновение представим, что Вы — дровосек. И благодаря своему лучшему топору в округе, вы являетесь самым продуктивным дровосеком в лагере. Но однажды появляется некто, начинающий расхваливать достоинства новой парадигмы в рубке леса — бензопилы. В силу убедительности продавца, Вы покупаете бензопилу, но не знаете как она работает. Прилагая неимоверные усилия, пробуете вырвать или раскачать дерево, применяя на практике свою новую парадигму. И быстренько делая вывод, что эта самая новомодная бензопила — ерунда, возвращаетесь к привычному делу — рубить лес топором. А затем кто-то приходит и показывает как заводить бензопилу.

Эта история может показаться Вам знакомой, поставив функциональное программирование на место бензопилы. Проблема в совершенно новой парадигме программирования — не изучение нового языка. Более того, синтакс языка — это всего лишь детали. Вся тонкость же — научиться мыслить иначе. Это то, почему я оказался тут — заводящий бензопилы и “функциональный” программист.

Итак, добро пожаловать в Functional thinking. Эта серия исследует предмет функционального программирования, но не несет исключительной направленности описать функциональные языки. Как я покажу дальше, написание кода в функциональном стиле касается дизайна, компромиссов, разных повторно используемых кусков кода и служит основой для иных догадок. Насколько это окажется возможным, я попытаюсь показать концепции функционального программирования в Java (или близких к Java языках) и перейду к другим языкам, чтобы осветить возможности, отсутствующие на данный момент в Java. Я не полезу сразу в дебри, рассказывая о довольно необычных вещах, таких как монады (monads). Напротив, я постепенно проведу Вас через новый путь мышления.

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

Number classifier


Начиная разговор о разных стилях программирования, Вам необходим код для сравнения. В качестве первого примера я приведу вариацию одной проблемы, рассматриваемой в моей книге The Productive Programmer, а также первой и второй частях Test-driven design. Как минимум, я выбрал этот код потому, что в этих двух публикациях он рассматривается достаточно глубоко. Нет ничего дурного в дизайне, превозносимом в этих статьях, но я хочу ниже предложить разумное обоснование иному подходу.

Суть требований в том, что при поступлении на вход положительного числа больше 1, Вам нужно классифицировать его как perfect (совершенное), abundant (избыточное) или deficient (недостаточное). Совершенным является то число, чьи делители (за исключением его самого, как делителя) в сумме ему равны. Подобно тому, сумма делителей избыточного числа выше его самого, а недостаточного — меньше.

Imperative number classifier

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

Listing 1. NumberClassifier, the imperative solution to the problem
public class Classifier6 {
    private Set<Integer> _factors;
    private int _number;

    public Classifier6(int number) {
        if (number < 1)
            throw new InvalidNumberException(
            "Can't classify negative numbers");
        _number = number;
        _factors = new HashSet<Integer>>();
        _factors.add(1);
        _factors.add(_number);
    }

    private boolean isFactor(int factor) {
        return _number % factor == 0;
    }

    public Set<Integer> getFactors() {
        return _factors;
    }

    private void calculateFactors() {
        for (int i = 1; i <= sqrt(_number) + 1; i++)
            if (isFactor(i))

                addFactor(i);
    }

    private void addFactor(int factor) {
        _factors.add(factor);
        _factors.add(_number / factor);
    }

    private int sumOfFactors() {
        calculateFactors();
        int sum = 0;
        for (int i : _factors)
            sum += i;
        return sum;
    }

    public boolean isPerfect() {
        return sumOfFactors() - _number == _number;
    }

    public boolean isAbundant() {
        return sumOfFactors() - _number > _number;
    }

    public boolean isDeficient() {
        return sumOfFactors() - _number < _number;
    }

    public static boolean isPerfect(int number) {
        return new Classifier6(number).isPerfect();
    }
}

Несколько моментов в этом коде, которые следует отметить:
  • необходим большой набор Unit-тестов (частично из-за того, что я написал его для рассуждения в аспекте TDD)
  • класс состоит из большого числа связанных методов, что в таком виде является побочным эффектом (side effect) для использования в TDD
  • встроенная в метод calculateFactors() оптимизация производительности. Сущность этого класса составляет сбор делителей, чтобы в дальнейшем их сложить и в конечном итоге классифицировать. Делители всегда могут быть собраны в пары. Например, если число на входе — 16, то когда я определяю делитель 2, я также использую и 8, так как 2*8=16. Если я собираю делители в пары, мне всего лишь необходимо проверить на них, составляющие квадратный корень из рассматриваемого числа, что в точности и делает метод calculateFactors().

(Slightly more) functional classifier

Используя те же самые техники TDD, я создал альтернативную версию классификатора, которую Вы можете увидеть в листинге 2:

Listing 2. Slightly more functional number classifier
public class NumberClassifier {

    static public boolean isFactor(int number, int potential_factor) {
        return number % potential_factor == 0;
    }

    static public Set<Integer> factors(int number) {
        HashSet<Integer> factors = new HashSet<Integer>();
        for (int i = 1; i <= sqrt(number); i++)
            if (isFactor(number, i)) {
                factors.add(i);
                factors.add(number / i);
            }
        return factors;
    }

    static public int sum(Set<Integer> factors) {
        Iterator it = factors.iterator();
        int sum = 0;
        while (it.hasNext())
            sum += (Integer) it.next();
        return sum;
    }

    static public boolean isPerfect(int number) {
        return sum(factors(number)) - number == number;
    }

    static public boolean isAbundant(int number) {
        return sum(factors(number)) - number > number;
    }

    static public boolean isDeficient(int number) {
        return sum(factors(number)) - number < number;
    }
}

Разница между двумя версиями классификаторов едва уловимая, но достаточно важная. Основное отличие — это осознанное удаление из кода разделяемого состояния (shared state). Избавление от него является одной из важных черт в функциональном программировании. Вместо того, чтобы разделять состояние в виде промежуточных результатов между методами (поле factors в Листинге 1), я вызываю методы напрямую, что позволяет избавится от него. С точки зрения дизайна, метод factors() становится длиннее, но препятствует утеканию поля factors за пределы метода. Также стоит отметить, что вторая версия может состоять исключительно из статических методов. Никаких аккумулирующих переменных, используемых от метода к методу, что позволяет мне избавится от необходимости инкапсуляции через области видимости (scoping). Все эти методы отлично отработают, если послать на вход параметр требуемого типа. (Это пример чистой функции (pure function), коцепции, которую я рассмотрю позже, в следующей части).

Functions


Функциональное программирование является широкой и довольно активно развивающейся областью в computer science, вызывающей все больший интерес. Появляются новые функциональные языки в JVM (такие как Scala или Clojure) и фреймворки (Functional Java или Akka) вместе с заявлениями о возникновении меньшего числа ошибок, большей продуктивности, лучшей читаемости, больших дивидендов и т.д. Вместо того, чтобы сразу охватить весь предмет функционального программирования, я сфокусируюсь на нескольких ключевых концепциях, завершая повествование некоторыми интересными выводами.

В самом центре функционального программирования находится (звучит барабанная дробь) функция, также как и классы являются основной абстракцией в объектно-ориентированном программировании (ООП). Функции формируют “строительные блоки” для обработки и проникнуты некоторыми особенностями, отсутствующими в традиционных императивных языках.

Higher-order functions

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

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

Проблемы, решаемые использованием функций высшего порядка не уникальны для функциональных языков. Тем не менее, подход, которым Вы решаете проблему отличается, когда начинаете думать “функционально”. Обратите внимание на пример метода в Листинге 3 (вырванный из большего куска кода), обеспечивающего безопасный доступ к данным:

Listing 3. Potentially reusable code template
public void addOrderFrom(ShoppingCart cart, String userName,
                     Order order) throws Exception {
    setupDataInfrastructure();
    try {
        add(order, userKeyBasedOn(userName));
        addLineItemsFrom(cart, order.getOrderKey());
        completeTransaction();
    } catch (Exception condition) {
        rollbackTransaction();
        throw condition;
    } finally {
        cleanUp();
    }
} 

Код в Листинге 3 содержит инициализацию, выполняет какую-то работу, завершает транзакцию в случае успешного выполнения, иначе совершает откат (rollback) и в завершении производит освобождение ресурсов. Безусловно, шаблонная часть этого кода может быть повторно использована, и мы, как правило, реализуем это в ООП созданием структуры. В нашем случае, я совмещу 2 паттерна “Банды четырех” (Gang of Four Design Patterns): Шаблонный метод (Template Method) и паттерн Команда (Command). Шаблонный метод предполагает, что мне нужно перенести повторяющуюся часть кода вверх по иерархии наследования, оставляя реализацию алгоритма в дочерних классах. Паттерн “Команда” позволяет инкапсулировать поведение в классе с хорошо известной семантикой выполнения. Листинг 4 демонстрирует результат применения этих двух паттернов к коду из Листинга 3:

Listing 4. Refactored order code
public void wrapInTransaction(Command c) throws Exception {
    setupDataInfrastructure();
    try {
        c.execute();
        completeTransaction();
    } catch (Exception condition) {
        rollbackTransaction();
        throw condition;
    } finally {
        cleanUp();
    }
}

public void addOrderFrom(final ShoppingCart cart, final String userName,
                         final Order order) throws Exception {
    wrapInTransaction(new Command() {
        public void execute() {
            add(order, userKeyBasedOn(userName));
            addLineItemsFrom(cart, order.getOrderKey());
        }
    });                
}

В Листинге 4 я вынес общие части кода в метод wrapInTransaction() (как вы могли разглядеть, семантика которого основывается на упрощенной версии TransactionTemplate во фреймворке Spring), передавая объект Command как кусок кода для выполнения. Суть метода addOrderFrom() сводится к определению анонимного внутреннего класса (anonymous inner class) для создания экземпляра класса команды, оборачивая пару выражений.

Оборачивание нужного поведения в класс команды — чистой воды артефакт дизайна Java, не содержащего возможность отделения этого поведения. Все поведение в Java должно быть размещено внутри класса. Даже проектировщики языков быстро разглядели недостаток в таком дизайне — оглядываясь в прошлое, немного наивно думать о невозможности существования поведения, не привязанного к классу. JDK 1.1 исправил этот недостаток добавлением анонимных внутренних классов, которые, как минимум, добавляют синтаксического сахара для создания большого числа мелких классов всего лишь с несколькими функциональными методами — не структурными. В качестве занятного эссе об этом аспекте в Java, могу порекомендовать «Execution in the Kingdom of Nouns» (Steve Yegge).

Java принуждает меня создавать экземпляр класса Command, даже если я хочу всего лишь определения одного метода. Сам по себе класс не предоставляет никаких преимуществ: не содержит полей, конструктор (не принимая во внимание стандартный) или какого-либо состояния. Это только оболочка для поведения, реализуемого внутри метода. В функциональном же языке, это решается использованием функцией высшего порядка.
Если я решу на момент оставить Java, то приближусь к идеалу в функциональном программировании, используя замыкания (closures). Листинг 5 демонстирует такой же отрефакторенный пример, но с использованием Groovy вместо Java:

Listing 5. Using Groovy closures instead of command classes
def wrapInTransaction(command) {
  setupDataInfrastructure()
  try {
    command()
    completeTransaction()
  } catch (Exception ex) {
    rollbackTransaction()
    throw ex
  } finally {
    cleanUp()
  }
}

def addOrderFrom(cart, userName, order) {
  wrapInTransaction {
    add order, userKeyBasedOn(userName)
    addLineItemsFrom cart, order.getOrderKey()
  }
} 

В Groovy все, распологаемое внутри фигурных скобок {} является блоками кода, которые могут передаваться в качестве параметров, имитируя функции высшего порядка. За кулисами Groovy реализует для Вас паттерн “Команда”. Каждый блок замыкания в Groovy на самом деле экземпляр специального типа Closure, который содержит метод call(), автоматически выполняющийся в момент размещения круглых скобок () после переменной, указывающей на экземпляр замыкания. Groovy позволяет реализовывать некоторое, сравнимое с “функциональным”, поведение, выстраивая соответствующие структуры данных, добавляя в язык синтаксического сахара. Как я покажу в следующих частях, Groovy также содержит другие возможности функционального программирования, лежащие за границами Java. Также в дальнейшем я вернусь назад для некоторого интересного сравнения замыканий и функций высшего порядка.

First-class functions

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

В императивных языках программирования мне приходится думать о каждом элементарном шаге в алгоритме. Код в Листинге 1 наглядно демонстрирует это. Для реализации классификатора чисел мне нужно точно определить то, как собирать делители, что в свою очередь означает необходимость писать специфичный код, чтобы пройти по циклу для определения среди чисел делителей. Но обход списков с проведением операции на каждом элементе кажется довольно привычной вещью. Посмотрите в Листинге 6 на переопределенный код классификатора чисел с использованием фреймворка Functional Java:

Listing 6. Functional number classifier
public class FNumberClassifier {

    public boolean isFactor(int number, int potential_factor) {
        return number % potential_factor == 0;
    }

    public List<Integer> factors(final int number) {
        return range(1, number+1).filter(new F<Integer, Boolean>() {
            public Boolean f(final Integer i) {
                return number % i == 0;
            }
        });
    }

    public int sum(List<Integer> factors) {
        return factors.foldLeft(fj.function.Integers.add, 0);
    }

    public boolean isPerfect(int number) {
        return sum(factors(number)) - number == number;
    }

    public boolean isAbundant(int number) {
        return sum(factors(number)) - number > number;
    }

    public boolean isDeficiend(int number) {
        return sum(factors(number)) - number < number;
    }
} 

Основное отличие между Листингами 6 и 2 составляют два метода: sum() и factors(). sum() использует преимущества метода foldLeft() применительно к классу List в Functional Java. Это специфичная разновидность манипуляций со списками носит название катаморфизм (catamorphism), что является обобщением для свертывания списков (list folding). В этом случае “свертывание списка” означает:
  1. Взять начальное значение применить к нему в паре с первым элементом списка указанную операцию
  2. Получить результат и применить эту же операцию к вместе со следующим элементом
  3. Повторять действие пока не будет достигнут конец списка

Заметьте, это в точности то, что вы делаете когда суммируете список элементов: начинаете с 0, добавляете первый элемент, получая результат, добавляете второй и далее, пока не обойдете весь список. Functional Java делат возможным использование функции высшего порядка (в этом примере Integers.add) и применяет ее за Вас. (Несомненно, в Java на самом деле нет функций высшего порядка, но Вы можете написать отличный аналог на случай ограничений, накладываемых структурой данных или их типом.

Другой интригующий метод в Листинге 6 — factors(), который иллюстрирует совет “концентрируйтесь на результате, а не промежуточных шагах”. В чем суть проблемы определения делителей числа? Иначе говоря, учитывая список всех возможных чисел до рассматриваемого, как я могу выделить из них те, что являются его делителями? Это подсказывает использование операции фильтрации — я могу отфильтровать полный список чисел, исключая те, которые не удовлетворяют моему критерию. Обычно читается: берем диапазон чисел от 1 до рассматриваемого, фильтруем список кодом внутри метода f(), что является способом в Functional Java, позволяющим создавать класс со специфичными типами и возвращаемыми значениями.
Этот код иллюстрирует гораздо большее понятие — такое, как тенденцию в языках программирования в целом. В прошлом разработчикам приходилось обрабатывать большое количество раздражающих вещей, как выделение памяти, сборка мусора, указатели. С течением времени многое из этого взяли под свою опеку языки и среды выполнения. Подобно тому, как компьютеры становятся все более мощными, мы выносим обыденные, поддающиеся автоматизации задачи в языки и среды выполнения. Как Java-разработчик, я привык уступать языку все проблемы, касающиеся памяти. Функциональное программирование расширяет эти полномочия, охватывая более специфичные детали. Со временем, мы проводим все меньше времени за заботой о шагах, необходимых для решения проблемы и думаем больше в терминологии процессов. В продолжении серии я продемонстрирую множество примеров этого.

Заключение


Функциональное программирование в большей степени является складом ума, нежели набором инструментов или языков. В этой первой части я начал описывать некоторые темы функционального программирования, от простых дизайнерских решений к переосмысливанию некоторых амбициозных проблем. Я переписал простой класс Java, чтобы сделать его более “функциональным”, затем начал погружение в некоторые темы, которые отличают функциональный стиль программирования от традиционного — императивного.

Здесь также были рассмотрены две важные, перспективные концепции. Первая — концентрируйтесь на результате, а не шагах. Функциональное программирование старается представить проблемы в другом свете, потому-что в ваших руках есть разные “строительные блоки”, способствующие нахождению решений. Вторая тенденция, которую я покажу на протяжении серии — это делегирование простых вещей языкам программирования и средам выполнения, позволяющая нам фокусироваться на уникальных аспектах проблем программирования. В следующей части я продолжу рассматривать базовые аспекты функционального программирования и их применение в разработке программного обеспечения на сегодняшний день.

P.S. Огромное спасибо CheatEx за review перевода. Скорее всего, продолжение следует…
Поделиться публикацией

Похожие публикации

Комментарии 102
    +7
    Спасибо за перевод. Правда, мне лично этот материал показался тяжеловатым для введения в «функциональное мышление» и как следствие ставящим под сомнение ценность изложенной в нем идеи. Думаю, можно объяснить намного проще.
      +2
      Думаю, статья направлена на ООП программистов со «средним» уровнем, которые читая это могут сразу примерить освещенные недостатки привычной парадигмы на себе, исходя из личного опыта.
      Более плавное начало, думаю, можно найти в Functional Programming for Java Developers
        0
        В этой книге тоже Java-подобные языки используются? Мне кажется, в статье совсем не видно преимуществ ФП, поскольку код получается такой же загроможденный. Для изучения ФП стоит взять более удобный язык, чья семантика не тонет в шелухе необходимых конструкций кода. Ну, я лично рекомендую Haskell, однако есть еще Lisp, F# и целый ворох прочих ФЯ.
          0
          Полностью согласен, только я бы посоветовал как-раз Common Lisp, потому что на Haskell могут быть проблемы с императивными реализациями примеров.
            0
            Никаких проблем — Лисп так Лисп. :) А вот если бы функциональное программирование нормально преподавалось в том же моем ЧитГУ, то язык был бы неважен. Важнее — как преподаватель его преподнесет.
      +3
      >> Проблема в совершенно новой парадигме программирования — не изучение нового языка.

      А тут я бы на месте автора сделал акцент на том, что парадигма нова _для_программиста_. В самом функциональном программировании нет ничего нового, просто раньше интереса к нему было меньше. Лично я это связываю с тем, что репутация ООП со временем подпортилась, а также с появлением многоядерных процессоров.
        +2
        Думаю, что говорить что функциональщина — это бензопила, а остальное, включая ООП — топор, неверно. Просто потому, что каждый стиль, хоть в целом и универсален, справляется лучше только с определенным типом задач. Это все равно, что сравнивать хайку(хокку) и традиционными стихотворениями.
        Помимо того, что что-то легче описывать с помощью ООП, что-то легче с помощью функциональной парадигмы, существует также и чисто технические различия — функциональная парадигма требует постоянного выделения\собирания памяти, что, все-таки имеет некоторый эффект как на быстродействие системы, так и на требование к количеству используемой памяти, так и на, возможно, применяемые алгоритмы, которые должны учитывать данную особенность.
        Функциональное программирование, хоть и входит в моду и считается чуть ли не элитарным, не является абсолютным победителем в войне парадигм.
          +4
          «функциональная парадигма требует постоянного выделения\собирания памяти» — а ООП не требует?
          «что, все-таки имеет некоторый эффект как на быстродействие системы, так и на требование к количеству используемой памяти» — выделение памяти и сборка мусора в функциональной среде куда быстрее и экономнее, чем в императивной, так как не неизменяемые данные не нуждаются в копировании и контроле за изменениями. Проблема в другом — на нижнем уровне архитектура таки императивная и нет универсального эффективного интерпретатора.
            0
            >«функциональная парадигма требует постоянного выделения\собирания памяти» — а ООП не требует?
            Естественно требует, но не на каждый чих. Да, она может выделяться и собираться быстрее, нежели в ООП, однако это отнюдь не означает, что данной проблемы нет. Естественно, пока приложение работает с малыми объемами, такое поведение оказывает слабый эффект, однако если приложение по своей природе жруще, то данная проблема встанет, и тут можно только пожелать, чтобы разработчики не попали в тупик.
              +3
              «Естественно требует, но не на каждый чих.»
              Именно, что на каждый — создание-уничтожение объекта требует выделение-освобождение памяти.
              «Да, она может выделяться и собираться быстрее, нежели в ООП, однако это отнюдь не означает, что данной проблемы нет.»
              Есть, но стоит менее остро из-за отсутсвия потребности в копировании сущностей. Сборка мусора для функциональных сред появилась раньше и сейчас более эффективно, чем для ООП. Разница есть только на задачах, которые длительное время используют фиксированные ресурсы — но это уже не ООП, а процедурный подход.
              На больших объемах ООП сливает быстрее, чем функциональщина — вторая может требовать нетривиальной оптимизации, а первая рушится под грузом миллионов объектов и синхронизации между потоками. Груз массы объектов лечится только шагом назад — возвратом на критичном уровне к массивами, процедурам и прелестям чистого C.
              А груз синхронизации — устранением где только возможно изменяемого состояния, т.е. использованием функциональщины.
              Думать, что ООП кушает меньше ресурсов, чем ФП — принципиальная ошибка.
                +1
                Ну вот так сразу и принципиальная )
                > Именно, что на каждый — создание-уничтожение объекта требует выделение-освобождение памяти.
                Вы обходите вниманием тот факт, что в функциональных языках количество созданий и уничтожений намного выше, т.к. этим заканчивается любая вызванная функция.
                Согласен с вами по поводу уменьшения разницы при уменьшении времени использования объекта.
                  0
                  Создание-уничтожение на стеке — штука одинаково стремительная. Создание-уничтожение в куче — в ФП быстрее, причем не в одной операции, а в целом. Сущности в ФП далеко не столь краткоживущи как кажется, благодаря неограниченному повторному использованию по ссылкам.
                  И если приложение по своей природе обладает зверским аппетитом, то кормить придется либо возвратом к процедурами, либо задействуя функциоанльный подход, либо используя и то и другое. ООП же экономии ресурсов железа не обещает ни в каком варианте. От него нет плюсов в плане производительности приложения — только в плане производительности разработки по сравнению с процедурным подходом.
                    0
                    Интересное мнение. Надо будет изучить вопрос поглубже.
                      0
                      Очень спорно все, что вы тут говорите. А говорите вы, в основном, о вещах зависимых (производительность программы, скорость разработки, «аппетит» программы). Они все зависят от конкретной программы и программиста. А вот создание/уничтожение объектов, действительно, критерий, по которому можно судить.
                        0
                        Я говорю о вещах, которые имеют практическое значение. По счетчику созданий-уничтожений судить ни о чем, корме реализации RTL нельзя.
                          0
                          Разумеется, практическое значение эти вещи (которые я назвал «зависимыми») имеют. Однако вот вам пример.

                          У себя на работе я занимаюсь процессингом телефонного трафика, биллингом, тарификациями и прочим. Появилась задача: распарсить трафик с новых станций. Отлично! Задача для ФП. Поскольку именно в то время я начал изучать Haskell, написал программу на нем. Я точно знаю, что разработка этой программы на С/С++ заняла бы у меня уйму времени. Гораздо больше, чем это ушло на Haskell-программу. Однако у программы был существенный недостаток: на 10000 текствых файлах (в которых примерно по 1000 строк) она жрала полгигабайта памяти и выполнялась минут пять-десять. Что бы вы думали, это виноват язык? И на С/С++ у меня бы получилось лучше? Отнюдь. Просто через два месяца, когда я уже поднабрался опыта, я переписал программу полностью все на том же Haskell. Она получилась в три раза короче, гораздо выразительнее, а самое главное, уже не требовала памяти (ну, мегабайта 4) и выполнялась за 10 секунд все на том же наборе данных. Вывод очевиден: эти показатели (скорость разработки, производительность) — не показатели вовсе. На них разницу в ООП, функциональном и процедурном программировании не покажешь.
                            +1
                            было бы интересно узнать, какие ошибки вы совершили по неопытности, и какие приёмы помогли добиться такого ускорения.
                              +2
                              Я и так собирался описать это, а тут, пожалуй, даже отвертеться не смогу. :)
            +2
            в реальной жизни никого не устроит классифицировать число каждый раз. для повышения производительности нужно будет кэширование результатов классификации. а теперь реализуйте это кеширование без использования состояния.
              0
              Эта классификация из мира математики: совершенное чило и там далее по ссылкам.
                0
                гы. там еще внизу примечание-ссылка на статью с говорящим названием «Совершенная красота и совершенная бесполезность совершенных чисел»

                :)
                  0
                  тоже улыбнуло :) Математика…
                +2
                я думаю, во второй части будет сказано, что результат выполнения чистой функции зависит только от ее аргументов и поэтому может быть кеширован самой средой исполнения.
                  0
                  как среда поймёт каков необходимый объём кэша? или кэш будет динамически раздуваться от 0, до всей доступной памяти? а как она поймёт когда кэширование лучше отключить? а когда кеш нельзя чистить? а когда параметров несколько да еще и являются структурами — будет сериализовывать их и склеивать в сложный ключ? а когда кеш нужно сбрасывать на диск чтобы не потерять при перезагрузке?
                    +1
                    Ну, я думаю имелось ввиду что-то вроде
                    g x = f x + f x
                    то в случае и чистых функций в конкретном выражении можно применить оптимизацию и считать f x только один раз.
                      –1
                      ну а в императивном стиле это придётся писать так:
                      g x = 2 * f x

                      из этого можно сделать вывод, что функциональная парадигма поощряет копипасту, а императивная — нет %-)
                        +1
                        Неправда. Это не императивный стиль, а тоже вполне функциональный. Вот это будет императивный, да и то условно:

                        g (var x)
                        {
                        var tmp1 = f*x
                        tmp1 = tmp1 * 2
                        return tmp1
                        } 
                          –1
                          в таком случае функциональный будет так:

                          g (var x)
                          {
                          var tmp1 = f*x
                          var tmp2 = tmp1 * 2
                          return tmp2
                          }

                          все 3 кода эквивалентны. единственное отличие — мы либо надеемся, что компилятор закеширует вызов функции, либо мы просто не делаем одинаковых вызовов. будем ли мы при этом именовать полученное значение сохраняя в переменную или не будем — не важно.
                          0
                          Да, зачем читать тред, лучше сразу ответить.
                          Почитайте что такое чистые функцие.
                        0
                        я имел в виду оптимизацию сложных выражений. Заданные же вами задачи, разумеется, не имеют общего решения :)
                          0
                          ну да, а оптимизация сложных выражений имеет?
                          0
                          Вы обо всём этом думаете для каждой написанной вами функции?
                            0
                            разумеется, а что?
                            +3
                            А как сборщик мусора в ООП-языках понимает, когда пора собирать урожай? Точно также выделяется фиксированный объем памяти.
                            «а когда параметров несколько да еще и являются структурами — будет сериализовывать их и склеивать в сложный ключ»
                            у вас определенно устойчивое императивное мышление. функциональные программы не так работают
                              +1
                              Думаю, менеджер кеша, поддерживающий работу функциональной парадигмы, вполне может собирать и учитывать статистику времени вычисления значения, частоты вызовов, повторяемость аргументов (ключа), учитывать объёмы этого ключа и результата, сравнивая их с объёмом памяти и диска…

                              Я вот подумываю бухгалтерскую отчётность попробовать соорудить на чистом функциональном подходе, с обязательным кешированием каждого вызова каждой функции в специальных табличках в БД — кешах этих функций.
                                +1
                                без фишек типа лямбд, монад и т.п.
                                  0
                                  > Я вот подумываю бухгалтерскую отчётность попробовать соорудить на чистом функциональном подходе, с обязательным кешированием каждого вызова каждой функции в специальных табличках в БД — кешах этих функций.

                                  Без хорошего математического и алгоритмического фундамента это будет очень непросто. Особенно если вы хотите обойтись без лямбд и, вероятно, замыканий, функций высшего порядка и других удивительных вещей, используемых преимущественно в ФП.
                                    +1
                                    А в чём Вы видите основные подставы на этом пути?
                                      0
                                      Ну, для начала, как вы понимаете «чистый функциональный подход»? Так-то словосочетание «чистый функциональный» — это вполне определенный термин, предполагающий чистоту функций и языка. Ее, судя по материалам, нельзя добиться без изоляции побочных эффектов (или вообще отказа от них). Допустим, монад у вас не будет, тогда можно было бы задействовать продолжения. Но продолжения не сделать без ФВП (если я правильно понял матчасть). Кажется, замыкания тут где-то тоже нужны. Если же понимать ваше слово «чистый» как «исключительный» («исключитеьно на функциональном подходе»), то те же функции высшего порядка будут очень и очень нужны, иначе какой это функциональный подход?
                                        +1
                                        Просто функции, и всё.

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

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

                                        Для поддержки изменения первички можно сделать механизм инвалидации кеша при изменении первичных даных.
                                          +2
                                          По-моему, если судить о функциональном подходе в таком ключе, то подойдет любой хороший императивный код. Модульность, разделение на более мелкие функции, разграничение ответственности (классов, функций, модулей), — все это ведет к функциональному виду кода. Я уж молчу про паттерны проектирования: большая их часть под собой имеет функциональную природу. Вывод моих рассуждений, в общем, такой: любой хороший код стремится быть более функциональным и аккуратным. То есть, получается, вы движитесь в правильном направлении. :)
                                            +1
                                            ну, по большому счёту, внутри моих функций может быть и императивные фрагменты будут, но тут бонус в том, что они должны быть простыми, чтоб можно было любой результат наглядно раскрыть на его составляющие. И все эти составляющие при желании разглядывать, анализировать и сравнивать.
                                              +1
                                              > результат наглядно раскрыть на его составляющие. И все эти составляющие при желании разглядывать, анализировать и сравнивать.

                                              Функциональщики говорят: построить элементную базу для комбинаторной обработки информации. :)
                                                +1
                                                именно!

                                                так что, без тех вышеупомянутых фундаментов во что я уткнусь?
                                                  0
                                                  Не знаю, вам виднее. Вы даже язык не назвали, на котором хотите это делать.
                                                    0
                                                    PL/SQL
                                                      +1
                                                      Этот диалект я не знаю, не приходилось с ним работать. Знаю T-SQL и PL/pgSQL. Могу предположить, что главной трудностью будет вопрос, как перекидывать большие объемы данных между функциями. Через аргументы? Через временные таблицы? Передавая запрос? Возникнут вопросы производительности и удобства использования. Но мне кажется, в конечном счете структура БД и логика, реализованная на функциях, будут проекту только на пользу. Я и сам на своей работе многое выношу на сервер, функций всяких полно, они друг друга вызывают. В принципе, это не сказать чтобы настоящий функциональный подход, но уж точно никак не вредительский.
                                    +1
                                    Чисто в фукциональном стиле сохранять кешы в базе не получится — это операция с побочными эффектами. И зачем такая операция, если функция на заданных аргументах и так вычисляется только один раз? Кешы потом где-то используются?
                                      0
                                      Кеш нужен, лишь если функция на одинаковых аргументах вызывается неоднократно (как минимум, во второй раз она будет вызываться, когда кто-то захочет посмотреть её результат, использовавшийся первый раз при вычислении зависимой от неё функции).

                                      И кеш возможен, лишь если сама функция побочных эффектов не имеет.

                                      В этом случае кеширование в результат функции побочных эффектов не привнесёт.
                                        0
                                        Побочные эффекты возникают из-за операций ввода-вывода, т.е. обращения к БД. Если его нет — функция чиста. А если кеширование самоцель, т.е. нужен только чтоб восстанавливать результаты вычисления на одинаковых аргументах во время работы программы — то кеш вобще не нужен. Функциональные программы и так два раза не вычисляют одно и то же значение, вызывайте сколько угодно — вычисления будут проводиться только один раз
                                          0
                                          каким образом эта однократность вычисления реализована? ИМХО, ровно тем кешированием, о котором я говорю.
                                            0
                                            Во-многом зависит от компилятора. Но если возможность реализуется на уровне компилятора — зачем ее повторно реализовывать?
                                              0
                                              так речи и не шло о том, чтоб реализовывать повторно. Речь шла ровно об устройстве среды исполнения.
                                +1
                                Поинт в том, что без него обычно можно жить и часто гораздо приятнее. А не в том что надо всем бросить заниматься делом и строить бесполезные программы в лямбда-исчислении.
                                  +2
                                  Гуглить «мемоизация»
                                    0
                                    ух, какое хорошее слово!
                                    0
                                    Чистые функциональные языки вычисляют значение функции только один раз. Изменяемые состояния для в самой программе для этого не нужны
                                      0
                                      ага, функция random тоже только один раз вычисляется? х)
                                        0
                                        Разумеется. В Haskell random вычисляет одно случайное число для конкретного ГПСЧ и возвращает новый генератор ГПСЧ. С этим новым генератором можно вычислить следующее случайное число.
                                          0
                                          классный костыль. вместо меняющегося числа у нас будет меняющаяся функция. функции ввода-вывода тоже так лицемерно реализованы?
                                            0
                                            А как по-вашему псевдослучайные числа вообще генерируются?
                                              +1
                                              Ваш комментарий просто смешон. Какой костыль? Под этим решением есть строгое математическое обоснование. А решение само — очень элегантное.
                                                –3
                                                да-да, стыдливо прячем изменяемое состояние за невъебенной абстракцией «монада» и вместо простого кода

                                                var state= 0
                                                function gen( ){
                                                return ++state
                                                }

                                                генерируем что-то типа

                                                var state= StateMonada( 0 )
                                                gen= GeneratorHelper({
                                                getState: function(){ return state },
                                                nextValue: function( value ){ return value + 1 }
                                                })

                                                или ещё какую серьёзную математически обоснованную загогулину
                                                  +1
                                                  Не темните своей некомпетенцией. Поизучайте накануне матчасть; то, что вы написали, не соотносится с функциональной природой ФЯ, потому что это исключительно императивный стиль. Какое изменяемое состояние? От него отказались не потому, что так захотелось, а потому, что это несет прямые выгоды в языках, подобных Haskell.
                                          +1
                                          Да. random есть функция от генератора ПСЧ. Если два раза вызвать функцию на одном генераторе — получите один и тот же результат.
                                          Скажу Вам больше. В императивных языках это работает также, просто Вам это не показывается.
                                        0
                                        Мемоизация отлично ложится на функциональную парадигму.
                                        0
                                        … при поступлении на вход положительного числа больше 1, Вам нужно классифицировать его как perfect, abundant или deficient...

                                        Не понимаю. Скажите кто-нибудь, что общего эта задача имеет с реальным миром?
                                          +1
                                          В реальной жизни использовать какую-либо чистую парадигму никогда не удается. Даже ООП в реальности приходится постоянно разбавлять другими парадигмами, ибо святого грааля программирования не существует. Инструмент должен соответствовать задаче. Ну и как было правильно замечено, инструмент должен соответствовать человеку. Но в реальности пока еще не одна парадигма не показала существенных преимуществ над другими. В каких-то задачах лучше одно, в других другое. Невозможно с помощью одной парадигмы быть эффективным во всем… И для инженеров это хорошо, потому что если бы в технологии программирования все было бы так просто, то программная инженерия не оплачивалась бы так высоко :)
                                            +1
                                            круто. было
                                            static public Set<Integer> factors(int number) {
                                              HashSet<Integer> factors = new HashSet<Integer>();
                                              for (int i = 1; i <= sqrt(number); i++)
                                                if (isFactor(number, i)) {
                                                  factors.add(i);
                                                  factors.add(number / i);
                                                }
                                              return factors;
                                            }


                                            стало
                                            public List<Integer> factors(final int number) {
                                              return range(1, number+1).filter(new F<Integer, Boolean>() {
                                                public Boolean f(final Integer i) {
                                                  return number % i == 0;
                                                }
                                              });
                                            }

                                            Ну вообще никаких различий производительности… Что за жалкие подтасовки?

                                            странно что в конце везде остались
                                            sum(factors(number)) - number

                                            по сути это неплохо бы выделить в отдельную функцию (reducedFactorSum), после чего писать вроде
                                            isPerfect = (== number).reducedFactorSum
                                            isAbundant = (> number).reducedFactorSum
                                            isDeficiend= (< number).reducedFactorSum

                                            Всё-таки демонстрация работы с функциями высшего порядка (использован синтаксис хаскеля для создания композиции функций, так как синтаксиса Groovy я не знаю).
                                              +1
                                              Хуже стало :) вот если бы
                                              range(1, sqrt(number)+1)
                                              тогда да. И почему должны быть различия в производительности, если алгоритм не меняется?
                                                0
                                                Если алгоритм не меняется, то изменения производительности зависят от компилятора. Но в данном случе был изменён алгоритм.
                                                то есть вместо того чтобы перебирать sqrt(n) мы стали перебирать n чисел. Это весьма существенное изменение сложности.

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

                                                Пример такой некрасивости (язык хаскель)
                                                [1… n] — местный range
                                                [1… (fromInteger.toInteger.round.sqrt.fromIntegral) n] — это уже с корнем.
                                                Поскольку вначале надо сделать преобразование в рациональное, чтобы сосчитать корень, потом округлить, потом преобразовать в Integer и только потом уменьшить до типа Int.
                                                Сложно, долго и непонятно.
                                                  +1
                                                  [1… ceili­ng $ sqrt n]
                                                  [1… floor­ $ sqrt n]
                                                    0
                                                    Тут нужно чтобы n был правильного типа. А то
                                                    No instance for (RealFrac Int)
                                                    arising from a use of `ceiling'
                                                    Possible fix: add an instance declaration for (RealFrac Int)
                                                    In the expression: ceiling
                                                    In the expression: ceiling $ sqrt n
                                                    In the second argument of `filter', namely
                                                    `[1 .. ceiling $ sqrt n]'
                                                      0
                                                      $ cat test.hs
                                                      range n = [1… ceiling $ sqrt n]
                                                      main = putStrLn $ show $ range 24
                                                      $ ghci test.hs
                                                      GHCi, version 6.12.1: www.haskell.org/ghc/ :? for help
                                                      Loading package ghc-prim… linking… done.
                                                      Loading package integer-gmp… linking… done.
                                                      Loading package base… linking… done.
                                                      [1 of 1] Compiling Main ( test.hs, interpreted )
                                                      Ok, modules loaded: Main.
                                                      *Main> main
                                                      [1,2,3,4,5]
                                                      *Main>
                                                    0
                                                    Я думаю, в данном случае опечатка просто ,) Там еще в listing 6 функция isFactor зачем-то затесалась непонятно. И еще опечатки есть, сейчас уже не вспомню, лень перечитывать.
                                                +1
                                                Вы листинги копипастом переносили? Там ошибки просто
                                                  +1
                                                  Мне кажется в функциональном стиле тяжело будет написать реально большую систему. Взгляните на программы из мира бизнеса, баз данных, офисных приложений и прочего. В них весьма немного хитрых алгоритмов. Основное — это данные. Очень легко в функциональном стиле переписать класс с одним полем данных (как в примере в начале статьи). А если их десяток, да еще каждое является на простым типом, а вложенным классом? Код будет плохо читаем, мне кажется.
                                                    0
                                                    Мне вместе с Вами просто, возможно, пока сложновато это представить в полной мере. Взять веб-фреймворк для Scala — Lift. «Lift way», в отличие от своих Scala-аналогов, исключительно опирается на большое количество функциональных конструкций и уже довольно успешно используется в крупных компаниях.
                                                    Также DSL для работы с базой данных от Foursquare — Rogue
                                                      +1
                                                      Большая это какого уровня примерно?

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

                                                      Дело в том, что мы пишем, например. Но, конечно, возможно системы недостаточно большие для вас. Таким образом, насколько большие системы считаются?
                                                        0
                                                        Уровня LinkedIn и Twitter.
                                                        +2
                                                        А кто Вам сказал, что в функциональную программу будут переносить иерархию классов? Другая парадигма — другие подходы к решению задачи. Все будет выглядит совсем по-другому.
                                                          0
                                                          Кстати, Scala очень хорошо совмещает ООП и ФП.
                                                          +1
                                                          Основой всех систем являются две сущности: состояния и операции. Состояния обычно описываются структурой данных: иерархической, реляционной и т.д. Операции — это действия над данными, т.е. то, что изменяет состояние. Основная проблема всех времен и народов — как максимально локализовать данные и код, который оперирует с этими данными, в отдельные и независимые куски (свободные от нелюбимых side-effects). Отсюда берет начала куча парадигм программирования.

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

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

                                                          Тем не менее ФП было бы несправедливо называть отдельной и независимой парадигмой. Скорей это некий метод или прием, который может использоваться для перехода от императивного стиля описания к декларативному.
                                                            0
                                                            SQL можно было бы назвать функциональным, но как-то язык не поворачивается. Уж очень он ограниченный в этом плане, очень. Вот декларативный — да, вполне.
                                                          0
                                                          Это очень круто, все понятно, спасибо большое за перевод. Ждем продолжения!
                                                            –3
                                                            Боже насколько же легче это сделано в Питоне!
                                                              0
                                                              А в Haskell еще легче. ;) Теперь мы с вами обязаны похоливарить!

                                                              А если серьезно, то да. От языка к языку та или иная парадигма программирования может так измениться, что любой, кто это увидит, скажет: «Какой ужас, эта ваша парадигма!». Инструмент все же имеет значение. А если рассматривать руки за самый что ни на есть полезный инструмент, то и без них никуда.
                                                              +2
                                                              Не в обиду автору — но несколько утомили дифирамбы функциональному подходу. Его особенности (отстутсвие сайд-эффектов и его следствия — возможность «ленивого» выполнения, легкость распараллеливания и т.д.) думаю, известны каждому более-менее опытному программисту. Но вот рекламный тон каждый раз смущает — не попадалось пока ни одной статьи, где были бы указаны области применения, где ФП даёт хороший эффект, а где где оно создаёт проблемы.

                                                              Как мне кажется, именно статьи вида «ФП в реальном мире» были бы куда интереснее и, кстати, полезнее для его популяризации. Чтобы затрагивались вопросы — когда ФП стоит использовать в первую очередь, какие пдводные камни, как интегрироватьс императивным кодом, какие идеи у обоих подходов общие (вон, функциональный map и объектный foreach — близнецы-братья), какие элементы из ФП можно позаимствовать, не переходя на него полностью — и так далее, и тому подобное.
                                                                +2
                                                                Вы правы: нужны практические примеры. Такой есть у меня. Можете увидеть здесь. Подумываю развернуть его в статью.

                                                                > (вон, функциональный map и объектный foreach — близнецы-братья)
                                                                А это вы, конечно, загнули. Нет, map, он, конечно, «делает в цикле» что от него хотят, но…
                                                                  +3
                                                                  Они близнецы-братья в том, что отделяют задачу — сделать нечто с каждым отдельным элементом коллекции — от способа, которым это делается. Гарантий перебора в определённом порядке я не припомню ни в одном языке (хотя могу ошибаться, конечно). Из этого, к пример естественным образом вырастают параллельные реализации foreach.

                                                                  Делегаты и коллбеки — другой древнючий пример одного и того же действия (передачи функции как значения), существующего в обеих парадигмах. Надо ещё? Пожалуйста: qsort из stdlib — вполне себе пример ФВП — разумеется, учётом возможностей языка. На плюсах это уже шаблонами решается впрочем, там уже скорее заимствование из ФП.

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

                                                                  В общем, как мне кажется, не стоит так уж жестко противопоставлять парадигмы — вопрос скорее в том, как их удобным образом интегрировать.
                                                                    0
                                                                    Все верно, я же не отрицаю. Вот тред в тему из этого топика.

                                                                    foreach в Qt (тот, который пришел с шаблонами, а не тот, который пришел с C++00x) все же обычный цикл, следовательно, задачи в нем выполняются последовательно. За другие языки не скажу.
                                                                      0
                                                                      Гарантию перебора в определенном порядке (что для foreach, что для map) даёт, вроде как, сама коллекция методами типа rewind и next или, грубо говоря, реализуя интерфейс итератора (или их низкоуровневыми аналогами для примитивных типов — массивы, объекты и т. п., где гарантии уже на уровне референса языка или транслятора).
                                                                        0
                                                                        хм… и впрямь, подстава. А реализаций foreach, не опирающихся на последовательные итераторы, нет?

                                                                        Хотя — пусть даже и через next — спецификация же не запрещает реализации получить все элементы разом, и обрабатывать их одновременно?
                                                                          0
                                                                          Не встречал. Вроде везде именно на последовательных итерациях реализуется и это строго регламентировано для foreach. Для map могут быть варианты.
                                                                        0
                                                                        Не надо их интегрировать. Они базируются на разных предпосылках, разных идеях. Лично я противопоставления с ООП не вижу, есть задачи, которые элегантней решаются средствами ООП, но есть и те, для которых ФП — то, что доктор прописал. Вон, в Питоне, Руби и прочих сделана попытка интеграции — и получилась, простите, кастрация ФП, чтоб его фичи в принципы ООП влезли.
                                                                          0
                                                                          ТАк в том-то и дело. Если у меня в рамках одного рпоекта часть задач удобно решается через FP, а часть — императивно — то либо два языка тащить либо всё же подбирать один, который может то и другое. И подозреваю что в подавляющем большинстве случаев «кастрированного» ФП будет достаточно. Вон в D примерно так — и довольно красиво получается. Что до питона/руби — не знаю я их, так что судить не могу.
                                                                      +2
                                                                      Я тут когда-то переводил статью, там эти вопросы кратенько освещались. Недостаточно, но все же. habrahabr.ru/blogs/net/71656/
                                                                      +4
                                                                      Может автор и профессионал, но почитал его первый листинг (Classifier6) и рождается естественный скепсис к человеку, кто написал код:
                                                                      — который вернет «Can't classify negative numbers» на число 0;
                                                                      — имеет public метод getFactors — мало того, что ненужный, так еще и позволяющий испортить состояние программы вызовом classifier.getFactors().insert(42);
                                                                      — делающий кучу вычислений на каждый вызов isPerfect/isAbundant/isDeficient;
                                                                      — работающий неправильно для больших number по причине переполнения;
                                                                      И немного ворчания:
                                                                      — «static public», а не наоборот, несмотря на java.sun.com/docs/books/jls/third_edition/html/classes.html#78091;
                                                                      — поля не final, хотя могли бы быть таковыми;
                                                                      — не нравятся подчеркивания в именах полей.

                                                                        +3
                                                                        За перевод и пост спасибо. Особенно за теорию. Но как-то ожидал большего в листингах и комментах к ним. Уж сильно всё завязано на Java, в костылях, имхо, суть теряется.
                                                                          0
                                                                          >В самом центре функционального программирования находится (звучит барабанная дробь) функция, также как и классы являются основной абстракцией в объектно-ориентированном программировании (ООП).

                                                                          Основной абстракцией в ООП являются объекты. Классы есть и в ФП.
                                                                            0
                                                                            А одному мне показалось, что _factors.add(_number); в первом листинге в конструкторе не нужно делать? Надо же исключить само число из суммы его делителей.

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

                                                                            Самое читаемое