Как устроено приложение JavaScript, основанное на функциональном программировании?

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

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

var external;
function foo() {
   external = 'bar';
}
foo();

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

Итак, как это работает, когда вы имеете дело с объектами, а что нет? Например, много раз у меня будет конструктор и метод инициализации, который инициализирует объект, например:

var Foo = function(initVars) {
   this.init(initVars);
}

Foo.prototype.init = function(initVars) {
   this.bar1 = initVars['bar1'];
   this.bar2 = initVars['bar2'];
   //....
}

var myFoo = new Foo({'bar1': '1', 'bar2': '2'});

Итак, мой метод init намеренно вызывает побочные эффекты, но каким будет функциональный способ справиться с подобной ситуацией?

Кроме того, если бы кто-нибудь мог указать мне на исходный код Python или JavaScript программы, которая пытается быть максимально функциональной, это также было бы очень признательно. Я чувствую, что близок к тому, чтобы "понять это", но я не совсем там. В основном меня интересует, как функциональное программирование работает с традиционной концепцией классов ООП (или избавляется от нее для чего-то другого, если это так).


person user126715    schedule 20.04.2010    source источник
comment
Я просто хотел указать, что теперь я понимаю, что метод Foo.init на самом деле не имеет смысла, если вы пытаетесь придерживаться функционального программирования, потому что единственная причина, по которой у меня был бы метод init, было бы, если бы я хотел повторно инициализировать объект Foo позже ... что полностью противоречит идее неизменности. Поэтому вместо этого я должен просто сделать все, что мне нужно, для инициализации объекта в конструкторе, а затем в следующий раз, когда мне понадобится либо обновить объект Foo, либо повторно инициализировать его, я просто создаю новый экземпляр Foo и разберусь с ним. с этим. Все начинает обретать смысл!   -  person user126715    schedule 23.04.2010


Ответы (4)


Вы должны прочитать этот вопрос:

Javascript как функциональный язык

Есть много полезных ссылок, в том числе:

Теперь на мое мнение. Многие люди неправильно понимают JavaScript, возможно потому, что его синтаксис похож на синтаксис большинства других языков программирования (где Lisp / Haskell / OCaml выглядят совершенно иначе). JavaScript не объектно-ориентированный, это фактически язык на основе прототипов < / а>. У него нет классов или классического наследования, поэтому его не стоит сравнивать с Java или C ++.

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

Возьмем пример (используя sys.puts из node.js):

var external;
function foo() {
    external = Math.random() * 1000;
}
foo();

sys.puts(external);

Чтобы избавиться от глобальных побочных эффектов, мы можем заключить его в закрытие:

(function() {
    var external;
    function foo() {
        external = Math.random() * 1000;
    }
    foo();

    sys.puts(external);
})();

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

Теперь, чтобы избавиться от побочного эффекта external:

(function() {
    function foo() {
        return Math.random() * 1000;
    }

    sys.puts(foo());
})();

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

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

var Square = function(x, y, w, h) {
   this.x = x;
   this.y = y;
   this.w = w;
   this.h = h;
};

function getArea(square) {
    return square.w * square.h;
}

function sum(values) {
    var total = 0;

    values.forEach(function(value) {
        total += value;
    });

    return total;
}

sys.puts(sum([new Square(0, 0, 10, 10), new Square(5, 2, 30, 50), new Square(100, 40, 20, 19)].map(function(square) {
    return getArea(square);
})));

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

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

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

