本篇教程由作者设定使用 CC BY-NC-SA 协议。

这篇教程会讲解运算符的一些高级特性与用法,最主要的是自定义运算符,字较多,阅读本教程前建议先看看基础篇

 

什么是运算符常量?

在基础篇教程中谈论过常量与变量的类型,其中有个特殊的类型为“运算符”,它主要是作为某些运算符的输入参数,或者用于配合Apply(执行)运算符来运行,运行结果和直接使用运算符一样。另外,运算符常量是柯里化后的运算符,其具体含义会在后面介绍,它的作用之一是用于实现自定义运算符。

 

获取运算符常量:

首先在逻辑编程台中选择“运算符”标签:

逻辑编程教程(高级篇)-第1张图片

然后在输入框输入运算符的名字,例如获取一个“加”运算符常量,输入“jia”后,选择“加运算符”,放入变量卡再取出,即获得了一个值为“加运算符”的运算符常量:

逻辑编程教程(高级篇)-第2张图片

逻辑编程教程(高级篇)-第3张图片


使用Apply运算符来运行运算符常量:

逻辑编程台中搜索“apply”(也可以搜索zhixing),会出现3个apply运算符:

逻辑编程教程(高级篇)-第4张图片

这3个apply运算符的区别就是接受输入参数的个数不同,apply接受1个输入参数,apply2接受2个输入参数,apply3接受3个输入参数,需要注意的是这里说的输入参数指的是被运行的运算符的输入参数而不是apply运算符的输入参数;

 

假设我们要运行刚刚获取到的“加运算符”的运算符常量,这个运算符需要2个输入参数,所以我们要选择Apply2运算符来执行:

逻辑编程教程(高级篇)-第5张图片

左边放置加运算符变量卡,右边两个框放入参数变量卡,值分别为1、2,右下角放入空变量卡后再取出,即获得表示这个加法运算结果的变量:

逻辑编程教程(高级篇)-第6张图片

逻辑编程教程(高级篇)-第7张图片

将这个变量卡放入显示器,其余变量卡放入变量卡箱,就能观察到加法运算的结果了:

逻辑编程教程(高级篇)-第8张图片



什么是柯里化?

在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。

 

集成动力的柯里化与上述的稍微不同,如果一个运算符被柯里化后,接受的参数个数少于所需的参数个数时,会返回一个新的运算符,这个新运算符将接受剩余的参数,如果运算符所有参数都接受完毕,则返回真正的运算结果。

 

为了解释柯里化这个概念,继续以“加”运算符为例,在逻辑编程台中选择“运算符”标签并搜索“jia”,鼠标移动到“加运算符”的位置:

逻辑编程教程(高级篇)-第9张图片

这个XXX -> XXX -> XXX的意思是:

  • 通常用来表示柯里化后的运算符(或函数)的参数类型与返回值类型;

  • 最后一个XXX是这个运算符的返回值的类型,其他的XXX是输入参数的类型;

  • 图中可知 “加运算符”具有两个Number类型的输入参数,一个Number类型的返回值。

 

前面已经讲过,运算符常量的值都是柯里化后的运算符,继续沿用上面的“加运算符”,先将运算符变量卡放入显示器,观察其结果:

逻辑编程教程(高级篇)-第10张图片

第一行为运算符的名称,其他行表示它的输入参数类型与返回值类型,现在这个运算符有两个参数待输入,类型都是Number(数值)。

通过Apply运算符,传入一个参数(值为1)并运行后的结果:

逻辑编程教程(高级篇)-第11张图片

可以看到返回的值依然是一个运算符,而运算符名称后面多了个[integer;],表示已经传入了一个整型参数,现在这个运算符仍需要一个参数来完成加法运算。

继续使用Apply运算符,传入参数(值为2)并运行:

逻辑编程教程(高级篇)-第12张图片

最终,通过使用两次Apply运算符后获得了真正的运算结果。


自定义运算符:

自定义运算符指的是将内置的运算符以某种方式组合起来形成的新运算符,用于实现一些自定义的功能,例如输入一个整数,判断是否大于0,或是获取箱子内所有数量大于64的物品。

 

为什么要自定义运算符?

一般来说只有在使用某些运算符的情况下才会自定义运算符,例如Map(遍历映射)、filter(过滤)、contains_p(包含)、count_p(计数)、uniq_p(条件唯一)这些运算符,它们都有一个运算符类型的参数而且必须是特定类型的运算符,这种情况下需要自定义一个符合条件的运算符。


