Pull to refresh

Comments 48

UFO just landed and posted this here

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

Как удостовериться, что функции действительно многократно используемые и могут использоваться везде? (Подсказка: чистые функции (Pure functions), ссылочная прозрачность (referential transparency))
Как удостовериться, что данные, передаваемые нами в функции, не изменены и могут использоваться и в других местах? (Подсказка: чистые функции (Pure functions), неизменяемость)
Если функция берёт несколько значений, но при объединении в цепочку (chaining) можно передавать только по одной функции, то как нам сделать эту функцию частью цепочки? (Подсказка: каррирование (currying) и функции высшего порядка)

Из разряда «давайте придумаем несущественные проблемы, которые зато легко решаются на ФП и героически их решим»

Хотелось бы правда практических примеров, а не переповторение одних и тех же «преимуществ».

Вот как быстро убедится, что в a(b(x)) — «a» на самом деле корректно работает с тем, что пришло ему в аргументы, Если в нем эти аргументы передаются в другую процедуру и так далее. Ходить по всему стеку?

Где в чистом фукциональном коде хранить состояние приложения и, Главное, как с ним работать? Только прошу — не приходите в пример Редакс — Это грязная и процедурная библиотека, которая имититирует функциональный контракт.

&& user.prefs.languages.primary != 'undefined'

Куда делась эта проверка в функциональном коде? Зачем столь избыточная проверка в процедурном коде? Она сокращается в 4 раза без потери смысла. Если написать корректно, то вся статья разрушится?

const getUrlForUser = (user) => {
  const defLang =  indexURLs['en'];
  if (user == null) { //не залоггирован
    return defLang;
  } else {
    return indexURLs[user.prefs.languages.primary] || defLang;
  }
}


Вот код, который делает то же самое, но без искусственных фор для фп. Что, без лжи нельзя доказать преимущество?
без лжи нельзя доказать преимущество?


Трудно наверное.

Вот к примеру, стандартная задача в ООП — расчёт зарплаты (веса, и прочего) Института.

Институт состоит из Отделений, Отделения из отделов, отделы из секторов.

В ООП — надо просто:
Институт.geЗарплата();


И всё, ООП — далее уже каждый объект(Отделение, отдел, сектор) имеет метод .geЗарплата();

То есть обычная задача обхода объектов и суммирования того, что выдаёт каждый объект по известному методу.

Она конечно имеет место быть в ФП — и там она тоже решается. Наверное.

Но вот ООП тут ясно как день ясный. — А вот также же ясно будет с ФП?

ООП обычно винят в том, что при передвижении методов в классах — всё… ломается. Да, это так, но:

Программировать в парадигме ООП просто — при разработке библиотеки — передвигай методы в классы, образуй интерфейсы и смотри где можно внедрить абстрактный класс. — Как всё удалось — выпускай библиотеку.

Если что потом надо поломать (метод передвинуть и прочее) — выпускай новую версию библиотеки.
Если программируешь не библиотеку — а прикладную задачу — просто перевыпускай приложение.

И так поступают все кто пишет в парадигме ООП. (С)

Чем же лучше парадигма ФП? — Функции чистые и потому легче тестировать и легче искать ошибки?
И это всё?
Насколько я понимаю ФП, у вас будет какой-то тип a, и функция geЗарплата: a -> b. В питоне методы класса явно принимают объект в параметры, в JS есть .call / .apply — позволяющий указать контекст исполнения метода — то есть вполне себе функционально (хотя и ООП) — geЗарплата.call(a).

Абстрагирование в ФП есть, оно просто иначе строится. И да, на JS это всё местами очень криво ложится — TCO того же до сих пор в браузерах нету.
JSmitty
Насколько я понимаю ФП, у вас будет какой-то тип a, и функция geЗарплата: a -> b. В питоне методы класса явно принимают объект в параметры, в JS есть .call / .apply — позволяющий указать контекст исполнения метода — то есть вполне себе функционально (хотя и ООП) — geЗарплата.call(a).

Как это сделать на ООП в JS — это не проблема и это ясно.

Как это сделать в ФП — я не знаю, но очевидно так же возможно.