person Brian McKenna    schedule 23.04.2010
comment
спасибо за ответ и полезные ссылки. Мне очень нравится идея просто передавать новые объекты моим функциям и заставлять их действовать в соответствии с ними. Это было ага! момент для меня. Единственная вещь, с которой я все еще боролся с fp в целом, - это сохранение функциональности вместе с разумным кодом. С помощью объектно-ориентированного программирования это просто, потому что почти все является методом вне класса (например, car.startEngine (); car.applyBrakes ();). Я думаю, что с FP я могу сделать то же самое, используя пространство имен и просто имея мои методы, требующие объект в качестве первого аргумента. так что var car = {}; car.startEngine = функция (carObj) {} - person user126715; 23.04.2010
comment
@ user321521, почему бы не поместить функцию в прототип объекта, который вы ей передаете? Отделение данных от операций не делает код более или менее FP. Это вполне ортогонально. - person Alexey; 23.04.2010
comment
@Brian То, что вы называете объектом в приведенном выше примере, является не столько объектом, сколько структурой или кортежем, я имею в виду, что это чисто тип data . Объекты (как в объектно-ориентированном) представляют собой инкапсуляцию данных и методов, и они характеризуются тем, что они могут изменять состояние того, что вы вызываете для них методы. Теперь, как бы вы это ни называли, это концепция, которая пронизывает JavaScript, и вам придется обойтись без нее, если вы решите придерживаться чисто функционального стиля. - person KaptajnKold; 28.04.2010
comment
Сумма должна быть function sum(values) { return values.reduce(function(a, b) { return a + b; }); } - person Cristian Garcia; 14.05.2014

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

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

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

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

Я стараюсь придерживаться следующих правил:

Функции, выполняющие вычисления, не должны изменять состояние. А функции, которые изменяют состояние, не должны выполнять вычисления. Кроме того, функции, которые изменяют состояние, должны изменять как можно меньше состояния. Цель состоит в том, чтобы иметь множество маленьких функций, которые делают только одно. Затем, если вам нужно сделать что-то большое, вы составляете кучу маленьких функций, которые будут делать то, что вам нужно.

Следуя этим правилам, можно получить ряд преимуществ:

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

  2. Надежность кода. О правильности кода легче рассуждать, если он менее сложен.

  3. Функции легче тестировать, когда они делают только одно. Таким образом, будет меньше особых случаев для тестирования.

Обновлять:

Включено предложение из комментария.

Обновление 2:

Добавлены полезные ссылки.

person KaptajnKold    schedule 22.04.2010
comment
Я не согласен. На самом деле JavaScript более функциональный, чем объектно-ориентированный. В нем есть замыкания, первоклассные функции, в нем отсутствуют классы и наследование. Синтаксически он больше похож на Java, чем на Lisp, но в целом имеет больше общего с последним. - person Brian McKenna; 23.04.2010
comment
Вы ошибаетесь: в JavaScript есть и классы, и наследование (но, возможно, не в том стиле, к которому вы привыкли). И в большинстве случаев его структуры данных изменяемы, что отличает его от всего чисто функционального языка. Я категорически не согласен с мнением, что он больше похож на Lisp, чем на Java. - person KaptajnKold; 23.04.2010
comment
Адам, я полностью согласен с изложенными вами правилами. Также было бы полезно добавить обоснование (или ссылку), почему правила действительно ценны. ИМХО: 1. легко повторно использовать код 2. надежность кода (легко рассуждать о правильности кода) 3. легко тестировать код - person Alexey; 23.04.2010
comment
Я думаю, что JavaScript лучше подходит для парадигмы ООП, чем FP. - person Alexey; 23.04.2010
comment
@KaptajnKold, конечно, вы можете использовать некоторые функции JavaScript вместо классов и классического наследования, но это не означает, что JavaScript имеет классы и классическое наследование (вы просто используете прототипы). Вы даже можете прибегнуть к некоторой инкапсуляции, но тем не менее, вы просто используете прототипы для создания объектно-ориентированных концепций. Это работает для многих, и это нормально (возможно, потому, что работает хорошо). В конце концов вы обнаружите, что JavaScript основан на прототипах и вообще не принципиально объектно-ориентирован. - person Brian McKenna; 24.04.2010
comment
@KaptajnKold, извините, я также хотел указать, что большинство структур данных в Лиспе изменчивы, что не делает его объектно-ориентированным. - person Brian McKenna; 24.04.2010
comment
@Brian Почему вы не думаете, что наследование на основе прототипов представляет собой реальную объектную ориентацию? Вы также считаете, что Go не является объектно-ориентированным, потому что у него нет наследования вообще? Я бы сказал, что именно инкапсуляция функций и состояния делает язык объектно-ориентированным. Re. Лисп: Я поправляюсь. Тем не менее, большинство языков, считающихся чисто функциональными, не имеют изменяемых структур данных. - person KaptajnKold; 26.04.2010
comment
@KaptajnKold Я понимаю вашу точку зрения. Нет, я бы не назвал Go объектно-ориентированным. Я думаю, вы описываете ориентированные на классы, а не объектно-ориентированные функции (ориентация на классы - это лишь одна важная часть). Используя прототипы JavaScript, можно создавать наследование, абстракции, полиморфизм, инкапсуляцию и т. Д., Которые являются объектно-ориентированными функциями. В JavaScript это всего лишь эмуляции классических объектно-ориентированных языков. Сам по себе JavaScript не является объектно-ориентированным языком, но его можно заставить работать как таковой. Если он работает как единое целое, разве это не делает его единым? Хорошая точка зрения. - person Brian McKenna; 27.04.2010
comment
@Brian Я все еще думаю, что вы неправильно понимаете, что именно делает язык объектно-ориентированным, но я думаю, мы не собираемся соглашаться по этому поводу. Я просто хотел отметить, что сами создатели Go называют его глубоко объектно-ориентированным языком. Возможно, даже больше, чем Java (по памяти из технического выступления на golang.org). Я знаю, что апелляция к авторитету - слабая форма аргументации, но я очень доверяю мнениям этих парней. - person KaptajnKold; 27.04.2010
comment
@KaptajnKold Я также уважаю ребят из Plan9, которые создали Go. Я думаю, они имеют в виду тот факт, что Go очень похож на Smalltalk в отношении объектов. Smalltalk считается создателем объектной ориентации, но с тех пор этот термин значительно изменился. Я думаю, что в соответствии с исходным определением JavaScript вполне может считаться объектно-ориентированным. - person Brian McKenna; 27.04.2010
comment
@Brian По вашему мнению, каково исходное определение объектно-ориентированного программирования? А какой текущий? Я просмотрел статьи об объектно-ориентированном программировании, функциональном программировании и программировании на основе прототипов в Википедии, и все они, похоже, совпадают с моей точкой зрения. - person KaptajnKold; 27.04.2010
comment
Я думаю, нам нужно различать чисто функциональное и чисто функциональное. Если мы требуем, чтобы чистота считалась функциональной, это отбрасывает все семейство ML, все семейство Lisp, Erlang - почти все, кроме Haskell и нескольких эзотерических языков. - person Chuck; 28.04.2010
comment
Объектная ориентация @KaptajnKold SmallTalk - это то, что я считаю исходной версией: en.wikipedia.org / wiki / SmallTalk # Объектно-ориентированное_программирование - person Brian McKenna; 28.04.2010