假如要筛选出一个物品列表中堆叠数大于10的物品,就要使用filter(过滤)运算符对一个物品列表进行过滤:

逻辑编程教程(高级篇)-第13张图片

仔细研究一下filter运算符,需要两个输入参数,第一个参数是一个Predicate(断言,也被翻译成谓词)——一个特殊的运算符,有一个任意类型的输入参数,一个Boolean(布尔)类型的返回值;第二个参数是个列表。filter运算符会依次将列表里每一个元素作为参数传入给定的运算符,如果这个运算符返回true(真),则将元素加入一个新列表,最终所有符合条件的元素被筛选出来,将这个新列表返回。

 

那么过滤物品的思路大概是:将“判断物品堆叠数是否大于10”的运算符、从物品容器读取器得到的物品列表作为参数传入filter运算符。问题是你会发现逻辑编程器中根本找不到“判断物品堆叠数是否大于10”的运算符,只能自己定义一个这样的运算符。

 

如何自定义运算符?

继续沿用上面的例子,我们要定义一个“判断物品堆叠数是否大于10”的运算符,根据内置的运算符我们可能会想到用“数量”运算符和“大于”运算符来完成这个功能,如果仅为了完成“判断某物品堆叠数是否大于10”这个功能,用这两个运算符就够了,但是将这个功能做成一个运算符就会复杂很多:

逻辑编程教程(高级篇)-第14张图片

上图每个方框都表示一个变量卡,变量卡的之间的箭头连线表示引用关系(对于每个变量卡,箭头朝外表示作为输入参数,箭头朝内表示运算符接受参数,参数接受顺序为由上到下),带括号的方框表示使用运算符获得变量,不带括号的都为常量,带有“运算符:”前缀的是运算符常量。

 

上面流程图就是定义这个运算符的完整过程,过程的大致描述(对应图中序号):

①首先要获取物品的堆叠数,再将这个数作为“数量是否大于10”的运算符的参数,最后得到物品堆叠数是否大于10;

②接着就是实现“数量是否大于10”的运算符,这里用了“小于”运算符,是因为原来的逻辑是“某数大于10”,但这里要先给运算符传入10,所以逻辑变成了“10小于某数”;根据柯里化的特性,“小于”运算符通过Apply(执行)运算符传入一个参数后返回的依然是一个运算符;

③最后要将物品堆叠数作为参数传入“数量是否大于10”运算符,这里要借助“管道”运算符,管道运算符的作用是执行第一个运算符并把返回值作为参数,传入第二个运算符并执行,“管道”运算符返回的值依然是个运算符;将获取物品堆叠数运算符、“数量是否大于10”运算符依次作为“管道”运算符的参数,最终获得了“判断物品堆叠数是否大于10”的运算符。

 

运算符的设计思路:

  • 设计之前先确定运算符的输入参数类型、返回值类型

  • 第一次设计的时候可以画个图,理清每个变量卡的逻辑关系,熟练之后可以不画

 

 

一些自定义运算符需要用到的内置运算符:

其作用都是将一个或多个运算符组合成新的运算符

谓词:具有一个任意类型的输入参数、一个Boolean(布尔)类型的返回值的运算符

运算符名称

参数个数(运算符)

作用

.&&.(谓词连接)

2

将两个谓词的返回值做“与”操作

.||.(谓词分离)

2

将两个谓词的返回值做“或”操作

!.(反转)

1

谓词的返回值取反,即“非”操作

.(管道)

2

创建一个新的运算符:先执行第一个运算符,将返回值作为第二个运算符的第一个参数并执行。

.2(管道二元)

3

创建一个新的运算符:先执行第一个运算符,再执行第二个运算符,将二者返回值分别作为第三个运算符的前两个参数并执行。

Flip(翻转)

1

翻转运算符前两个输入参数的传入顺序。

每个运算符的图形描述:

(最外层的框表示新运算符,箭头朝内表示输入参数,箭头朝外表示返回值,虚线为可选参数)

逻辑编程教程(高级篇)-第15张图片



写在后面:学习逻辑编程最简单的方式是实践,官方手册中大部分运算符的描述都很含糊,可能只有学过编程的有一定编程经验的才能直接看懂吧,不懂编程也没啥关系,可以自己多测试几下,试多几次就懂了,当然有兴趣的话可以学习一下函数式编程,毕竟这是这mod的核心。