Являются ли функции генератора действительными в функциональном программировании?

17

Вопросы:

  • Разрушают ли генераторы парадигму функционального программирования? Почему или почему нет?
  • Если да, могут ли генераторы использоваться в функциональном программировании и как?

Учтите следующее:

function * downCounter(maxValue) {
  yield maxValue;
  yield * downCounter(maxValue > 0 ? maxValue - 1 : 0);
}

let counter = downCounter(26);
counter.next().value; // 26
counter.next().value; // 25
// ...etc

downCounterМетод оказывается без гражданства. Кроме того, вызов downCounterс одним и тем же вводом всегда будет приводить к одному и тому же выводу. Однако, в то же время, звонки next()не дают последовательных результатов.

Я не уверен, нарушают ли генераторы парадигму функционального программирования, потому что в этом примере counterэто объект генератора, и поэтому вызов next()вызовет те же результаты, что и другой объект генератора, созданный с точно такой же maxValue.

Кроме того, вызов someCollection[3]массива всегда будет возвращать четвертый элемент. Аналогично, next()четыре раза вызов объекта-генератора также всегда возвращает четвертый элемент.

Для большего контекста эти вопросы были подняты во время работы над ката программирования . Человек, который ответил на вопрос, поднял вопрос, могут ли генераторы использоваться в функциональном программировании и имеют ли они состояние.

Пит
источник
2
Каждая программа содержит состояние. Реальный вопрос заключается в том, квалифицируется ли оно как функциональное состояние, которое я интерпретирую как «неизменное состояние», состояние, которое не изменяется после назначения. Я утверждаю, что единственный способ заставить генератор возвращать что-то свое при каждом вызове, это если каким-то образом изменить изменяемое состояние.
Роберт Харви,

Ответы:

14

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

function downCounter(maxValue) {
  return {
    "value": maxValue,
    "next": function () {
      return downCounter(maxValue > 0 ? maxValue - 1 : 0);
     },
  };
}

let counter = downCounter(26);
counter.value; //=> 26
counter.next().value; //=> 25

Очевидно, что downCounterон настолько чистый и функциональный, насколько это возможно. Здесь нет проблем.

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

counter.next().value; //=> 25
otherFunction(counter); // does this consume the counter?
counter.next().value; // what will this be? It depends on the otherFunction()

Очевидно, что генераторы поддерживают состояние и поэтому не подходят для «чистого» функционального программирования. К счастью, вам не нужно заниматься чисто функциональным программированием, и вы можете быть прагматичными. Если генераторы делают ваш код более понятным, вы должны использовать их без совести. В конце концов, JavaScript не является чисто функциональным языком, в отличие, например, от Haskell.

Кстати, в Haskell нет разницы между возвратом списка и генератора, поскольку он использует ленивую оценку:

downCounter :: Int -> [Int]
downCounter maxValue =
  maxValue : (downCounter (max 0 (maxValue - 1)))
-- invoke as "take n (downCounter 26)" to display n elements
Амон
источник