Почему итератор считается функциональным в документации Python?

Я пытаюсь понять итератор. Я замечаю документацию Python, считает iterator конструкцией функционального стиля. Я этого не понимаю.

Не правда ли, что итератор имеет внутри него. Поэтому, когда вы вызываете it.__next__(), вы мутируете состояние итератора. Насколько мне известно, мутировавшее состояние объекта не считается функциональным, поскольку функциональное программирование подчеркивает неизменность и способность компоновки объекта/закрытия.

На самом деле проблема возникает из-за того, что я хочу написать процедуру/функцию Scheme, которая принимает токены и возвращает итератор.

(define tokens->iterator
  (lambda ls
    (lambda ()
      (if (null? ls)
          '*eoi*
          (let ((tok (car ls)))
            (set! ls (cdr ls))
            tok)))))

Обратите внимание, что я должен использовать set! для мутирования ls, вот как я поднимаю этот вопрос.

Чтобы использовать его,

(define it (tokens->iterator 1 '+ 2))

Чтобы проверить это,

[email protected](guile-user)> (it)
$2 = 1
[email protected](guile-user)> (it)
$3 = +
[email protected](guile-user)> (it)
$4 = 2
[email protected](guile-user)> (it)
$5 = *eoi*
[email protected](guile-user)> (it)
$6 = *eoi*

Просто для удовольствия, я также перевести это на Python:

def tokens_to_iterator(*tup):
    ls = list(tup)
    def iterator():
        if not ls:
            return "*eoi*"
        else:
            tok = ls.pop(0)
            return tok
    return iterator

Аналогично, метод pop() удаляет и возвращает первый элемент, изменяя список.

Чтобы использовать его,

it = tokens_to_iterator(1, "+", 2)

Чтобы проверить это,

>>> it()
1
>>> it()
'+'
>>> it()
2
>>> it()
'*eoi*'
>>> it()
'*eoi*'

Может ли кто-нибудь разъяснить это? Кстати, я использую Python 3 и Guile Scheme на случай, если кто-то заинтересован в том, чтобы попробовать примеры.

Ответ 1

У вас отличная точка. Итераторы, конечно, не являются "чисто функциональными", термин часто используется для описания идиом, которые вообще не используют мутации. Более широкий термин "функциональный", однако, более слабо определен для обозначения программ, которые используют относительно небольшую мутацию, которые используют функции более высокого порядка и первого класса и, возможно, наиболее широко, "используют странные абстракции, которые не выглядят как C."

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

Ответ 2

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

>>> lst = ["a", "b", "c", "d", "e"]
>>> lst[3] = "Z"
>>> lst
["a", "b", "c", "Z", "e"]

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

>>> lst = ["a", "b", "c", "d", "e"]
>>> new_lst = [x if i != 3 else "Z" for (i, x) in enumerate(lst)]
>>> lst
["a", "b", "c", "d", "e"]
>>> new_lst
["a", "b", "c", "Z", "e"]

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

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

>>> state = 0
>>> def it(lst, state):
...   if state is None:
...       return None
...   return lst[state], state + 1
...
>>> lst = ["a", "b", "c", "d", "e"]
>>> value, new_state = it(lst, state)
>>> value
'a'
>>> state, new_state
(0, 1)
>>> it(lst, new_state)
('b', 2)
>>> state, new_state
(0, 1)