Вопрос в ином. — Показать что в ФП это не только можно и это более ясно можно реализовать, в крайнем случае если не более ясно, то более чем-то (чем?) лучше реализовать.

Пока такого не встречал.

Классическая задача — определение бинарных (и выше) операций. Операция — классическая функция, и на ООП вид ложится очень некрасиво. Извините, но вот так писать — objA.plus(objB) — просто фу, гораздо лучше — plus(objA, objB). Экстремистское ООП (java) вынуждает писать вот такое во втором случае — Helper.plus(objA, objB). Это всё к чему — есть границы применения. Думаю, что всем понятно, что трансформация массивов через map/filter/reduce удобнее и читается легче. Хотя и по идее медленнее, чем цикл (накладные расходы на вызов функций).

Потоковое преобразование данных, как по мне, гораздо удобнее делать в функциональном ключе. Чем нагораживать тонны абстракций в ООП, лишь бы оно было ООП. С другой стороны, если есть какие-то явно выделяемые сущности (а не исходящие из логики ОО дизайна) и операции, к ним относящиеся — глупо не воспользоваться тем, что в ООД придумано.

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

По ФП — я бы предложил — более элегантно. Мне лично нравятся тестовые примеры из Mostly adequate guide to FP.
гораздо лучше — plus(objA, objB).

Это чем лучше? Естественно писать a + b, а значит a.plus(b) — более естественный способ записи. А учитывая перегрузку операторов — он вообще сводится к a + b.

Думаю, что всем понятно, что трансформация массивов через map/filter/reduce удобнее и читается легче

Мне непонятно. Особенно reduce (особенно в JS) — абсолютно нечитабелен даже на маленьких функциях. Вот вам жизненный пример на JS, где есть map (item => item.id), filter (item.isValid()) и reduce — напишите это в функциональном и чистом стиле на JS. Оно будет медленно и нечитабельно:

const hash = {};

for (let item of array) {
   if (!item.isValid()) continue;

   if (hash[item.category] == null) {
      hash[item.category] = [ item.id ];
   } else {
      hash[item.category].push(item.id);
   }
}

return hash


На ООП такое вынужденно обрастет лесом вспомогательного кода.

Или просто заюзать одно из двух — интерфейсы или дженерики, зависимо от деталей задачи. И мы кроме кратного кода получим типобезопасность и поддерживаемость.
UFO just landed and posted this here
Ну это разве ФП? Это «я слышал, что reduce — модно, и потому использовал его в своем процедурном коде». Ни чистоты, ни декларативности. У вас дважды мутируются переменные.

А еще первый код работать не будет, потому что вы забыли объявить acc. Но это для reduce нормально — один из его главных и очень критических недостатков, из-за которого он нечитабелен, что по коду содержимое переменной объявляется значительно позже, чем само содержимое используется.
Используя Ramda
let group = R.pipe(R.filter(isValid),                   
                   R.groupBy(d => d.category),
                   R.mapObjIndexed(R.map(d => d.id)))

//или
R.pipe(	R.filter(isValid),
  	R.reduceBy((category, d) => category.concat(d.id), 
       	[],
       	d => d.category))
//Используем так:
group(array)


Если хотите на родных map, filter, reduce, можно что-то вроде
function group(by, selector) {  
  return function(acc, next) {
    let key = by(next),
        newItem = selector(next),
        category = acc[key]? acc[key]: [];        
    return Object.assign(acc, {[key]: category.concat(newItem)});
  }
}

array.filter(isValid)
     .reduce(group(d => d.category, d => d.id), {})


Функция group пишется один раз, и может быть использована в других местах.
Вот это (с Ramda) больше похоже на ФП, только совершенно не соответствует «трансформация массивов через map/filter/reduce удобнее и читается легче». Потому что даже зная Ramda и такой подход читается он хуже императивного (дайте какому либо мидлу код мой и код на Ramda — сами увидите), а уж в больших количествах поддерживать его нереально. Я сам лет 5 назад очень увлекался таким кодом и результате такие места были самые сложные для поддержки, изменения и фикса багов.

Код без Ramda — опять грязный, мутируете acc. Если мутируете, зачем тогда вообще reduce?
Код без Ramda — опять грязный, мутируете acc. Если мутируете, зачем тогда вообще reduce?


