转载自品略图书馆 http://www.pinlue.com/article/2020/07/1115/5911013668815.html

逻辑式编程语言极简实现(使用C#):逻辑式编程语言介绍

讲NMiniKanren的运行原理。老明敲了敲白板,开始涂画代码,我们从一个喜闻乐见的例子开始。

KRunner.PrintResult(KRunner.Run(null, (k, q) =>{    var x = k.Fresh();    var y = k.Fresh();    return k.All(        k.Any(k.Eq(x, 1), k.Eq(x, 2)),        k.Any(k.Eq(y, x), k.Eq(y, "b")),        k.Eq(q, k.List(x, y)));}));

这题我会了!小皮在例子下边写下答案:

[(1 1), (1 b), (2 2), (2 b)]

看到小皮没把昨天的知识忘光,老明略感欣慰:不错。你这个答案是怎么算出来的呢?

呃……就是那个……小皮忽然卡壳了。这种问题就好比几何证明题,明明一眼就能看出来的两条垂直线,真下手证明却发现还挺不容易。小皮抓了几把头发,总算理出一缕思绪:大概就是找出所有条件可能的组合……然后算一下解……小皮一边说,一边在白板上写着:

嗯,其实你已经知道怎么算出答案来了。只是对于其中的细节还不甚明了。我们接下来要做的事要理清楚这个计算过程,得到一个每一步都可以由计算机明确执行的算法。

这个算法其实就是你所说这样,找出所有可能的条件组合。每组条件组合可以求出一个解,也可能自相矛盾从而无解。由于NMiniKanren中的条件都是相等条件,所以一组条件组合可以看作一个替换(Substitution)。一个替换能产生一个解,或者无解。

因此,只需解决下面两个问题:

  • 要在什么数据结构上按照什么顺序遍历替换。

  • 如何从替换中算出一个解,或者判断其无解。

遍历分支

首先,我们要从代码构造出一个数据结构(其实就是一张图)。这个数据结构能够按照一定的顺序进行遍历,并依次生成替换。

例子中的代码使用到了Eq、Any和All这三种构造目标的方法。下面分别探讨怎样从这三种方法构造出我们需要的数据结构来。

Eq

k.Eq(a, b)构造的目标是什么意思呢?老明以一个看似平凡的问题开头。

简单,意思就是a要等于b这个条件。

孤立地看,是这样。但是考虑到上下文,更精确地说应该是,在上下文的基础上追加a等于b这个条件。

小皮有点不解:emm……多了‘追加’有什么不同呢?

从文字上看,多了‘追加’后,目标的解释从一种名词(一组条件)变成了动词(追加条件)。这样一来,目标不仅表达了一组条件,同时也表达了这些条件如何跟上下文结合。就Eq的情况来说,这个结合方式是‘追加’。而Any和All会有其他结合方式。

虽然还不是很明白,我想这个要等Any和All的情况一起对比才能清晰起来。我还另外有个问题,上下文指的是什么?

狭义地说,上下文是解释器运行到这一条代码时,已执行的代码生成的替换。

上下文 <-> 一个替换 <-> 一组条件

广义上看,上下文还应该包含回溯分支等控制信息,不过目前我们先忽略这些。

综合起来,按照对Eq目标的解释,我们可以用下图来表示这个目标。

Any(或)

接着看Any。按照上面的讨论,我们要怎么解释Any目标呢?老明继续发问。

解释目标要说清楚两个方面:名词(什么条件)和动词(如何与上下文结合)。以一开始的例子中的k.Any(k.Eq(x, 1), k.Eq(x, 2))为例。名词方面自然就是x等于1和x等于2两个条件了,不过这两个条件是‘或’的关系。动词方面,应该是从上下文分岔出两个分支,一个分支追加x等于1这个条件,另一个分支追加x等于2这个条件。

很好。也就是说,和Eq不同,Any操作和上下文结合后,会生成多个替换。老明赞许地点点头,它把参数的分支都放在一起,就像加法似的。用图表示的话,就像下面这样。

All(与)

最后是All……

这个我也会了!小皮打断老明,k.All(a, b)名词上表示条件a且条件b;动词上表示上下文先追加a,再追加b。

你说的太笼统了。a和b可能都有多个分支,这种情况下怎么做?老明接着问道。

小皮想了想一开始做的例子,答道:这种情况要取所有组合,也就是a的分支和b的分支两两组合!最后分支数量等于a分支数量乘以b分支数量。

很好。如果Any类比加法,那么All类比的是乘法。下面这图描述了开头例子中的All方法的结合过程。

这是个有向图,每条边表示一次追加条件的过程。每条从开始节点(上下文)到结尾的路径,上面的节点组合起来就是一个替换。遍历所有路径,我们就遍历了所有替换。而遍历的顺序,就是解释器输出结果的顺序。

Anyi

接下来我们还可以来看看Anyi。

普通的Any使用的普通的树结构遍历顺序:

而Anyi以交替的顺序遍历分支:

Alli类似采用交替的顺序遍历,这里就不再画了(主要是不好画,懒)。

再看目标(Goal)

上一篇主要从构造目标的角度出发,介绍了不同方式构造出来的目标。

为了实现NMiniKanren的解释器,我们需要更加深入地了解在解释器的实现中,Goal是什么类型。

在前面的讨论中,我们知道,目标的含义是对上下文/一个替换按照某种方式追加一些条件,返回零个、一个或多个替换——Eq返回一个;Any和All可能返回多个;另外前面没讨论到的Fail会返回零个。

从这个描述不难看出,最方便表述目标类型的是一个单参数函数,其参数是一个替换,返回值是替换的枚举,相当于C#中的Enumerable<替换>,也可以说是一个替换的流(Stream)。

Goal: (替换) -> Stream<替换>

Goal(替换)这个函数调用的含义是把Goal包含的条件,追加到替换上,返回一系列(因为可能有分支,就会变成多个)的替换。

为什么不直接用List呢?小皮又发问了。

因为很多情况下,分支数量会很多,甚至是无穷多,而我们只需要挨个取前面几个结果就够了。这种情况下使用List会极大降低解释器效率,甚至造成死循环。

递归的情况

略。

啥?小皮瞪了下眼。

懒得画,留着思考吧。

替换求解

生成替换后,剩下的就是求解了。

替换求解的方法很简单,就是应用一下小学时学过的代入消元法。来,看看这个怎么解。老明一边说一边写下例题:

(1) y == x(2) q == (x y)(3) x == 1

毕竟是小学难度的题目,小皮看了一眼,马上就有了解法:x等于1是确定的了,把(3)代入(1)后,y也等于1。把(1)和(3)都代入(2),得到q等于(1 1)。

解是求出来了,不过你觉得你这个步骤有通用性吗?老明虚着眼说,计算机能自觉地使用你这个蛇皮顺序吗?

呃……小皮陷入沉思。判断代入顺序的规则似乎还挺麻烦的。或者简单粗暴按照所有顺序都代入一遍?

其实没想象中复杂,按顺序代入一遍,再反过来代入一遍,就OK了。

按顺序代入

把(1)代入(2)(3):

(1) y == x(2) q == (x x)(3) x == 1

把(2)代入(3):

(1) y == x(2) q == (x x)(3) x == 1

在解释器实现中,条件是一条一条追加上来的。可以每次追加条件的时候,将已有的条件代入新条件,这样就把这一步化解到生成替换的过程中了。

加入条件(1) y == x:

(1) y == x

加入条件(2) q == (x y):

(1) y == x(2) q == (x x)

加入条件(3) x == 1:

(1) y == x(2) q == (x x)(3) x == 1

按相反顺序代入

把(3)代入(2)(1):

(1) y == 1(2) q == (1 1)(3) x == 1

把(2)代入(1):

(1) y == 1(2) q == (1 1)(3) x == 1

搞定!

这只是个简单的例子。实际情况还可能会出现无解、自由变量以及死循环等情况。这里就不多赘述了。

再议非运算

现在能看出NMiniKanren为什么不支持‘非’运算了吗?

小皮认真想了一会,说:岂止不支持‘非’,‘大于’和‘小于’这些也不行吧。按照代入消元法,NMiniKanren只支持相等条件。。

那如果要支持这些运算应该怎么做呢?

要拓展条件的类型。除了相等条件,还要有不相等条件等。响应的求解算法也要有所变化。

没错。改动虽然不大,但是代码看起来会混乱得多。所以以教学为目的的话,就不支持这些了。

小结

不知不觉时间已到了喜闻乐见的午餐时间,于是老明总结道:虽然还没有落地成代码,但运行原理算是弄清楚了。关键点就两个:

  • 要在什么数据结构上按照什么顺序遍历替换。

  • 如何从替换中算出一个解,或者判断其无解。

第一点,我们从代码构造了一张图。该图的每条路径对应一个替换,遍历路径的顺序就是遍历替换的顺序。同时也明确了目标Goal的类型。

第二点,我们使用代入消元法,来回两遍代入解出了所有未知量。

接下来可以写代码实现NMiniKanren解释器了吧。

C#逻辑式编程语言极简实现:运行原理相关推荐

  1. 逻辑式编程语言极简实现(使用C#) - 1. 逻辑式编程语言介绍

    逻辑式编程语言极简实现(使用C#) - 1. 逻辑式编程语言介绍 相信很多朋友对于逻辑式编程语言,都有一种最熟悉的陌生人的感觉.一方面,平时在书籍.在资讯网站,偶尔能看到一些吹嘘逻辑式编程的话语.但另 ...

  2. Rust 编程语言极简教程 --- 实例学习

    Rust 编程语言极简教程 --- 实例学习 安装 $ curl https://sh.rustup.rs -sSf | sh info: downloading installerWelcome t ...

  3. 什么叫做罗列式_极简罗列法怎么写作文

    极简罗列法怎么写作文以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧! 1. 作文罗列式怎么开头 列举式 案例一:" ...

  4. Colima:MacOS 上的极简容器运行时和 Kubernetes

    作者 | Addo Zhang 来源 | 云原生指北 Colima 是一个以最小化设置来在MacOS上运行容器运行时和 Kubernetes 的工具.支持 m1,同样也支持 Linux. Colima ...

  5. 意式极简轻奢风格装修

    所谓极简主义,就是追求真实.自由,没有任何繁杂的装潢:而意大利极简主义是进一步的创新探究,是一种追求时髦.崇尚自由与特性的生活方法,"少即是多"便是意式极简风格的精华.意式极简家具 ...

  6. 函数式编程语言python-函数式编程语言

    最近一段时间总是听到或者看到有人谈论"函数式编程",第一次接触是在大概半年前的一次沙龙中,当时听人讲的时候,心想这有什么难理解的,函数式编程,函数嘛,那就是C呀,C++放在首位的是 ...

  7. 【一天一门编程语言】Go 语言程序设计极简教程

    文章目录 Go 语言程序设计极简教程 Go 语言程序设计极简教程 第一章:Go 语言基础 1.1 Go 语言简介 1.2 Go 语言安装 1.2.1 下载安装包 1.2.2 安装 Go 语言 1.3 ...

  8. 业务逻辑组件化android,AppJoint 极简 Android 组件化方案

    AppJoint 极简 Android 组件化方案.仅包含 3 个注解加 1 个 API,超低学习成本,支持渐进式组件化. 开始接入 在项目根目录的 build.gradle 文件中添加 AppJoi ...

  9. 极简风格的响应式简历模板

    Crisp Minimal Résumé github.com/crispgm/res- 简介 极简风格的响应式简历模板,基于 Jekyll,可以直接部署在 GitHub Pages 上. 通过配置 ...

最新文章

  1. Android中常见的MVC模式
  2. 第一次冲刺-个人工作总结01
  3. feign一个接口多个方法_spring cloud 建一个服务消费者client-feign(最好用这种方式)...
  4. Linux文件查找命令具体解释-which whereis find locate
  5. SSIS数据转换组件_复制转换、数据转换、行计数
  6. 使用VM Tools让VMware虚拟机里的ubuntu能够共享Windows系统的文件夹
  7. mysql sohu_【MySQL中间件之SOHU-DBProxy】
  8. 聚焦效率与目标差距,数据才是远程办公的内核!
  9. dos输入java Hello,出现错误: 找不到或无法加载主类 Hello
  10. “哎哟,真的很快哦” 闪送宣布签约周杰伦为其品牌代言人
  11. 微信中html5获取手机号,微信小程序通过用户授权获取手机号
  12. 鸽主姓名查询成绩_SQL学习之旅-Select简单查询
  13. functools.partial
  14. css中的伪元素:before :after与jQuery中的.before .after的区别
  15. ftp和http转参数的使用(转)
  16. 麻将算法(二)牌型转换以及接牌
  17. 容联云通讯发送短信java实现
  18. python实现DBSCAN聚类
  19. model java_编程中的 Model 到底是什么?
  20. 修改SAPGUI的默认文件保存/下载路径 - SAP S/4 Basis Tips

热门文章

  1. 面向忙碌开发者的 Android
  2. 职教大赛正热华三通信实训室方案为辽宁省赛“加小灶”
  3. 软件开发众包 个人接单_如何将研究与开发众包
  4. 第一部分 基本概念 - 第 1 章
  5. 非常简单的一个块设备驱动,simp_blkdev,2.6.31之后的变化
  6. arima模型预测 matlab,ARIMA模型预测问题
  7. 【NOIP模拟赛】逛公园
  8. Android使用Google定位服务定位并将经纬度转换为详细地址信息(国省市县街道)
  9. java调用office_java 调用word
  10. C# 从FTP上下载指定文件到本机