Python: функциональное программирование с минусами, автомобилем и компакт-диском

Вот эта проблема:

cons(a, b) создает пару, а car(pair) и cdr(pair) возвращают первый и последний элементы этой пары. Например, car(cons(3, 4)) возвращает 3, а cdr(cons(3, 4)) возвращает 4.

Теперь я видел решение, но мне интересно, может ли кто-нибудь объяснить, как на самом деле думать, чтобы достичь решения?

  1. cdr(cons(3, 4)): в каком порядке оцениваются эти две функции? Обычно я думаю, что сначала оцениваются cons(3, 4), но в данном случае это не имеет смысла, потому что cons(3, 4) возвращает функцию, в которой аргументы 3 и 4 интегрированы, поэтому нет возможности выделить аргументы.
  2. Мне кажется, что car(f) возвращает функцию, так как же cons(3, 4) вернуть 3? ИЗМЕНИТЬ: опечатка, должно быть car(cons(3, 4)) вместо cons(3, 4)
  3. Я, очевидно, пытаюсь решить эту проблему, потому что хочу изучить Python, но вы бы порекомендовали мне пропустить эти проблемы? Я испытываю искушение сделать это, прочитав здесь: Зачем программировать функционально на Python?

Решение:

def cons(a, b):
    def pair(f):
        return f(a, b)
    return pair

def car(f):
    def pair(a,b):
        return a
    return f(pair)

def cdr(f):
    def pair(a, b):
        return b
    return f(pair)

print(car(cons(3,4)))
Output: 3
print(cdr(cons(3,4)))
Output: 4

person Helen    schedule 24.09.2020    source источник
comment
. . . но в данном случае это не имеет смысла, потому что cons(3, 4) возвращает функцию. Обратите внимание на имя параметра cdr и его использование. То же самое и с car.   -  person Carcigenicate    schedule 24.09.2020
comment
Это не лучшее упражнение для новичка. Это довольно сложное использование вложенных функций, и его трудно четко объяснить.   -  person Barmar    schedule 24.09.2020
comment
А подобное кодирование в реальном мире почти никогда не применяется. Это немного связано с тем, как работают декораторы, но сложнее, чем большинство из них.   -  person Barmar    schedule 24.09.2020
comment
@Carcigenicate: спасибо за помощь :) Да, я это заметил, но я просто не могу понять, как можно выделить аргументы из уже созданной функции f ... извините, я понятия не имею, как даже объяснить себя   -  person Helen    schedule 24.09.2020
comment
Если они хотят, чтобы вы изучили Лисп, почему бы не научить вас Лисп (или Схему)? Конечно, Python - это мультипарадигма, но зачем изобретать в нем Лисп?   -  person ShadowRanger    schedule 24.09.2020
comment
@Barmar: да, я как бы надеялся, что кто-то скажет, что это бесполезно в реальном мире, я просто ненавижу сдаваться, ничего не понимая =)   -  person Helen    schedule 24.09.2020
comment
@ShadowRanger: это вопрос из задачи dailycoding, которую я использую, чтобы научить себя Python, поэтому я всего лишь пытаюсь стать лучше программистом, решая подобные вещи каждый день.   -  person Helen    schedule 24.09.2020
comment
Это работает так: вложенные функции являются замыканиями. pair() запоминает значения параметров внешней функции.   -  person Barmar    schedule 24.09.2020


Ответы (3)


В a(b()) первым будет всегда оцениваться b. Я не знаю ни одного исключения из этого в Python. a должен быть макросом, чтобы было верно обратное.

Обратите внимание на названия параметров cdr и car: f, как в функции. Каждая функция принимает функцию в качестве аргумента. Это работает, потому что, как вы заметили, cons возвращает функцию.

В car(cons(3,4)) cons возвращает функцию (локально известную как pair). Затем эта функция передается car, и car использует ее здесь: f(pair). В данном случае f - это переданная функция. Сложность состоит в том, что f - это функция, которая принимает другую функцию и вызывает ее с двумя аргументами. Эти два аргумента - данные, которые изначально были переданы cons: 3 и 4.


cons(3, 4) не возвращает 3, car(cons(3,4)) возвращает. cons(3, 4) возвращает функцию, которая воздействует на данные, которые ей были переданы. В этом случае функция car pair отбрасывает второй переданный аргумент (4) и вместо этого возвращает первый (3).


Да, держитесь подальше от такого кода какое-то время. Передача функций довольно полезна, но этот код - скорее экспериментальная игрушка. Это теоретический код для отображения стиля (полученный из шепелявого Scheme, основанного на терминологии). Есть много более простых способов добиться того же конечного результата.