Да нет же. Object.assign — создает копию. А вообще на самом деле, т.к. js однопоточный и этот объект генерируется только внутри reduce можно было смела заменить на мутации. Получили бы выигрышь в производительности :)
Object.assign — создает копию

Нет)
const acc = {};
Object.assign(acc, { foo: 123 });
console.log(acc); // Object {foo: 123}


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

этот объект генерируется только внутри reduce можно было смела заменить на мутации

А можно просто писать императивно и не заблуждаться о ФП)
Да, действительно, забыл добавить {} :).
Вот так надо было
Object.assign({}, acc, {foo: 123});
Ну вы ведь понимаете, что такой код делает сложность квадратичной вместо линейной? И зачем?
Конечно понимаю. Я ведь даже писал, что нам на самом деле нет смысла использовать иммутабельные данные внутри reduce.

Вообще говоря это обычная практика когда надо произвести много операций над иммутабельным объектом, то заменять его на мутабельный, производить операции, а затем если нужно делать опять иммутабельным.
Тот же StringBuilder из C# примерно так и работает.

Если мы не используем всякие сторонние библиотеки для иммутабельных структур, то любой объект можно интерпретировать как мутабельный так и иммутабельный. И т.к. в случае reduce создается пустой объект — соответственно нет необходимости в преобразованиях вида
Иммутабельный->Мутабельный и Мутабельный->Иммутабельный

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


JS != Haskell, зачем лепить горбатого, пишите на Haskell, транслируйте в WASM и будет вам счастье.

JS != Haskell, зачем лепить горбатого, пишите на Haskell, транслируйте в WASM и будет вам счастье.

Хм. Было бы интересно почитать про подобный практический опыт.

Такого опыта из реальных приложений ещё даже для C++ нету, а тут ещё про Haskell заикаются.
гораздо лучше — plus(objA, objB).


Это чем лучше? Естественно писать a + b, а значит a.plus(b) — более естественный способ записи. А учитывая перегрузку операторов — он вообще сводится к a + b.


Раз Вы сами заговорили про перегрузку операторов, каким образом она реализуется?
...
public static ComplexNumber operator+(ComplexNumber a, ComplexNumber b)
    {
        return new ComplexNumber(a.real + b.real, a.imaginary + b.imaginary);
    }
...


Ничего не напоминает статический метод? Да ведь это обычная функция :) Просто ее нужно ведь куда-то поместить… Статический метод, кстати это маркер того, что на самом деле нам нужна функция, а не метод.
На самом деле все просто — если мы пишем a.plus(b) — мы делегируем ответственность за операцию классу a. А чем он лучше чем класс b? (Конечно имеется ввиду что а и b разного типа).

В случае plus(a, b) типы a,b равноценны.
Да ведь это обычная функция :)

И что? Вообще-то императивный код не отрицает процедур. И чистых функций. Например Math.sqrt. И на том же C# я вполне люблю использовать Linq:

int CountActiveModules (Room[] rooms) {
  return rooms
    .Where(room => room.IsActive())
    .Sum(room => room.modules);
}


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

На самом деле все просто — если мы пишем a.plus(b) — мы делегируем ответственность за операцию классу a. А чем он лучше чем класс b?

А как тогда на ФП написать, когда реально первое «лучше» чем второе. Раз функция — это когда равнозначые. Ну этот пример.
category.concat(newItem)
Или просто заюзать одно из двух — интерфейсы или дженерики, зависимо от деталей задачи. И мы кроме кратного кода получим типобезопасность и поддерживаемость.

Я что-то пропустил, и в JS появились интерфейсы и/или дженерики? Или это код на шарпе?

Это чем лучше? Естественно писать a + b, а значит a.plus(b) — более естественный способ записи. А учитывая перегрузку операторов — он вообще сводится к a + b.

А как вы коммутативность обеспечите в общем случае? Ну там int + float, complex + long? Расширяемость по типам — плюс тип и все классы править/etc?
У меня есть два варианта. Один более читабельный:
const hash = new Map()

const isItemValid = (item) => item.isValid()

const addItemToCategoryInHash = (hmap, {category, id}) =>
  hmap.has(category)
    ? hmap.set(category, [id])
    : hmap.set(category, [...hmap.get(category), id])

