Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[18.191.157.186] |
|
Сообщ.
#1
,
|
|
|
Предлагаю вашему вниманию демо-пример, где четко показано различие фунционального от императивного стиля программирования. Первая часть кода содержит императивный подход, а вторая - функциональный с использованием монады Maybe:
class Person { constructor(name, age) { this._name = name; this._age = age; } get name() { return this._name; } get age() { return this._age; } } // data const persons = [ new Person('Gregory', 42), new Person('Julia', 27), new Person('Mark', 30) ]; // imperative function findPerson(persons, name) { for (const person of persons) { if (person.name === name) { return person; } } throw Error('Person not found.'); } try { var p = findPerson(persons, 'Gregory'); console.log(p.name); } catch (error) { console.log(error.message); } // functional const R = require('ramda'); const Maybe = require('ramda-fantasy').Maybe; const findPerson2 = (persons, name) => R.compose( person => Maybe.toMaybe(person), (persons, name) => persons.filter(p => p.name === name)[0] )(persons, name); var p = findPerson2(persons, 'Julia'); console.log(p.map(R.prop('name')).getOrElse('Person not found.')); Чтобы не писать ручками все функциональные приблуды , заюзал библу Ramda и ramda-fantasy. |
Сообщ.
#2
,
|
|
|
Дальше ожидается "и ..."
|
Сообщ.
#3
,
|
|
|
Цитата JoeUser @ Дальше ожидается "и ..." чуть позже запосщу детальное обсуждения фунционального подхода. |
Сообщ.
#4
,
|
|
|
подравил код findPerson, поскольку R.compose возвращает функцию, параметрами которой являются параметры последней переданой функции, то ее обертывание не требуется. Также выделил операцию взятие по индексу в отдельную функцию, тем самым улучшив ясность кода:
const findPerson = R.compose( person => Maybe.toMaybe(person), persons => persons[0], (persons, name) => persons.filter(p => p.name === name) ); как видите findPerson состоит из трех последовательных вызовов функций, отсчет начинается снизу, последний вызов вернет результат в виде монады. Заметьте вызов этих функций произойдет тока когда будет обращение к собствено findPerson. На этом строиться вся логика функционального программирования, т.е. мы составляем нужный функционал из коротких функций, которые легко читать и понимать. Эти функции в свою очередь являются чистыми, т.е. без побочных эффектов, или еще их называют ссылочно-прозрачными, это когда результат функции всегда зависит от его входных параметров и не меняется. Это приводит к тому что их легко тестировать. Вооот |
Сообщ.
#5
,
|
|
|
Предыдущий пример можно еще сократить, поскольку Maybe.toMaybe принимает один параметр, ее можно сразу передать в compose:
const findPerson3 = R.compose( Maybe.toMaybe, persons => persons[0], (persons, name) => persons.filter(p => p.name === name) ); - Что такое Maybe? Maybe это такая клевая штукенция , которая позволяет отложить обработку ошибок на момент когда это потребуется без генерации исключений. К примеру вот как это выглядит в случае исключений без Maybe const findPerson = R.compose( person => { //<-- не чистая функция if (!person) throw Error('Person not found.') return person; }, persons => persons[0], (persons, name) => persons.filter(p => p.name === name) ); try { var p = findPerson(persons, 'Julia'); console.log(p.name); } catch (error) { console.log(error.message); } Как видите вызов findPerson в случае если объект не обнаружен выбрасывает исключение, которое надо обработать иначе программа прервет выполнение. Также стоит отметить, что фукнция которая вызывает исключение не является чистой, по понятной причине. В случае Maybe вы просто вызываете findPerson, заключать вызов в try-catch не требуется и что самое интересное обработать результат этой функции можно когда угодно: var p = findPerson(persons, 'Gregory'); //... какой то еще код... var result = p.map(R.prop('name')).getOrElse('Person not found.'); //=> Gregory Да конечно можно и не выбрасывать исключение и просто возвращать объект ошибки: const findPerson = R.compose( person => { //<-- не чистая функция if (!person) return { error: 'Person not found.' } return person; }, persons => persons[0], (persons, name) => persons.filter(p => p.name === name) ); var p = findPerson(persons, 'Julia'); //... какой то еще код... if (!p.error) { console.log(p.name); } else { console.log(p.error); } но в это случае нам всеравно придется писать код обратотки ошибочного условия, что приводит как спагетти-коду читай гавнокод - Что означает p.map(R.prop('name')? map - тоже что и в случае Array.prototype.map отображает, переданную функцию на данные и возвращает новый объект Maybe. R.prop - это Ramda функция, возвращает функцию, которая при предоставлении объекта возвращает указанное свойство этого объекта, если оно существует. В нашем случае возвращает функцию вида val => val.name. - А нельзя сразу обратиться к свойсвту name объекта? Можно например p.value.name - Зачем тогда так p.map(R.prop('name').getOrElse('Person not found.') писать? Затем что если объект не обнаружен, то вот это p.value.name приведет к исключению TypeError обращение к свойству name не существующего объекта (undefined). - А зачем конецовой getOrElse('Person not found.')? Как я уже писал выше map возвращает новый объект Maybe его еще называют монада. Так вот это вот монада буть она не ладна! хранит наше значение, в нашем случае значение name и чтобы корректно получить его, а не через p.value.name (хотя так тоже можно) юзается getOrElse, который вернет в зависимости от исхода map либо name, либо строку, переданную через параметр, в нашем случае это 'Person not found.' - А что такое монада? Это еще одно модное словечко, которое стоит запомнить и сувать его везде где тока можно, чтобы показать что ты не джун, а сеньор JS Ну а если серьезно то монада это математический термин из теории категорий, что он там обозначает я не интерисовалси, а в программировании это тип данных, со специфическим фейсом |