Практикуйте простые примеры функций высшего порядка (например, map и reduce), овладевайте ими , затем вернитесь к этому коду. Его по-прежнему будет трудно понять (потому что этот код не поддается легкому пониманию), но он будет иметь больше смысла.

person Carcigenicate    schedule 24.09.2020
comment
На самом деле, это, вероятно, перевод Python кода схемы из SICP. Программисты на CL этого тоже не делают. - person Barmar; 24.09.2020
comment
@Barmar Я сходил с car и cdr; Я думал, что это специфично для CL. Единственная шепелявка, которую я когда-либо слышал, - это Clojure, и он устранил все непонятные имена. - person Carcigenicate; 24.09.2020
comment
Scheme сохранил имена автомобилей, cdr и cons Лиспа. - person Barmar; 24.09.2020
comment
Но моя основная мысль заключалась в том, что подобные упражнения с использованием вложенных функций распространены в SICP, а Scheme - это язык, который он использует. - person Barmar; 24.09.2020
comment
Большое спасибо @Carcigenicate! Я все еще не понимаю, но я постараюсь прочитать ваш ответ много раз. Что касается вопроса 1: если b выполняется первым в a(b()), как a может когда-либо иметь доступ к аргументам / коду / всему, что было использовано для генерации функции b в первую очередь? cons возвращает функцию, но функцию, состоящую из аргументов 3 и 4, так как же их можно выбрать позже, после того, как функция будет сгенерирована (потому что cons выполняется до car)? - person Helen; 24.09.2020
comment
Эта функция затем передается в car, и car использует ее здесь: f (pair): Итак, в моей простой голове это означает, что car вернет cons(3,4)(pair) ... функцию, а не аргумент .. . - person Helen; 24.09.2020
comment
@Helen cons(3, 4) возвращает функцию, внутри которой сохранены 3 и 4 (известная как закрытие). Если вы думаете о функции как об объекте (которым он и является), 3 и 4 хранятся внутри как атрибуты объекта (не совсем, но как бы). Если это помогает, вы можете представить, что функция, возвращаемая cons(3, 4), выглядит как def pair(f): return f(3, 4). Аргументы не выбираются позже, значения a и b сохраняются и используются, когда он вызывается позже. - person Carcigenicate; 24.09.2020
comment
@Carcigenicate Ага ... интересно! Тогда я думаю, что смогу понять это на поверхностном / концептуальном уровне! Огромное спасибо! =) Теперь я супер в восторге =) - person Helen; 24.09.2020
comment
@Helen Если вы хотите поиграть с этим, попробуйте следующее: f = cons(3,4), который сохраняет функцию в f переменную, затем print(f.__closure__[0].cell_contents, f.__closure__[1].cell_contents). Или print(*[cell.cell_contents for cell in f.__closure__]). __closure__ - это кеш сохраненных данных, к которому вы можете получить доступ. - person Carcigenicate; 24.09.2020
comment
@Carcigenicate Очень красиво, спасибо за подсказку! У меня была опечатка в моем вопросе 2, думаю, все еще несколько неясно, как car(cons(3,4)) не возвращает функцию ... Я вижу, как pair(3,4) внутри car возвращает 3, но все же оператор возврата car равен f(pair), так почему он не возвращается f(3) = cons(3) ... может быть, глупый вопрос, никакого давления для ответа! - person Helen; 24.09.2020
comment
@Helen yes car(cons(3, 4)) возвращает f(pair), но это равно cons(3, 4)(pair), что равно pair(3, 4) (пара из car! Не замечательно, что они назвали все эти функции одинаково, только сбивает с толку), который возвращает 3. - person Jasmijn; 24.09.2020
comment
@Helen Ya, обратите внимание, как заканчивается car: return f(pair). Он не возвращает функцию (обязательно); он возвращает результат, вызывающий f. - person Carcigenicate; 24.09.2020
comment
@Jasmijn: спасибо, но я не могу понять, что cons(3, 4)(pair) равно pair(3, 4) = ( - person Helen; 24.09.2020
comment
@Carcigenicate right, извините, я очень поправился, спасибо =) - person Helen; 24.09.2020

Таким образом можно решить и указанную вами проблему.

def cons(a, b):
    return (a,b)

def car(pair):
    return pair[0]

def cdr(pair):
    return pair[1]

Вот как вы будете его использовать:

lst = cons(1,cons(2,3))

# Get the first element of lst
print(car(lst))

# Get the second element of lst
print(car(cdr(lst)))