const getHash = (array) => array
  .filter(isValid)
  .reduce(addItemToCategoryInHash, hash)


Другой быстрее из-за отсутствия лишних проходов:
const hash = new Map()

const addValidItemToCategoryInHash = (hmap, item) =>
  item.isValid()
    ? hmap.has(item.category)
      ? hmap.set(item.category, [item.id])
      : hmap.set(item.category, [...hmap.get(item.category), item.id])
    : hmap

const getHash = (array) => array.reduce(addValidItemToCategoryInHash, hash)
JSmitty
Операция — классическая функция, и на ООП вид ложится очень некрасиво. Извините, но вот так писать — objA.plus(objB) — просто фу, гораздо лучше — plus(objA, objB). Экстремистское ООП (java) вынуждает писать вот такое во втором случае — Helper.plus(objA, objB).

O, спор о красоте — это уже хорошо — ибо остальное можно просто доказать. ;-)

Понимаете, «plus» то может быть много?
Когда вы пишите plus(objA, objB) вы какой «plus» имеете ввиду? Откуда вы взяли эту функцию?
Вы её наверняка где-то вверху импортировали, к примеру, из какого-то файла, так?

Записи же:
Helper.plus(objA, objB)
MyHelper.plus(objA, objB)
OtherHelper.plus(objA, objB)


однозначно определяют — прямо в строке — откуда взялась эта функция (обычно статический метод в Java). — Не так ли?

Запись вида
objA.plus(objB)
или 
objA.addObject(objB)

Обычно выражает что objA что-то «втягивает» («прибавляет») к себе (в данном случае объект objB)
И обычно применяется к объектам типа список:
MуList.addObject(objB)

А вот выражение:
addObject(MуList, objB)

уже потребует пояснения о порядке следования параметров — типа, первым следует тот куда добавляют то, что следует вторым. — Согласитесь, это потребует некого напряжения ума. Оно того стоит?

Думается мне, чтобы не заморачиваться (не напрягаться лишний, ненужный раз) в ФП и пишут типа функции с одним параметром, типа:

var addObject = list => obj => {
    list.push(obj);
  return list
}

var mуList = [];
addObject(mуList)("test objB");
addObject(mуList)("test objC");

// Возвращает mуList = [ "test objB", "test objC" ]

Хотя это не спасает от того, что приходиться пояснять, что писать:
addObject("test objB")(mуList);

нельзя.

JSmitty
Думаю, что всем понятно, что трансформация массивов через map/filter/reduce удобнее и читается легче. Хотя и по идее медленнее, чем цикл (накладные расходы на вызов функций).

Пишу перемежая map и обычный цикл for…
И когда пишу for… испытываю горечь и угрызения совести что не пишу в этом месте map. Но узнав что for… быстрее, горечь проходит. ;-)

JSmitty
Еще ФП упрощает переиспользование кода, в классическом ОО для этого надо очень много телодвижений совершать. Обход дерева — есть тип А, у нее есть свойство children, в котором массив А, надо обойти A. Для типа B, который тоже с children — придется или: а) копипастить код обхода, б) делать абстрактного предка с методом обхода — что может быть недоступно (разные иерархии), в) использовать трейт или (java) дефолтную реализацию интерфейса — что в языках появилось вот только-только по сути.


Новинка типа «дефолтная реализация метода интерфейса» — это конечно иногда спасает от копи-пасты. Это верно.

Но понимаете что - классическое ООП подразумевает что реализация метода в разных объектах… разная.

Обычно в начальник учебниках по ООП приводят зоопарк — и обходя зоопарк каждое животное издаёт свой звук при вызове метода yelp()

JSmitty
Против старой-доброй функции обхода дерева, внешней к типу — которой будет без разницы, что обходить. И разные действия можно комбинировать и запоминать как новые функции. Которые будут относительно универсальны по вводу и выводу. На ООП такое вынужденно обрастет лесом вспомогательного кода.


Вот, вам придётся городить где-то во вне связь между объектом и его голосом -> yelp()
В ООП место для хранения поведение определено чётко — это объект.

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

Вы считаете что это нормально?