Я думаю, что http://documentcloud.github.com/underscore/ лучше всего подходит для вам нужно - он предоставляет наиболее важные функции высшего порядка для функционального программирования и не имеет клиентских функций для манипулирования DOM, которые вам не нужны на стороне сервера. Хотя опыта у меня нет.

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

person Alexey    schedule 21.04.2010
comment
Да, в настоящее время я использую underscore.js, и я использовал его для устранения всех циклов for. Это своего рода ключ к FP, как я это тоже вижу. Идея просто передать новые объекты функциям, как упомянуто ниже Брайаном, действительно открыла мне препятствие в том, как с этим бороться. Я посмотрю, как он работает. Node.js / V8 кажется действительно быстрым из всего, что я читал до сих пор, поэтому будет интересно посмотреть, работает ли этот метод. - person user126715; 23.04.2010

Итак, две вещи, на которые стоит обратить внимание:

  1. В вашем первом примере ваша переменная не будет просачиваться в глобальную область, и именно так это должно быть сделано, попробуйте никогда не использовать переменные без их объявления, т.е. test = 'data' приведет к утечке данных в глобальную область.

  2. Ваш второй пример также верен, bar1 и bar2 будут объявлены только в объекте Foo.

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

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

person RC1140    schedule 20.04.2010
comment
Спасибо за комментарий. node.js - это серверный javascript, поэтому я не думаю, что extJS предназначен для работы там, но я могу ошибаться. Меня больше интересуют только лучшие практики применения модели функционального программирования к javascript в целом. Я знаю, как создать приложение, если я просто использую традиционный объектно-ориентированный дизайн (хотя это хороший аргумент в пользу прототипирования), но я хочу знать, как атаковать его, используя концепции функционального программирования, насколько это возможно. - person user126715; 21.04.2010