关于python中Y组合子的问题讨论
关于python中Y组合子的问题讨论
by Wenze Jin
在 The Structure and Interpretation of Computer Programs 这门课的hw03-Recursion中有如下的 JUST FOR FUN拓展问题,值得研究。
文章目录
- 关于python中Y组合子的问题讨论
- [Problem 7: Anonymous factorial](https://sicp.pascal-lab.net/2022/homework/hw03/3_1.html#problem-7-anonymous-factorial-0pts)
- 问题分析
- 一般实现
- lambda表达式
- lambda表达式的实现
- 但是!!!
- 最终解答
- [Problem 8: All-Ys Has Been](https://sicp.pascal-lab.net/2022/homework/hw03/3_2.html#problem-8-all-ys-has-been-0pts)
- 问题分析
- Y组合子语义分析
- Y组合子功能实现
- 1. 实现第n个Fibonacci数的计算
- 2. 实现统计一个数字中有几个六
- 最终解答
- 我们从Y组合子中获得了什么
Problem 7: Anonymous factorial
>>> fact = lambda n: 1 if n == 1 else mul(n, fact(sub(n, 1)))
>>> fact(5)
120
The ternary operator
<a> if <bool-exp> else <b>
evaluates to<a>
if<bool-exp>
is truthy and evaluates to<b>
if<bool-exp>
is false-y.
However, this implementation relies on the fact (no pun intended) that fact
has a name, to which we refer in the body of fact
. To write a recursive function, we have always given it a name using a def
or assignment statement so that we can refer to the function within its own body. In this question, your job is to define fact recursively without giving it a name!
Write an expression that computes n
factorial using only call expressions, conditional expressions, and lambda expressions (no assignment or def statements). Note in particular that you are not allowed to use make_anonymous_factorial
in your return expression. The sub
and mul
functions from the operator
module are the only built-in functions required to solve this problem:
from operator import sub, muldef make_anonymous_factorial():"""Return the value of an expression that computes factorial.>>> make_anonymous_factorial()(5)120>>> from construct_check import check>>> # ban any assignments or recursion>>> check(HW_SOURCE_FILE, 'make_anonymous_factorial', ['Assign', 'AugAssign', 'FunctionDef', 'Recursion'])True"""return 'YOUR_EXPRESSION_HERE'
问题分析
题设要求我们构建一个可以求n
的阶乘的递归函数。
一般实现
在我们的认识中,解决这样的问题通常可以使用这样的递归函数来解决:
def factorialR(n):if n == 1:return 1else:return n * factorialR(n-1)
同时我们又知道:
lambda表达式
lambda x: x + 1
可以创建一个匿名函数,这个函数需要一个参数x,实现功能是返回x + 1
例如
>>> f1 = lambda x: x + 1
>>> f1(2)
3>>> (lambda x: x + 1)(2)
3>>> (lambda f: lambda x: f(x + 1))(f1)(2)
4>>> if_test = lambda x: 1 if x > 0 else 0 if x == 0 else -1
>>> [if_test(10), if_test(0), if_test(-10)]
[1, 0, -1]
lambda表达式的实现
可以如下用一个有名称的lambda表达式简洁的实现这个递归
>>> factlambda = lambda x: 1 if x == 1 else x * factlambda(x - 1)
>>> factlambda(5)
120
但是!!!
题目的要求是让我们输出一个匿名的lambda函数,也就是它不可以有名字,理论上不可以调用自己!同时题目为了防止我们偷鸡,会在代码中检查你有没有赋值,定义函数,调用自身函数以实现递归的功能。
有如下答案:
def make_anonymous_factorial():return (lambda fact: lambda k: fact(fact,k))(lambda factcopy, k: 1 if k == 1 else k * factcopy(factcopy,k-1))
在这个答案中我们可以看到如下流程:
定义一个 lambda 它需要一个参数 fact,它的功能是返回一个需要一个参数k的函数,并且指定fact的参数数目为两个
在右侧的括号中,整个形成一个lambda function,作为左侧括号中的fact参数,这个函数接受两个参数
factcopy
,k
并对k进行判断 实现一个阶乘的功能我们可以将右侧的函数理解为
def f(factcopy, k):if k == 1:return 1else:return k * factcopy(factcopy, k - 1)
这样一个递归的框架,相比较普通的递归,我们发现参数多了一个
factcopy
这是因为,这是一个匿名函数!没有其他人帮你记住你叫什么名字,如果我们想要知道自己叫什么,我们必须写一个小纸条把自己的名字记住!现在还差一步:记自己名字的纸条准备好了,还差搞清楚自己叫什么!
在进行调用的过程中,如果我们为左侧的函数提供一个k,左侧则会调用右侧函数,变为
lambda k: f(f, k)
f 是我们根据上面的理解假设的一个名字,便于理解这个函数的结构,实际上匿名函数是没有名字的
现在,我们就能理解左侧括号内的功能了:
- 告诉右侧函数,你叫自己! 现在你可以用
自己(自己,k)
调用自己了! - 生成一个需要一个参数值
k
的函数
- 告诉右侧函数,你叫自己! 现在你可以用
最终解答
def make_anonymous_factorial():return (lambda fact: lambda k: fact(fact,k))(lambda factcopy, k: 1 if k == 1 else k * factcopy(factcopy,k-1))
于是,这题到这就结束了,引出了下题的重要概念与思想
Problem 8: All-Ys Has Been
Hint: You may use the ternary operator
<a> if <bool-exp> else <b>
, which evaluates to<a>
if<bool-exp>
is truthy and evaluates to<b>
if<bool-exp>
is false-y.
Y = lambda f: (lambda x: x(x))(lambda x: f(lambda z: x(x)(z)))
fib_maker = lambda f: lambda r: 'YOUR_EXPRESSION_HERE'
number_of_six_maker = lambda f: lambda r: 'YOUR_EXPRESSION_HERE'my_fib = Y(fib_maker)
my_number_of_six = Y(number_of_six_maker)# This code sets up doctests for my_fib and my_number_of_six.my_fib.__name__ = 'my_fib'
my_fib.__doc__="""Given n, returns the nth Fibonacci nuimber.>>> my_fib(0)
0
>>> my_fib(1)
1
>>> my_fib(2)
1
>>> my_fib(3)
2
>>> my_fib(4)
3
>>> my_fib(5)
5
"""my_number_of_six.__name__ = 'my_number_of_six'
my_number_of_six.__doc__="""Return the number of 6 in each digit of a positive integer n.>>> my_number_of_six(666)
3
>>> my_number_of_six(123456)
1
"""
问题分析
在上题思想的基础上,本题定义了一个常量Y
其实Y就是我们所说的Y组合子
Y = lambda f: (lambda x: x(x))(lambda x: f(lambda z: x(x)(z)))
改个熟悉的形式
Y = lambda f: (lambda x: x(x))(lambda xcopy: f(lambda z: xcopy(xcopy)(z)))
Y组合子语义分析
Y
指向了一个lambda函数,这个函数的功能是获得一个f
,对其进行如下操作:
观察这个函数的内部,我们可以发现,函数的左侧括号内定义了一个函数,这个函数需要一个输入参数x
- 它的功能是返回一个东西,即将
x
作为x
的参数时生成的东西 - 同时它指定了
x
的参数数目为一个 - 它看起来非常像我们在上题当中说到的告诉右边那个笨笨的函数自己叫做***“自己”***
- 它的功能是返回一个东西,即将
接下来我们看一下右边那个笨笨的函数是什么,可以把它写成这样
def benben(xcopy, f):return f(lambda z: xcopy(xcopy, f)(z))
这个函数需要一个参数
xcopy
(不是刚才那个x) 以及从外面的lambda表达式里拿进来的f- 它的功能是,应用最外侧函数输入的函数
f
,并再次需要一个参数z
- 告诉
benben
自己叫做benben
也就是xcopy = benben
- 指定了最外侧输入的f函数参数为一个函数
- 嵌套应用了很多次f形成了一个对f的递归,你会发现,
benben
返回的应该还是一个函数,因为xcopy(xcopy, f)
后仍需要一个参数z - 这并不难理解,因为f的括号内还是一个
lambda
表达式,他需要一个参数z
- 到现在为止,我们对整个函数的运行方式有了粗略的理解,但仍不清楚具体的实现方式
- 它的功能是,应用最外侧函数输入的函数
Y组合子功能实现
现在,我们需要利用Y组合子的这些特性完成下面两个任务
1. 实现第n个Fibonacci数的计算
题面:
Y = lambda f: (lambda x: x(x))(lambda x: f(lambda z: x(x)(z)))
fib_maker = lambda f: lambda r: 'YOUR_EXPRESSION_HERE'
my_fib = Y(fib_maker)
我们需要完善这个fib_maker
解答
fib_maker = lambda f: lambda r: 0 if r == 0 else 1 if r == 1 else f(r - 1) + f(r - 2)
此时 fib_maker
就是一个函数, 它请求一个方法f 请求一个参数r
通过上面的分析, fib_maker
这个函数会被应用于f
,在benben
函数中,每次都会调用一次fib_maker
,每次调用的结果:
- 它的参数f是一个请求更高阶数参数的
fib_maker
- 它需要一个当前阶数的r参数
至此,我们发现,benben
教会了fib_maker
每次调用自己并请求一个参数哦~
2. 实现统计一个数字中有几个六
题面
Y = lambda f: (lambda x: x(x))(lambda x: f(lambda z: x(x)(z)))
number_of_six_maker = lambda f: lambda r: 'YOUR_EXPRESSION_HERE'
my_number_of_six = Y(number_of_six_maker)
我们需要完善这个my_number_of_six
这个在本次作业先前的Problem1
中有提到原递归式函数为:
def number_of_six(n):"""Return the number of 6 in each digit of a positive integer n.>>> number_of_six(666)3>>> number_of_six(123456)1"""if n == 0:return 0else:if n % 10 == 6:return 1 + number_of_six(n // 10)else:return number_of_six(n // 10)
解答
number_of_six_maker = lambda f: lambda r: 0 if r == 0 else 1 + f(r // 10) if r % 10 == 6 else f(r // 10)
此时number_of_six_maker
是一个函数,它要求一个方法f
,一个参数r
和上面同样的,benben
函数使得number_of_six_maker
:
- 的f是请求一个参数的更高阶数
number_of_six_maker
- 它需要一个当前阶数的参数r
至此,benben
又教会了number_of_six_maker
自己叫什么,自己调用自己与自己吃饭饭哦~
至此,我们也学会了怎么去完成一个匿名递归函数哦***(运用Y组合子)***
最终解答
fib_maker = lambda f: lambda r: 0 if r == 0 else 1 if r == 1 else f(r - 1) + f(r - 2)
number_of_six_maker = lambda f: lambda r: 0 if r == 0 else 1 + f(r // 10) if r % 10 == 6 else f(r // 10)
我们从Y组合子中获得了什么
到此为止,我们通过Y组合子的方式,仅通过λλ演算的概念,实现了「递归」的概念。因此,可以论证出「递归」这个概念可以在函数式编程语言中是「派生」的,而不需要「原生」支持。我们仅仅需要λ演算就能模拟自然数(hw02-Higher-Order-Funtions-JFF)
、运算(hw02-Higher-Order-Funtions-JFF)
、递归这些概念了。
笔者因专业知识有限,如有疏漏,欢迎交流!
Email: wenzejin2004@gmail.com
金文泽 Wenze Jin
Nanjing University
关于python中Y组合子的问题讨论相关推荐
- 【逻辑与计算理论】组合子逻辑与 Y 组合子
为什么是Y? 在前面的几个帖子里,我已经建立了如何把lambda演算变成一个有用的系统的点点滴滴. 我们已经有了数字,布尔值和选择运算符.我们唯一欠缺的是重复. 这个有点棘手.lambda演算使用递归 ...
- 函数式编程中的组合子
函数式编程是一个比较大的话题,里面的知识体系非常的丰富,在这里我并不想讲的特别的详细.为了应对实际中的应用,我们讲一下函数式编程中最为实用的应用方式--组合子.组合子本身是一种高阶函数,他的特点就是将 ...
- python中全组合函数(combinations)与全排列函数(permutations)
最近写代码时遇到排列组合问题,发现python中的itertools库用起来比较方便.itertools库中的permutations函数可以输出可迭代对象的全排列情况,而combinations函数 ...
- python中全组合函数(combinations)与全排列函数(permutations)的介绍与参数说明
概要:在平常的编程过程中,往往需要面对排列组合的应用情况,而每次自己编写相应的函数会耗费较多的时间,而python中的itertools库就为我们解决了这个小问题.itertools库中的permut ...
- python实现嵌套功能_我应该如何在Python中实现“嵌套”子命令?
我觉得argparse中的子解析器有一个小小的限制,如果说,您有一套工具,它们可能有类似的选项,可能分布在不同的级别上.这种情况可能很少见,但如果您正在编写可插入/模块化代码,则可能会发生这种情况. ...
- 郭家宝2012年写的PPT,讲解Y组合子,讲得很好
原文:byvoid.github.io/slides/apio- 转载于:https://juejin.im/post/5c7e7927518825408b658afe
- python有哪些作用-Python中的NumPy是什么?有什么作用?
Python中的NumPy是什么?NumPy或NumericPython是用于科学计算的通用数组处理python软件包.它包含许多强大的功能,其中包括:具有许多有用功能的健壮的多维数组对象.用于将其他 ...
- 【Python基础】Python中的高效迭代库itertools,排列组合随便求
本文目录 一.模块概述 二.组合生成器 2.1 product 2.2 permutations 2.3 combinations 2.4 combinations_with_replacement ...
- python中组合与继承的区别_python类与对象的组合与继承
1.把类的对象实例化放到一个新的类里面叫做类的组合,组合就是指几个横向关系的类放在一起,纵向关系的类放在一起是继承,根据实际应用场景确定.简单的说,组合用于"有一个"的场景中,继承 ...
最新文章
- IOCP , kqueue , epoll ... 有多重要?
- java构造方法的书写和注意事项(入门可看)
- 【Docker学习笔记(三)】Hello world!
- 您如何重命名MongoDB数据库?
- RHEL/CentOS/Fedora各种源
- mysql5.6.24怎么打开_mysql 5.6.24 安装配置方法图文教程
- 大数据集群搭建之节点的网络配置过程(二)
- Lucene3.5自学4--建索引相关知识总结
- 每个努力奋斗过的人,被不公正的际遇砸了满头包的时候,都有那么一瞬间的代入感。出生就是hard模式的人,早已经历了太多的劳其筋骨饿其体肤,再多的人为考验只会摧毁人对美好的向往。...
- 【UVM芯片漫游指南_000】总目录(下)——UVM目录
- 搭建直播系统并实现h5播放rtmp
- 最近做的一些文件破解的结果
- 使用nginx配置一级域名和多个二级域名
- lwj_C#_方法重载,递归,构造
- 新建网站常用的50个网站推广方法
- python超市进销存管理系统
- 服务链路追踪类型和区别
- [cocos2d-iphone]ios6截图问题
- 360打开html乱码怎么办,360浏览器出现乱码怎么回事_360浏览器页面乱码如何解决-win7之家...
- 服务器系统常用快捷键,电脑常用快捷键有哪些 电脑常用快捷键介绍