从零开始的函数式编程(2) —— Church Boolean 编码
[!quote] 关于λ表达式……
详见λ表达式
本文导出自Obsidian,可能存在格式偏差(例如链接、Callout等)
本文地址:https://www.cnblogs.com/oberon-zjt0806/p/18710283
λ演算与λ代数
上一整节我们利用λ符号体系构建了一套表达式系统,从这里开始,我们将正式开始利用这套系统进行代数应用,在进行演算之前,需要先利用符号体系构建一个代数运算系统。
[!note] 命名终究只是命名
虽然我们之前使用了很多诸如(+ x 1)等等这样的形式,但它们只是我们定义的命名,所以无论是x还是+和1,都只是一个记号而已,尽管我们根据以往的经验为这些符号赋予了某些我们所熟知的含义,但在当前的λ演算语境下,这些东西都还没定义过。
Church 编码
为了使λ演算能够具体应用到计算机和程序上,那么就意味着λ代数系统必须能够表示如下两种东西——
- 数值(逻辑值、整数……)
- 运算(算符、函数、操作……)
也就是说,这些东西要在λ演算中映射为λ表达式(使用表达式来表示)。
[!tip]
粗暴地说,Church编码就是一种把数值和运算编码为λ表达式的过程。
- 但注意!Church编码并非唯一的编码方式,还有其他的编码方式,如Scott编码等。
- Church编码的特点在于以数值表示为起点进行编码,并在基础上构建其他编码。
Church-Boolean 逻辑编码
[!abstract] Church-Boolean 编码汇总
为了方便查阅,这里将本节所有的编码定义列出来,正文是比较冗长的推导过程DEF T = λx.λy.x
DEF F = λx.λy.y
DEF AND = λP.λQ.(P Q P)
DEF OR = λP.λQ.(P P Q)
DEF NOT = λP.λQ.(P F T)
首先我们需要通过Church编码构建出布尔运算系统。之所以先选择布尔代数,是因为布尔代数的结构简单,性质清晰,比较容易构建。
布尔代数(Boolean Algebra)包含的内容非常简单——
- 布尔域\(\mathbb B\)中只包含两个元素\(\mathrm T\)和\(\mathrm F\)
- 支持三种基本运算\(\wedge\) 、\(\vee\)、\(\neg\) 。
- 运算对域封闭,且对于\(\wedge\)和\(\vee\)都在\(\mathbb B\)上分别存在上界和下界
条件选择函数
在介绍Church-Boolean中的真假值前,我们先来考察条件选择函数,所谓条件选择函数就是下面这样的一个三元函数——
x, &c=\mathrm{T} \\
y, &c=\mathrm{F}
\end{cases}
\]
其中\(c\)是条件值,条件选择函数根据\(c\)的值就在\(x\)和\(y\)中做出选择。可以发现,实际上,这个条件选择函数就对应了大多数编程语言中的三元运算符c ? x : y。
我们将这个运算表示为IF-THEN-ELSE形式,可以表示为——
IF c THEN x ELSE y
可以发现这里分为3个子部——
IF c:判断c的条件;THEN x:当c == true被满足时,选择x;ELSE y:上述条件不成立时,选择y;
至此,我们可以把这三个部分抽象为三个λ表达式。
DEF cond = λc.λx.λy.(c x y)
由于真假值承载于c中,因此我们就利用c来对真假值进行编码。
真与假 | True | False
基于上面的想法,我们就能够通过Church编码定义出逻辑的真值T和假值F。讨论c的情况,根据定义,cond函数应当满足——
cond T x y => λc.λx.λy.(c x y) T x y => λx.λy.(T x y) x y => T x y => x
COND F x y => λc.λx.λy.(c x y) T x y => λx.λy.(F x y) x y => F x y => y
观察倒数两步归约,我们发现
- 欲使
T x y => x,那么就要求(T x) y必须发生η归约。- 也就是说
(T x)中约束不生效,可以构建自由表达式(T x) == λb.x- 再脱去对
x的运用,解开约束对,意味着我们需要引入一个新的约束变量 - 于是我们就可以得到
T == λa.λb.a。
- 再脱去对
- 也就是说
- 欲使
F x y => y,那么就要求(F x) y必须发生β归约。- 而且更进一步地,
(F x) == identity == λb.b- 类似地,再脱去对
x的运用,解开约束对,引入另外的约束变量λa - 于是
F == λa.λb.b
- 类似地,再脱去对
- 而且更进一步地,
[!tip] 反归约技巧
我们知道对于运用(f x)进行归约时,会将f中受约束的变量替换为参数x,例如(λa.a x) => x
那么,反过来对于已知的某一表达式x如果想要引入约束,或者把x作为参数提出来,那么就需要引入新的不冲突的约束命名,x => (λy.y x)。
利用这种性质在已知(f x)的情况下可以展开f == λa.(f a)
^9b9507
经过α转换,将a更名为x,b更名为y,我们就可以得出T和F的定义
DEF T = λx.λy.x
DEF F = λx.λy.y
这种定义下的T和F被映射为λ函数,因此可以作为一种条件选择函数来运用。
可以将上述定义代入表达式(c x y)通过[[λ表达式#归约 消解|归约]]来证明这个编码的正确性——
[!warning] 注意
归约化简时,不要忘记变量约束的右结合律和函数运用的左结合律
(T T F) => (λx.λy.x λx.λy.x λx.λy.y)
β|=> (λx.λy.(λx.λy.x) λx.λy.y)
α|=> (λy.(λa.λb.a) λx.λy.y)
η|=> (λa.λb.a)
α|=> (λx.λy.x) => T
(F T F) => (λx.λy.y λx.λy.x λx.λy.y)
η|=> (λy.y λx.λy.y)
β|=> (λx.λy.y) => F // alternatively, ==> identity F => F
逻辑运算 | AND | OR | NOT
接下来要对逻辑运算进行Church编码,这里先给出三种基本逻辑运算的真值表——
| A | B | A AND B | A OR B | NOT A |
|---|---|---|---|---|
| F | F | F | F | T |
| F | T | F | T | T |
| T | F | F | T | F |
| T | T | T | T | F |
XOR、NAND之类的都可以在这三种基本运算的基础上组合出来。所以我们姑且只定义上面三个基本运算即可。
在正式开始之前,我们先考察一个东西——既然T和F都被映射为函数,那么意味着他们可以相互作为函数和参数构成约束对进行运用,那么约束对能否归约,以及归约后的结果是什么,这里给出两个基本函数相互运用的归约结果——
T T => (λx.λy.x) (λx.λy.x) β|=> λy.(λx.λy.x) α|=> λy.(λa.λb.a) => λy.T
T F => (λx.λy.x) (λx.λy.y) β|=> λy.(λx.λy.y) α|=> λy.(λa.λb.b) => λy.F
[i.e.] T P => λy.P
T P Q => P
F T => (λx.λy.y) (λx.λy.x) η|=> λy.y => identity
F F => (λx.λy.y) (λx.λy.y) η|=> λy.y => identity
[i.e.] F P => λy.y => identity
F P Q => Q
^6686a8
需要注意的是,归约结果中的T和F中的x和y和外层约束的y没有任何关系,而是出现了命名冲突(如果要展开那么需要进行一次α转换),所以实际上这里的T是自由表达式。在上面的归约过程中,我们可以归纳出如下性质——
- 如果以
T作为函数运用(T P)(其中P,Q in {T, F}),那么会通过β归约将x替换为P从而得到一个新的函子λy.P,且Q不受y的约束。- 把这个结果
λy.P再作为函数并传入参数Q构成约束对,那么下一步将发生η归约,消去λy约束,最终只会剩下P(T P Q => λy.Q Q => Q)
- 把这个结果
- 如果以
F作为函数,那么由于F的λx并没有进行约束,所以先进行η归约,消去λx约束,最终总会留下λy.y,好巧不巧地,这正好是恒等函数identity- 那接下来就很清晰了,如果再传入参数
Q,由于identity的性质,或者直接通过β归约替换,则会只留下后面的这个参数P(F P Q => identity Q => λy.y Q => Q)
- 那接下来就很清晰了,如果再传入参数
![Church T F.svg]]
完成上面的工作有助于我们通过Church编码来定义逻辑运算。
合取 | 且 | AND
首先来看一下合取运算,合取的要求是只有当两个输入均为T,才可以被归约为T,其他情形全部为F——
AND T Q => Q
AND T => (F P)
AND F Q => F
AND F => (T P)
观察上面的形式,对于AND P Q ,我们可以做出如下归纳
- 当
P==T时,AND P Q => AND T Q => Q- 这种情况对应[[#^6686a8|上面]]的
(F P),于是AND T Q => F P Q
- 这种情况对应[[#^6686a8|上面]]的
- 当
P==F时,AND P Q => AND F Q => F- 这种情况对应[[#^6686a8|上面]]的
(T P),于是AND F Q => T P Q
- 这种情况对应[[#^6686a8|上面]]的
[!question] 麻烦了
目前我们归纳出的结论是AND P == (NOT P) P,然而问题在于我们还没有定义过NOT, 这怎么办呢?
[!tip] 还好
AND满足交换律,也就是说应当有AND P Q == AND Q P
通过交换律将AND P Q换成AND Q P,不影响先前的结论,除了讨论对象此时从AND P变成了AND Q。
AND Q P[P:=T] => Q
AND Q => (T Q)
AND Q P[P:=F] => AND Q F => F
AND Q => (F Q)
AND Q => (P Q)
终于我们可以得出AND的Church编码——
DEF AND = λP.λQ.(P Q P)
析取 | 或 | OR
与合取类似,析取也具有交换律,并且我们也可以效仿刚才的过程完成OR的定义,首先考察
OR T Q => T
OR T => (T P)
OR F Q => Q
OR F => (F P)
这次无需交换律了,直接替换就能够得到OR的定义——
DEF OR = λP.λQ.(P P Q)
反转 | 非 | NOT
NOT比较特别,因为NOT是一个一元运算,需要单独讨论。
NOT T == NOT λx.λy.x => F == λx.λy.y
NOT F == NOT λx.λy.y => T == λx.λy.x
简单来说,输入的参数是选择其中一个,那么NOT的输出总是选择另外一个。考虑到真假值T和F均是通过cond定义的,那么,如果直接反转cond的定义是不是就能够得到相反的输出?
cond == λc.λx.λy.(c x y)
ncond == λc.λx.λy.(c y x)
于是我们得到了一种NOT的定义形式
DEF NOT1 = λP.λx.λy.(P y x)
这个形式看起来比较底层,我们能不能利用已有的逻辑值来定义呢?
再次考察 cond P ——
cond P => λP.λx.λy.(P x y) P => λx.λy.(P x y)
如果考虑将x替换为F,y替换为T,也能达成同样的效果,于是我们进一步提供参数——
cond P F T => λx.λy.(P x y) F T => P F T
于是我们得到了另一种NOT的定义——
DEF NOT2 = λP.(P F T)
通过归约可证明,NOT1 <=> NOT2
至此,两个逻辑值和三个基本逻辑运算被定义完毕,Church-Boolean编码完成,可以使用λ表达式进行逻辑演算了。
[!question] 思考
不妨试试用类似的方式定义出更多的逻辑运算,例如异或XOR、与非NAND等……
从零开始的函数式编程(2) —— Church Boolean 编码的更多相关文章
- s14 第4天 关于python3.0编码 函数式编程 装饰器 列表生成式 生成器 内置方法
python3 编码默认为unicode,unicode和utf-8都是默认支持中文的. 如果要python3的编码改为utf-8,则或者在一开始就声明全局使用utf-8 #_*_coding:utf ...
- python函数式编程,性能,测试,编码规范
这篇文章主要是对我收集的一些文章的摘要.因为已经有很多比我有才华的人写出了大量关于如何成为优秀Python程序员的好文章. 我的总结主要集中在四个基本题目上:函数式编程,性能,测试,编码规范.如果一个 ...
- Python学习笔记八:文件操作(续),文件编码与解码,函数,递归,函数式编程介绍,高阶函数
文件操作(续) 获得文件句柄位置,f.tell(),从0开始,按字符数计数 f.read(5),读取5个字符 返回文件句柄到某位置,f.seek(0) 文件在编辑过程中改变编码,f.detech() ...
- Python3基础(3)集合、文件操作、字符转编码、函数、全局/局部变量、递归、函数式编程、高阶函数
---------------个人学习笔记--------------- ----------------本文作者吴疆-------------- ------点击此处链接至博客园原文------ 1 ...
- day03 set集合,文件操作,字符编码以及函数式编程
嗯哼,第三天了 我们来get 下新技能,集合,个人认为集合就是用来list 比较的,就是把list 转换为set 然后做一些列表的比较啊求差值啊什么的. 先看怎么生成集合: list_s = [1,3 ...
- python - 技术提高要点之一,函数式编程,性能,测试和编码规范
摘自:http://www.cnblogs.com/kaituorensheng/p/4516983.html 函数式编程 命令式的编程风格已经成为事实上的标准.命令式编程的程序是由一些描述状态转变的 ...
- Java8内置的函数式编程接口应用场景和方式
首先,我们先定义一个函数式编程接口 @FunctionalInterface public interface BooleanFunctionalInterface<T> { boolea ...
- JDK 7中的函数式编程思想[转载]
原文作者的观点是Lambda表达式一定会包含在JDK 7中,而全文也着重介绍了这方面的知识,作者认为函数式编程的概念也将出现在JDK 7中. Lambda表达式 Lambda表达式并不是什么新概念,自 ...
- 函数式编程 -> Lambda
一.函数式编程 函数式编程,同面向对象编程.指令式编程一样,是一种软件编程范式,在多种编程语言中都有应用.百科词条中有很学术化的解释,但理解起来并不容易.不过,我们可以借助于数学中函数的概念,来理解函 ...
- Java8函数式编程以及Lambda表达式
第一章 认识Java8以及函数式编程 尽管距离Java8发布已经过去7.8年的时间,但时至今日仍然有许多公司.项目停留在Java7甚至更早的版本.即使已经开始使用Java8的项目,大多数程序员也仍然采 ...
随机推荐
- 【docker-compose】ElasticSearch安装教程
仅供学习参考 ,请勿轻易在生产环境使用 0. 目录树 1. 创建目录 mkdir -p /docker/elasticsearch/conf /docker/elasticsearch/data /d ...
- elastic 7.15 集群搭建
准备三台ES 7.15 关于系统配可以参考之前的文章. https://www.cnblogs.com/yg_zhang/p/10214196.html 这里写一下 的集群配置.这里和之前配置有所不同 ...
- uniapp 消息推送
1.前言 作为一个非原生App的开发者,对于手机系统的推送机制了解是是非有限的,只有了解清楚这些机制,后期的开发才会少踩很多坑,本文将对推送机制逻辑进行一个简单的梳理与记录 2.推送流程 推送流程1. ...
- HUAWEI SECURITY 2023 山东大学专场 WP
Crypto by Smera1d0 1.ezrsa 题干如下: from Crypto.Util.number import getPrime from secret import flag p = ...
- Taro微信小程序获取Tab页可视区域高度
前情 公司有自己的小程序项目,因公司主要技术栈为react,所以选择了Taro来开发,Taro是京东出品的多端统一开发解决方案,用来开发小程序也相比用原生开发,在开发体验上好很多,而且还能使用成熟的R ...
- 虚拟机 ubuntu18 树莓派4 QT5.14.2 交叉编译
编译过程主要参考了 <为树莓派4交叉编译QT5.14.2(带EGLFS支持)>,可以按照教程一步一步进行,在整个过程中,有2个地方需要注意. 1. sudo rpi-update 因为网络 ...
- QT日志类SimpleQtLogger的简单记录
在现代软件开发中,日志记录是必不可少的部分.它不仅帮助开发者在调试和维护软件时了解程序的运行状态,还能提供关键的错误信息.对于使用Qt框架开发应用程序的开发者来说,选择一个合适的日志库至关重要.本文将 ...
- 从底层源码深入分析Spring的IoC容器初始化过程
IOC容器的初始化整体过程 Spring是如何实现将资源配置(以xml配置为例)通过加载,解析,生成BeanDefination并注册到IoC容器中的?这主要会经过以下 4 步: 从XML中读取配置文 ...
- Centos中keytool不起作用的解决方法
keytool是Java开发中用于管理密钥和证书的工具,可以用于生成密钥.创建证书请求.导入和导出证书等操作.你可以在Oracle官网上下载和安装JDK,然后在JDK的 bin目录下找到 keyto ...
- Consul health check pass by Spring security filter
https://stackoverflow.com/questions/35079930/consul-health-check-pass-by-spring-security-filter By d ...