【问题标题】:Python: functional programming with cons, car & cdrPython:使用 cons、car 和 cdr 进行函数式编程
【发布时间】:2020-09-24 18:55:50
【问题描述】:

有这样的问题:

cons(a, b) 构造一对,car(pair)cdr(pair) 返回 该对的第一个和最后一个元素。例如,car(cons(3, 4)) 返回3cdr(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,但是你会建议我跳过这些问题吗?我很想在这里阅读:Why program functionally in 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

【问题讨论】:

  • "...但在这种情况下这没有意义,因为cons(3, 4) 返回一个函数"。注意cdr 的参数名称和使用方法。 car 也是如此。
  • 这对初学者来说不是一个好的练习。嵌套函数的使用相当复杂,很难解释清楚。
  • 这样的编码在现实世界中几乎从未做过。这与装饰器的工作方式有点相关,但比大多数都复杂。
  • @Carcigenicate:感谢您的帮助 :) 是的,我注意到了这一点,但我无法理解如何从已经“制造”的函数“f”中挑选出参数......对不起,我什至不知道如何解释自己
  • 如果他们想让你学习 Lisp,为什么不教你 Lisp(或 Scheme)呢?当然,Python 是多范式的,但为什么要在其中重新发明 Lisp?

标签: python functional-programming cons cdr


【解决方案1】:

你展示的问题也可以这样解决。

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 是一个接受两个参数ab 的函数,然后它返回一个函数f,它接受另一个函数,当给定参数时(a,b),返回c,其中c 可以是ab,或其他。 f 然后返回c 的值。

真是一口!

另一种思考方式是,cons 返回的函数f (((a, b) -> c) -> c) 用于将ab 转发给任何想要操作的运算符(或映射函数) cons。此运算符返回 cf 然后简单返回此映射函数返回的任何内容,恰好是 c

现在不用担心c 是什么。只需将其视为将函数应用于cons 的结果。

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

car 定义了从(a,b)c 的可能映射,并返回使用此映射调用f 的值。

car 接受一个函数f,它需要从输入(a,b) 到某个输出c 的映射。在这种情况下,car 将映射定义为(a, b) -> a,这意味着传递给car 的任何函数f 将返回(a,b) 的第一个参数,即a。这就是car 将返回的内容。

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

类似于car,但cdr 定义的映射返回b 而不是a


注意cdrcar 的输入与cons 返回的函数(f) 有多么相似?这就是为什么我只是将他们的输入称为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) 的结果。

我希望这能解决问题。

【讨论】:

  • 非常感谢您的详尽解释!恐怕我不理解 haskell 符号和编写函数conscarcdf 的新方法让我有点困惑。我现在已经多次阅读你的答案了。您很好地解释了这些步骤,我想我仍然不明白为什么每个步骤会发生什么,这是我的问题。
【解决方案2】:

a(b()) 中,b总是首先被评估。我不知道 Python 中有一个例外。 a 需要是一个宏才能反之为真。

注意cdrcar的参数名称是什么:f,如“函数”。每个函数都接受一个函数作为参数。不过,这可行,因为正如您所指出的,cons 返回一个函数。

car(cons(3,4))中,cons返回一个函数(本地称为pair)。然后将该函数提供给carcar 在这里使用它:f(pair)。在这种情况下,f 是传入的函数。这里复杂的部分是 f 是一个接受 另一个 函数的函数,并使用两个参数调用它。这两个参数是最初提供给cons 的数据:34


cons(3, 4) 确实返回3car(cons(3,4)) 确实。 cons(3, 4) 返回一个函数,该函数作用于提供给它的数据。在这种情况下,carpair 函数最终会丢弃第二个传递的参数 (4),而是返回第一个参数 (3)。


是的,暂时远离这样的代码。传递函数非常有用,但这段代码更像是一个实验玩具。这是显示样式的理论代码(源自基于术语的类似 Scheme 的 lisp)。有许多更简单的方法可以达到相同的最终结果。

练习Higher Order Functions 的简单示例(如mapreduce),熟练掌握它们,然后重新审视这段代码。它仍然很难理解(因为这段代码并不容易理解),但它会更有意义。

【讨论】:

  • 其实很可能是SICP的Scheme代码的Python翻译。 CL 程序员也不这样做。
  • Scheme 保留了 Lisp 的汽车、cdr 和 cons 名称。
  • @Helen cons(3, 4) 返回一个内部保存有34 的函数(称为“闭包”)。如果您将函数视为一个对象(它是),34 在内部存储为对象的属性(不完全是,但有点)。如果想起来有帮助,您可以假设cons(3, 4) 返回的函数看起来像def pair(f): return f(3, 4)。稍后不会“挑选”参数,ab 的值被保存并在以后调用时使用。
  • @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__是保存数据的缓存,可以访问。
  • @Helen 是的car(cons(3, 4)) 返回f(pair),但这等于cons(3, 4)(pair) 等于pair(3, 4)(来自car 的一对!他们将所有这些命名为功能相同,只会让人困惑)返回3
【解决方案3】:

您粘贴的代码是这样使用的:

首先,你定义你的配对:

f = cons(3, 4)

之后,您定义一个适用于对的函数:

add = lambda x, y: x + y

现在,您可以像这样使用“对”:

f(add)

输出:

7

所以,它的作用是:它将您的对转换为一个函数,该函数可以以定义的对作为参数在对上“执行”函数。 carcds 实际上可以“转换”您的配对函数并返回一个元素。

编辑:

如果您不熟悉lambda 表达式,请参阅this tutorial

现在,你也可以去

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

并以同样的方式使用它。 :)

【讨论】:

  • 这并不能真正解释函数内部发生了什么,这就是混乱所在。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-12-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多