Нормальный ФП в JS сложно представить. В Хаскеле например функции типизированные, т.е. именно та функция, которая умеет с типом работать — и будет вызвана (полиморфизм). Поэтому ваш пример про addObject (который еще и мутирующий входящий аргумент) — будет мимо (сигнатура будет [a] -> a -> [a]). Надо наоборот — пишите функцию для a -> [a] -> [a].

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

Перефразируя ваш пример про зоопарк — когда у вас рядом дендрарий, и его так же надо обойти, и каждое растение тоже издает свой звук.

ФП (как мне кажется, возможно не очень обоснованно) разделяет поведение и данные, и считает первичным именно поведение.

По скорости работы map vs for — в конечном итоге для языков высокого уровня всё упирается в эффективность компилятора/интерпретатора. Простой цикл обработки для конкретного CPU эффективнее всего писать на асме, если уж на то пошло. JS VM ни разу под ФП не заточена, странно было бы ожидать большую скорость.

Преимущество здесь — в декларативности, лучшей понятности и (да, субъективно) красивости.

function f(arr) {
  for (let item of arr) {
    if (item.isValid() { continue; }
    // processing
  }
  return _processed_data_;
}

// vs

function f(arr) {
  return arr.filter(i => i.isValid()).map(i => { /* processing */ });
}


PS Не очень понимаю агрессивный тон некоторых (не вас) комментирующих. Статья мне тоже не нравится, но это ничего не говорит о ФП — там есть конструктивные элементы, которые можно (и нужно) использовать. Очень надеюсь, что хотя бы .filter к массивам применяют в некритичных к скорости местах, вместо тут показанного if (...) { continue; }
однозначно определяют — прямо в строке — откуда взялась эта функция (обычно статический метод в Java). — Не так ли?

Не так. Вы всё равно импортируете класс, а затем в коде его используете. Без знания, откуда класс импортирован — вы не можете точно рассчитывать на какую-то реализацию. С импортом функции — никаких различий.
Вот как можно решить, используя стрелки Клейсли
Собственно решение занимает по факту 1 строчку:
const totalSallary = R.compose(R.sum, R.pipeK(departments, branches, sectors, sallary))

Ниже привел захардкоженные данные, чтобы можно было поиграться
/* Функции определящие стуктуру нашего институа. 
В них просто захордкожены данные */

//Institue -> [Department] 
//По институту возвращает список отделений
const departments = institue => ['D1', 'D2', 'D3'];

//Department -> [Branch]
//По отделению возвращает список отделов
const branches = department => {
  switch (department) {
    case 'D1': 
      return ['B11', 'B12', 'B13'];
    case 'D2':
      return ['B21', 'B22'];
    case 'D3':
      return ['B31']
  }
}

//Branch -> [Sector]
//По отделу возвращает список секторов
const sectors = branch => {
  switch(branch) {
    case 'B11':
      return ['S111', 'S112'];
    case 'B12':
      return ['S121']
    case 'B13':
      return ['S131', 'S132']
    case 'B21':
      return ['S211']
    case 'B22':
      return ['S221', 'S222']
    case 'B31':
      return ['S311']
   }
}
//Sector -> [Sallary]
//Возвращает зарплату для сектора
const sallary = sector => [+sector.slice(1, 4)];

//Institue -> Int
//Возвращает суммарную ЗП по институту
const totalSallary = R.compose(R.sum, R.pipeK(departments, branches, sectors, sallary))

totalSallary('Some institue')


Покажите теперь пожалуйста, как вы реализовываете это на ООП.
wheercool
Покажите теперь пожалуйста, как вы реализовываете это на ООП.

Мощно вы написали. Но я мало что понял. :-(

//Возвращает зарплату для сектора
const sallary = sector => [+sector.slice(1, 4)];

//Institue -> Int
//Возвращает суммарную ЗП по институту
const totalSallary = R.compose(R.sum, R.pipeK(departments, branches, sectors, sallary))

То есть вам чтобы подсчитать totalSallary надо передать в функцию заранее известный состав Института? -> departments, branches, sectors

А если он меняется? Если там нет секторов или есть отделы не входящие в Департамент, а подчиняющие прямо директору?

Вот мой примитивный пример — я обошёлся без классов, но с объектами.
Каждый объект имеет и данные и методы. Ну, это же фишка ООП.

Как обычно в ООП — мы только и делаем что дёргаем метод объекта, если у объекта есть метод.

Вы считаете ваш пример выше понятнее или лучше?

var getSallary = function (){
    var sallary = this.sallary || 0;
    if (this.consist) {
        sallary = this.consist.reduce(function(sum, current) {
                  return sum + (current.getSallary? current.getSallary() : 0);
        }, 0);
    }
    return sallary;
};

var getWeight = function (){
    var weight = this.weight || 0;
    if (this.consist) {
        weight = this.consist.reduce(function(sum, current) {
                 return sum + (current.getWeight? current.getWeight() : 0);
        }, 0);
    }
    return weight;
};

var getPower = function (){
    var power = this.power || 0;
    if (this.consist) {
        power = this.consist.reduce(function(sum, current) {
                return sum + (current.getPower? current.getPower() : 0);
        }, 0);
    }
    return power;
};   

var getNames = function (){
    if (this.consist) {
        this.consist.forEach(function(current) {
            current.getNames();
        });
    } else if (this.sallary) {
      console.log(' ' +this.name);        
    }
};

var institut = {
                name : 'NII-CHAVO',
                getNames,
                getSallary,
                getWeight,
                getPower,
                consist: [
                    {
                    name : 'Petrov-director',
                    sallary: 10000,
                    getNames,
                    getSallary},
                    {
                    name : 'Ivanova-secretar',
                    sallary: 1000,
                    getNames,
                    getSallary},
                    {
                    name : 'Sidorov-vodila',
                    sallary: 1000,
                    getNames,                    
                    getSallary},
                    {
                    name : 'avtomobil for director',
                    weight: 4000,
                    power: 400,
                    getNames,                    
                    getWeight,
                    getPower},
                    {
                    name : 'Departament1',
                    getNames,
                    getSallary,
                    getWeight,
                    getPower,
                    consist: [
                      {
                      name : 'Petrov-ml',
                      sallary: 5000,
                      getNames,                      
                      getSallary},
                      {
                      name : 'Sidorova-worker',
                      sallary: 500,
                      getNames,                      
                      getSallary},
                      {
                      name : 'avtomobil for Departament1',
                      weight: 3000,
                      power: 200,
                      getNames,                      
                      getWeight,
                      getPower},
                      {
                      name : 'Otdel-11',
                      getNames,                      
                      getSallary,
                      getWeight,
                      getPower,
                      consist: [
                        {
                        name : 'Ivanov-shef',
                        sallary: 3000,
                        getNames,                        
                        getSallary},
                        {
                        name : 'Sidorova-ml-worker',
                        getNames,                        
                        sallary: 500,
                        getSallary}]}]},
                    {
                    name : 'Departament2',
                    getNames,                    
                    getSallary,
                    getWeight,
                    getPower,
                    consist: [
                      {
                      name : 'Petrova',
                      sallary: 5000,
                      getNames,                      
                      getSallary},
                      {
                      name : 'Stepanova-worker',
                      sallary: 500,
                      getNames,                      
                      getSallary},
                      {
                      name : 'avtomobil for Departament2',
                      weight: 2500,
                      power: 150,
                      getNames,                      
                      getWeight,
                      getPower},
                      {
                      name : 'Otdel-21',
                      getNames,                      
                      getSallary,
                      getWeight,
                      getPower,
                      consist: [
                        {
                        name : 'Petrovich-shef',
                        sallary: 3000,
                        getNames,                        
                        getSallary},
                        {
                        name : 'Stepanova-ml-worker',
                        sallary: 500,
                        getNames,
                        getSallary}]}]}],
                 };

console.log(`Total Sallary: ${institut.getSallary()}`); // 30000
console.log(`Total Weight: ${institut.getWeight()}`); // 9500
console.log(`Total Power: ${institut.getPower()}`); // 750

console.log('List of employees: ');
institut.getNames();



Можно просто кинуть этот код в консоль броузера (или в сниппеты броузера) и… выполнить.

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

Да, семейственность, но уж как есть так есть. ;-)

Дерево содержит «узлы» и «листья».
«Узлы» всегда имеют методы getName, getSallary, getWeight, getPower.
«Листья» имеют только те методы, значения для которых у них есть. Например автомобиль не имеет метода getSallary ибо зарплаты не получает.
Но это не принципиально. Дерево может быть любое. Главное что это не данные, а это объекты.

P.S. Да, копи-пасты в моём коде (в функциях get...) хватает, можно было иметь одну универсальную функцию, наверное. Но этот код для ясности понимания… ООП. По крайней мере я надеюсь. ;-)
Очень приятно, что вы все-таки не ООП-экстремист (это про .reduce :) ). Вы с предыдущим комментатором решаете несколько разные задачи. Проектирование в JS да, отчасти ближе к ООП, чем к ФП — но тоже, при просмотре структуры данных вы неявно полагаетесь на то, что все «узлы» и «листья» будут поддерживать один и тот же интерфейс. Форсировать это ограничение — нормально нельзя (как в Java или C++).