# Get the last element of lst
print(cdr(cdr(lst)))

Вывод:

1
2
3

Я показываю это только для того, чтобы вы могли увидеть, что есть несколько способов решить эту проблему, и способ, который вы обнаружили, очень редко применяется в python. Любой, кто думает решить эту проблему на Python, в 99% случаев будет делать это так, как я показал здесь.

Теперь к твоей проблеме.


def cons(a, b):
    def pair(f):
        return f(a, b)
    return pair

def car(f):
    def pair(a,b):
        return a
    return f(pair)

def cdr(f):
    def pair(a, b):
        return b
    return f(pair)

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

cons::(a, b) -> (((a, b) -> c) -> c)

cons - это функция, которая принимает два параметра a и b, затем она возвращает функцию f, которая принимает другую функцию, которая при заданных параметрах (_10 _, _ 11_) возвращает c, где c может быть a или b, или что-то еще. f затем возвращает значение c.

Какой полный рот!

Другой способ представить это - то, что функция f (((a, b) -> c) -> c), возвращаемая cons, используется для пересылки a и b любому оператору (или функции сопоставления), который хочет воздействовать на cons. Этот оператор возвращает c. f, тогда simple возвращает все, что возвращает эта функция сопоставления, что оказывается c.

А пока не беспокойтесь, что такое c. Подумайте об этом как о результате применения функции к cons.

car::(((a, b) -> a) -> a) -> a

car определяет возможное отображение от (a,b) к c и возвращает значение вызова f с этим отображением.

car принимает функцию f, которая хочет сопоставить вход (a,b) с некоторым выходом c. В этом случае car определяет отображение как (a, b) -> a, что означает, что любая функция f, переданная в car, вернет первый аргумент (a,b), который равен просто a. И это то, что car вернет.

cdr::(((a, b) -> b) -> b) -> b

Подобно car, но отображение, определенное cdr, возвращает b вместо a.


Обратите внимание, насколько входные данные для cdr и car похожи на функцию (f), возвращаемую cons? Вот почему я просто назвал их входы f


Теперь отвечу на некоторые из ваших вопросов:

cdr(cons(3, 4)): в каком порядке оцениваются эти две функции? Обычно я думаю, что сначала оцениваются cons(3, 4), но в данном случае это не имеет смысла, потому что cons(3, 4) возвращает функцию, в которой аргументы 3 и 4 интегрированы, поэтому нет возможности выделить аргументы.

В свете объяснения, которое я дал выше, функция, возвращаемая из cons, точно того же типа, что и функция, ожидаемая cdr. Теперь все, что нужно сделать cdr, - это предоставить функцию сопоставления f и вернуть все, что возвращает f.

Мне кажется, что car(f) возвращает функцию, так как же cons(3, 4) вернуть 3? ИЗМЕНИТЬ: опечатка, должно быть car(cons(3, 4)) вместо cons(3, 4)

car(f) не обязательно возвращает функцию. См. Подписи типов выше. Он просто возвращает то, что возвращает f, и если это будет функция, то это то, что он вернет функцией.

Обычно car возвращает первый элемент cons. В этом случае, поскольку cons(3,4) возвращает функцию (f) и эта функция передается в car, тогда car предоставит этой функции другую функцию, которая выбирает первый из своих аргументов, которым в данном случае является 3. Этот 3 теперь результат car(cons(3,4).

Надеюсь, это проясняет ситуацию.

person smac89    schedule 25.09.2020
comment
Большое спасибо за подробное объяснение! Боюсь, я не понимаю нотацию haskell и новый способ написания функций cons, car и cdf меня немного сбивают с толку. Я читал ваш ответ много раз. Вы довольно хорошо объясняете шаги, я думаю, я до сих пор просто не понимаю, почему то, что происходит на каждом шаге, происходит, что является моей проблемой. - person Helen; 25.09.2020

Вставленный вами код предназначен для использования следующим образом:

Сначала вы определяете свою пару:

f = cons(3, 4)

После этого вы определяете функцию, которая работает с парами:

add = lambda x, y: x + y

Теперь вы можете использовать свою пару следующим образом:

f(add)

вывод:

7

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

Edit:

Если Вы не знакомы с lambda выражениями, см. это руководство.

На данный момент вы также можете использовать

def add(x, y):
    return x + y

и используйте его точно так же. :)

person N. Jonas Figge    schedule 24.09.2020
comment
Это на самом деле не объясняет, что происходит внутри функций, и в этом-то и заключается путаница. - person Barmar; 24.09.2020