def cons(a, b):
def pair(f):
return f(a, b)
return pair
def car(f):
def left(a, b):
return a
return f(left)
def cdr(f):
def right(a, b):
return b
return f(right)
在git上找到了这个python代码。
只想知道cons定义中的f(a,b)
是什么,它是如何工作的?
(我猜不是一个功能)
答案 0 :(得分:3)
cons
是一个带有两个参数的函数,并返回一个带有另一个将使用这两个参数的函数的函数。
例如,考虑以下功能:
def add(a, b):
return a + b
这只是一个将两个输入相加的函数,例如add(2, 5) == 7
由于此函数有两个参数,因此我们可以使用cons
来调用此函数:
func_caller = cons(2, 5) # cons receives two arguments and returns a function, which we call func_caller
result = func_caller(add) # func_caller receives a function, that will process these two arguments
print(result) # result is the actual result of doing add(2, 5), i.e. 7
此技术对于包装函数和在调用适当函数的之前和之后中执行内容很有用。
例如,我们可以修改cons
函数,以在调用add
之前和之后实际打印值:
def add(a, b):
print('Adding {} and {}'.format(a, b))
return a + b
def cons(a, b):
print('Received arguments {} and {}'.format(a, b))
def pair(f):
print('Calling {} with {} and {}'.format(f, a, b))
result = f(a, b)
print('Got {}'.format(result))
return result
return pair
通过此更新,我们得到以下输出:
func_caller = cons(2, 5)
# prints "Received arguments 2 and 5" from inside cons
result = func_caller(add)
# prints "Calling add with 2 and 5" from inside pair
# prints "Adding 2 and 5" from inside add
# prints "Got 7" from inside pair
答案 1 :(得分:3)
除非您知道cons
,car
和cdr
的含义,否则这对您毫无意义。
在Lisp中,列表以链接列表的非常简单的形式存储。列表可以是nil
(例如None
)以表示一个空列表,也可以是一对值和另一个列表。 cons
函数获取一个值和一个列表,然后仅需配对即可返回另一个列表:
def cons(head, rest):
return (head, rest)
还有car
和cdr
函数(它们代表“地址|数据寄存器的内容”,因为它们是用于在特定1950年代的计算机上实现它们的汇编语言指令,但这不是不太有用)返回一对中的第一个或第二个值:
def car(lst):
return lst[0]
def cdr(lst):
return lst[1]
因此,您可以列出一个列表:
lst = cons(1, cons(2, cons(3, None)))
...,您可以从中获取第二个值:
print(car(cdr(lst))
…,您甚至可以编写函数来获取第n个值:
def nth(lst, n):
if n == 0:
return car(lst)
return nth(cdr(lst), n-1)
…或打印出整个列表:
def printlist(lst):
if lst:
print(car(lst), end=' ')
printlist(cdr(lst))
如果您了解它们的工作原理,那么下一步就是尝试使用发现的那些奇怪的定义。
他们仍然做同样的事情。所以,问题是:如何?更大的问题是:有什么意义?
好吧,使用这些奇怪的功能没有实用的意义。真正的要点是向您展示计算机科学中的所有内容都可以只用函数编写,而不能像元组(甚至整数)那样使用内置数据结构编写。
键是高阶函数:将函数作为值和/或返回其他函数的函数。您实际上一直在使用这些内容:map
,sort
和key
,修饰符,partial
…它们只是在它们非常简单时才令人困惑:
def car(f):
def left(a, b):
return a
return f(left)
这需要一个函数,并在返回其两个参数中第一个参数的函数上调用它。
与cdr
类似。
在看到cons
之前,很难看清如何使用这两种方法:
def cons(a, b):
def pair(f):
return f(a, b)
return pair
这需要两件事,并返回一个函数,该函数需要另一个函数并将其应用于这两件事。
那么,我们从cons(3, None)
得到什么呢?我们得到一个带有函数的函数,并将其应用于参数3
和None
:
def pair3(f):
return f(3, None)
如果我们打电话给cons(2, cons(3, None))
?
def pair23(f):
return f(2, pair3)
如果在该函数上调用car
会发生什么?跟踪它:
def left(a, b):
return a
return pair23(left)
pair23(left)
会这样做:
return left(2, pair3)
left
很简单:
return 2
因此,我们得到了(2, cons(3, None))
的第一个元素。
如果您致电cdr
,怎么办?
def right(a, b):
return a
return pair23(right)
pair23(right)
会这样做:
return right(2, pair3)
…和right
非常简单,因此只返回pair3
。
您可以得出结论,如果我们调用car(cdr(pair23))
,我们将把其中的3
排除掉。
现在您可以编写lst = cons(1, cons(2, cons(3, None)))
,编写上面的递归nth
和printlist
函数,并跟踪它们在lst
上的工作方式。
我在上面提到过,您甚至可以摆脱整数。你是怎样做的?了解有关Church numerals的信息。您定义zero
和successor
函数。然后,可以将one
定义为successor(zero)
,将two
定义为successor(one)
。您甚至可以递归定义{{1}},以使add
为add(x, zero)
,而x
为add(x, successor(y))
,然后继续定义successor(add(x, y))
,以此类推。< / p>
您还需要一个特殊的函数,可以用作mul
的值。
无论如何,使用上面的所有其他定义完成此操作后,您可以执行nil
,而lst = cons(zero(cons(one, cons(two, cons(three, nil))))
会给您nth(lst, two)
的回馈。 (当然,写one
会比较棘手……)
显然,这将比仅使用元组和整数等慢很多。但从理论上讲,这很有趣。
考虑一下:我们可以写一个Python的小方言,它只有三种语句-printlist
,def
和表达式语句-仅有三种表达式-文字,标识符和函数调用-它可以完成普通Python所做的一切。 (实际上,仅通过具有Python已有的函数定义表达式就可以完全摆脱语句。)那种小语言很难使用,但是编写一个程序来推理程序会容易得多用那种小语言。而且我们甚至都知道如何使用元组,循环等将代码转换为这种微小的子集语言的代码,这意味着我们可以编写一个程序来编写真正的Python代码。
实际上,有了更多技巧(咖喱函数和/或静态函数类型以及惰性求值),编译器/解释器可以即时进行这种推理并为我们优化代码。很容易以编程方式告诉您return
将返回3,而不必实际评估大多数这些函数调用,因此我们可以跳过对它们的评估,而将car(cdr(cons(2, cons(3, None))
替换为整个表达式。
当然,如果有任何功能会产生副作用,这种情况就会崩溃。显然,您不能仅用3
代替None
并获得相同的结果。因此,您需要一些巧妙的技巧,其中由一些魔术对象处理IO,该魔术对象评估函数以确定应该读取和写入的内容,然后整个程序的其余部分,即用户编写的部分,将变得纯净并可以优化随你怎么便。再加上几个抽象,我们甚至可以使IO变得不必要地神奇。
然后,您可以构建一个标准库,该库可以将定义和调用函数方面的所有工作全部归还给您,因此它实际上是可用的,但实际上它只是减少了纯函数调用,因此简单到足以让计算机进行优化。然后,您基本上已经编写了Haskell。