На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
Модераторы: ElcnU, ANDLL, fatalist
  
> MonadDemo , Functional vs Imperative
    Предлагаю вашему вниманию демо-пример, где четко :D показано различие фунционального от императивного стиля программирования. Первая часть кода содержит императивный подход, а вторая - функциональный с использованием монады Maybe:
    ExpandedWrap disabled
      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.'));

    Чтобы не писать ручками все функциональные приблуды :D, заюзал библу Ramda и ramda-fantasy.
    Сообщение отредактировано: Cfon -
      Дальше ожидается "и ..." :)
        Цитата JoeUser @
        Дальше ожидается "и ..." :)

        чуть позже запосщу детальное обсуждения фунционального подхода.
          подравил код findPerson, поскольку R.compose возвращает функцию, параметрами которой являются параметры последней переданой функции, то ее обертывание не требуется. Также выделил операцию взятие по индексу в отдельную функцию, тем самым улучшив ясность кода:
          ExpandedWrap disabled
            const findPerson = R.compose(
                person => Maybe.toMaybe(person),
                persons => persons[0],
                (persons, name) => persons.filter(p => p.name === name)
            );

          как видите findPerson состоит из трех последовательных вызовов функций, отсчет начинается снизу, последний вызов вернет результат в виде монады. Заметьте вызов этих функций произойдет тока когда будет обращение к собствено findPerson.
          На этом строиться вся логика функционального программирования, т.е. мы составляем нужный функционал из коротких функций, которые легко читать и понимать. Эти функции в свою очередь являются чистыми, т.е. без побочных эффектов, или еще их называют ссылочно-прозрачными, это когда результат функции всегда зависит от его входных параметров и не меняется. Это приводит к тому что их легко тестировать. Вооот :D
          Сообщение отредактировано: Cfon -
            Предыдущий пример можно еще сократить, поскольку Maybe.toMaybe принимает один параметр, ее можно сразу передать в compose:
            ExpandedWrap disabled
              const findPerson3 = R.compose(
                  Maybe.toMaybe,
                  persons => persons[0],
                  (persons, name) => persons.filter(p => p.name === name)
              );


            - Что такое Maybe?
            Maybe это такая клевая штукенция :D, которая позволяет отложить обработку ошибок на момент когда это потребуется без генерации исключений. К примеру вот как это выглядит в случае исключений без Maybe
            ExpandedWrap disabled
              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 не требуется и что самое интересное обработать результат этой функции можно когда угодно:
            ExpandedWrap disabled
              var p = findPerson(persons, 'Gregory');
              //... какой то еще код...
              var result = p.map(R.prop('name')).getOrElse('Person not found.');
              //=> Gregory

            Да конечно можно и не выбрасывать исключение и просто возвращать объект ошибки:
            ExpandedWrap disabled
              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);
              }

            но в это случае нам всеравно придется писать код обратотки ошибочного условия, что приводит как спагетти-коду читай гавнокод :D

            - Что означает p.map(R.prop('name')?
            map - тоже что и в случае Array.prototype.map :D отображает, переданную функцию на данные и возвращает новый объект 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.')? :huh:
            :wall: :blink: :D
            Как я уже писал выше map возвращает новый объект Maybe его еще называют монада.
            Так вот это вот монада буть она не ладна! :D хранит наше значение, в нашем случае значение name и чтобы корректно получить его, а не через p.value.name (хотя так тоже можно) юзается getOrElse, который вернет в зависимости от исхода map либо name, либо строку, переданную через параметр, в нашем случае это 'Person not found.'

            - А что такое монада? :huh:
            Это еще одно модное словечко, которое стоит запомнить и сувать его везде где тока можно, чтобы показать что ты не джун, а сеньор JS :D
            Ну а если серьезно то монада это математический термин из теории категорий, что он там обозначает я не интерисовалси, а в программировании это тип данных, со специфическим фейсом :D
            Сообщение отредактировано: Cfon -
            0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
            0 пользователей:


            Рейтинг@Mail.ru
            [ Script execution time: 0,0236 ]   [ 15 queries used ]   [ Generated: 24.04.24, 13:02 GMT ]