И все же, согласитесь, композиция функции подсчета из более простых — выглядит забавно (да, я тоже вынужден был гуглить, что делает R.pipeK).
JSmitty
Очень приятно, что вы все-таки не ООП-экстремист (это про .reduce :) ).

Виноват. Хотел написать for… но потом вот типа сократил.
Но тут надо с этим поосторожнее.
image

JSmitty
Проектирование в JS да, отчасти ближе к ООП, чем к ФП — но тоже, при просмотре структуры данных вы неявно полагаетесь на то, что все «узлы» и «листья» будут поддерживать один и тот же интерфейс.

Обычно это так. Но у меня в примере у всех объектов он, «интерфейс», разный.
Например, автомобиль имеет вес и мощность (и соответствующие методы) но не имеет метода для get зарплаты, которой у него нет.
Неявно только один метод у всех — getNames().

А вообще тут только одно я показал — обход дерева разношерстных объектов и то что объект в ООП — это данные и метод вместе взятые (а не по отдельности).

JSmitty
И все же, согласитесь, композиция функции подсчета из более простых — выглядит забавно (да, я тоже вынужден был гуглить, что делает R.pipeK).

То что ФП забавно — тут спору нет. :-)
Куда делась эта проверка в функциональном коде?

Вы пропустили: path(['prefs', 'languages', 'primary'])


Она сокращается в 4 раза без потери смысла.

Тут такое дело, что использовав Ramda, они избавляют себя от проверок на null/undefined на всём пути использования, так что если user.perfs.languages будет null, код не сломается и так везде. В итоге получается типа безопасно, а функции простыми и чистыми, не перегруженными лишними проверками.


Но я ненастоящий сварщик, да и 200+ методов...

Вы пропустили: path(['prefs', 'languages', 'primary'])

Нет, я не пропустил. Этот кусок не проверяет, содержится ли там внутри строка 'undefined'. Конечно, автор мог ошибится и имел ввиду безсмысленную проверку «typeof x == 'undefined'».

… использовав Ramda, они ...


Тут можно задать другой вопрос — зачем снова эта ложь и присваивание достижений сторонней библиотеки функциональной парадигме?

Вот то же самое используя код из библиотеки MooTools 10-тилетней давности:
var primary = Object.getFromPath(indexURLs, 'prefs.languages.primary');


То есть «ФП с кучей сторонних библиотек круче, чем другие парадигмы без библиотек»? «Шахматист с автоматом убъет боксера, значит боксеры — слабее шахматистов»?

Ну таки пропустил, вот это может упасть с RTE
indexURLs[user.prefs.languages.primary]

если пользователь есть, но без настроек, а вот это не упадет
Maybe.of(user).map(path('prefs.languages.primary')).getOr(default);
Конечно, автор мог ошибится

100% ошибка, иначе глупо, да и примеры будут неравнозначные тогда.


MooTools 10-тилетней давности

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


То есть «ФП с кучей сторонних библиотек круче

По мне так всё началось с того, что кто-то угорел по Хаскелю и потом решил, что коль в JS есть функции (да ещё и анонимные), значит JS функциональный язык, но т.к. на этом сходство закончилось и Монад™ не оказалось… имеем, что имеем :]

100% ошибка, иначе глупо, да и примеры будут неравнозначные тогда.

Тогда эта проверка добавлена исключительно для того, чтобы сделать классический код «еще более ужасным», она не имеет никакого смысла

в ФП всё интереснее

А почему в топике это не освещено?
А почему в топике это не освещено?

¯\_(ツ)_/¯ Тут как и с любым другим примером Bad vs. Good. Bad — всегда нарочито «грязный», а Good бескомпромиссный, но реальный мир другой.


В ФП есть интересные идеи и подходы, многие мы в той или иной степени уже давно используем, но смотря на 200+ методов типичной Ramd'ы, хочется только посочувствовать ребятам. Но, как известно, адептам FP и/или FRP и так норм, так что все счастливы :]

Но, как известно, адептам FP и/или FRP и так норм, так что все счастливы

Стокгольмский синдром же. Жаль только, что они насильника навязывают окружающим.
Из реальных проблем для которых ФП структуры данных хорошо помогли — реализация аналога json-path. Правда все готовые реализации ужасно написаны и их невозможно наследовать, особенно плох имбютаблжс и рамда имьютабл, но они в принципе все ужасны (вместо синглтонов используют скрытые подклассы, имьютаблжс вообще кривой, нарушает спецификацию и работает только потому, что предварительно компилится бабелем). И вот это вообще в них бесит
const a = Maybe.of({}); // Just({})
const b = a.map(prop('x')); // Just(undefined) - неведомая бесполезная чушь
const c = b.map(prop('y')); // Error

Сделано это в угоду соответствия определениям (u.map(x => f(g(x))) is equivalent to u.map(g).map(f) обе части будут кидать ошибку), но полностью убивает смысл мейби монады которая должна от таких ошибок спасать (хотя можно было ограничить множество функций для которых определение должно работать и все было бы прекрасно). Можно переписать то же через чейн, но это начинает выглядеть плохо и опять же плохо расширяется и наследуется.
Не понял 1 :
//Монада — образец реализации
class Monad {
    constructor(val) {
        this.__value = val;
    }
    static of(val) {//Monad.of проще, чем new Monad(val)
        return new Monad(val);
    };
    map(f) {//Применяет функцию, но возвращает другую монаду!
        return Monad.of(f(this.__value));
    };
    join() { // используется для получения значения из монады
        return this.__value;
    };
    chain(f) {//вспомогательная функция, преобразующая (map), а затем извлекающая значение
        return this.map(f).join();
    };
     ap(someOtherMonad) {//используется для работы с несколькими монадами
        return someOtherMonad.map(this.__value);
    }
}


ap(someOtherMonad) {//используется для работы с несколькими монадами
return someOtherMonad.map(this.__value);

Но ведь
map(f) {//Применяет функцию, но возвращает другую монаду!
return Monad.of(f(this.__value));
};

map принимает на вход не value, а f (фунцию). Как же так?

Не понял 2 :
//Глобал indexURLs сопоставлен с разными языками
let indexURLs = {
    'en': 'http://mysite.com/en',  //English
    'sp': 'http://mysite.com/sp', //Spanish
    'jp': 'http://mysite.com/jp'   //Japanese
}

//Императивное решение
const getUrl = (language) => allUrls[language]; //Просто, но грязно (обращение к глобальной переменной), и есть риск ошибок


Наверное надо писать?:
//Императивное решение
const getUrl = (language) => indexURLs[language]; //Просто, но грязно (обращение к глобальной переменной), и есть риск ошибок


P.S.
Как-то не верится что эта довольно разжёванная статья как-то подвинет в сторону понимания ФП.

Имхо, конечно, имхо (С)

по второму — ну прямо вот функционально :)
const getUrl = (indexURLs) => (language) => indexURLs[language];
ap(someOtherMonad) {//используется для работы с несколькими монадами
return someOtherMonad.map(this.__value);

Но ведь
map(f) {//Применяет функцию, но возвращает другую монаду!
return Monad.of(f(this.__value));
};

В случае ap в контейнере находится не значение, а функция. Т.е. this.__value — это функция поднятая на уровень контейнера.

P.S.
Как-то не верится что эта довольно разжёванная статья как-то подвинет в сторону понимания ФП.

Согласен с Вами. Как уже упоминал выше, если есть желание действительно разобраться почитайте это.
Sign up to leave a comment.