首页 > 其他分享 >一种基于Common Lisp的用lambda从头构建逻辑、整数、算数、斐波拉契的方案

一种基于Common Lisp的用lambda从头构建逻辑、整数、算数、斐波拉契的方案

时间:2023-08-22 09:56:39浏览次数:41  
标签:lazy num1 num2 Lisp defun Common opt1 lambda

绪论

本文参考视频教程,教程给出了一系列编程题目。该教程作者裘香莲已经基于JavaScript,用lambda运算从头构建了一系列编程基础概念。本文则对其编程题以Common Lisp语言另外给出答案。

逻辑与判断的实现

代码展示

(defparameter *t*
  (lambda (opt1 opt2)
    `,opt2 (funcall opt1)))
(defparameter *f*
  (lambda (opt1 opt2)
    `,opt1 (funcall opt2)))

;(defun *if* (cnd opt1 opt2)    ;非惰性求值,使用时需要在选项处手动增加lambda封装
;  (funcall cnd opt1 opt2))
(defmacro lazy-if (cnd opt1 opt2) ;惰性求值
  `(funcall ,cnd
            (lambda () ,opt1)
            (lambda () ,opt2)))

(defun op-not(cnd)
  (lazy-if cnd *f* *t*))
(defun op-or (cnd1 cnd2)
  (lazy-if cnd1
           *t*
           (lazy-if cnd2 *t* *f*)))
(defun op-and (cnd1 cnd2)
  (lazy-if cnd1
           (lazy-if cnd2 *t* *f*)
           *f*))

程序分析

其中,原*if*(已被注释)以函数作为参数,这是《实用Common Lisp编程》中展示的一种编程特色,即某函数foo本身只提供抽象框架,具体功能依靠以参数形式输入的函数method决定。因此,可以通过控制函数method,显著改变函数foo的功能,从而实现高度可定制化。

考虑原*if**t**f*的一种简单实现方式:

(defparameter *t*
  (lambda (opt1 opt2) `,opt1))
(defparameter *f*
  (lambda (opt1 opt2) `,opt2))
(defun *if* (cnd opt1 opt2)
  (funcall cnd opt1 opt2))

其中由于*if*是一个函数,opt1、opt2的值在*if*分支中,首先直接求值,然后将值传递进入函数*if*。但这种实现方式有着重大问题:

  • 在实际使用中,往往不希望if的某一分支实际执行
  • 更多情形下,往往是为了避免某些操作的执行、减少意外,所以才使用if语句

为了解决这个问题,引入惰性求值,即传入*if*函数的实际上是以lambda表达式包裹而形成的函数;而在设计*t**f*函数时,函数体中应当实际执行该lambda表达式,从而释放出被包裹的表达式。

(defparameter *t*
  (lambda (opt1 opt2)
    `,opt2 (funcall opt1)))
(defparameter *f*
  (lambda (opt1 opt2)
    `,opt1 (funcall opt2)))

(defun *if* (cnd opt1 opt2)
  (funcall cnd opt1 opt2))

但该实现仍然存在不便之处:引入惯性加载后,*if*的分支需要手动用lambda表达式包裹,否则会导致执行错误。

为了解决这个问题,引入Common Lisp的概念:宏。与其它语言一样,Common Lisp的宏同样是一种文本替换。但由于Lisp语法本身的特点,Lisp语言的宏获得了极大的灵活性。甚至有人声称,Lisp宏具有无穷的可定制性。定义宏lazy-if

(defmacro lazy-if (cnd opt1 opt2) ;惰性求值
  `(funcall ,cnd
            (lambda () ,opt1)
            (lambda () ,opt2)))

进而,可以借助lazy-if来实现逻辑非、逻辑或、逻辑与。至此,逻辑与判断部分实现完毕。

自然数

代码展示

;;;; 自然数
(defparameter *0*
  (lambda (o)
    `(,o)))
(defun *0*-p (num)
  (if (eql num *0*)
      *t* *f*))

(defmacro *1*+ (自然数)
  `(lambda ()
     ,自然数))

(defun *1*- (自然数)
  (lazy-if (*0*-p 自然数)
           *0*
           (funcall 自然数)))

(defparameter *1* (*1*+ *0*))
(defparameter *2* (*1*+ *1*))
(defparameter *3* (*1*+ *2*))
(defparameter *4* (*1*+ *3*))
(defparameter *5* (*1*+ *4*))
(defparameter *6* (*1*+ *5*))
(defparameter *7* (*1*+ *6*))
(defparameter *8* (*1*+ *7*))
(defparameter *9* (*1*+ *8*))

程序分析

对于自然数零的定义,是任意的。关键在于定义自然数后继的规则。

可以通过lambda表达式的一层包裹来代替后继。

(defmacro *1*+ (自然数)
  `(lambda ()
     ,自然数))

类似地,前继则通过剥离lambda表达式来实现。其中,定义零的前继也是零。

(defun *1*- (自然数)
  (lazy-if (*0*-p 自然数)
           *0*
           (funcall 自然数)))

至此,自然数部分构建完毕。

算数

代码展示

(defun *1*-p (num)
  (lazy-if (*0*-p num) *f*
           (lazy-if (*0*-p (*1*- num))
                    *t* *f*)))
(defun *+* (num1 num2)
  (lazy-if (*0*-p num2)
           num1
           (*+* (*1*+ num1) (*1*- num2))))
(defun *-* (num1 num2)
  (lazy-if (*0*-p num2)
           num1
           (*-* (*1*- num1) (*1*- num2))))
(defun *mul* (num1 num2)
  (labels ((multi-not-*0* (base times sum)
             (lazy-if (*1*-p times)
                      sum
                      (multi-not-*0* base
                                     (*1*- times)
                                     (*+* base sum)))))
    (lazy-if (*0*-p num2)
             *0*
             (multi-not-*0* num1 num2 num1))))
(defun *div* (num1 num2)
  (labels ((loop-er (num1 num2 商)
             (lazy-if (*<* num1 num2)
                      商
                      (let ((剩余 (*-* num1 num2)))
                        (loop-er 剩余
                                 num2
                                 (*1*+ 商))))))
    (lazy-if (*0*-p num2) *0* ; 任何数除以0均为0,可以接受
             (loop-er num1 num2 *0*))))

(defun *eql* (num1 num2)
  (lazy-if (op-and (*0*-p (*-* num1 num2))
                   (*0*-p (*-* num2 num1)))
           *t* *f*))
(defun *<* (num1 num2)
  (lazy-if (op-not (*0*-p (*-* num2 num1)))
           *t* *f*))
(defun *>* (num1 num2)
  (lazy-if (op-not (*0*-p (*-* num1 num2)))
           *t* *f*))

程序分析

对于算数加,以以下步骤考虑:

  1. 考虑num1+num2,其中num2=0,则结果为num1
  2. 考虑num1+num2,其中num2≠0,则结果可以转换为(num1+1)+(num2-1)
  3. 递归进行第2步的转换,直到符合第1步的情形

类似地,实现算数减。

对于算数乘,以以下步骤考虑:

  1. 考虑num1*num2,其中num2=0,则结果为0
  2. 考虑num1*num2,其中num2=1,则结果为num1
  3. 考虑num1*num2,其中num2≠0且num2≠1,则问题可以转换为num1+num1*(num2-1)
  4. 递归进行第3步的转换,直到符合第2步的情形

对于算数除,以以下步骤考虑:

  1. 考虑num1/num2,其中num2=0,则不可能存在结果,但为了程序的鲁棒性,规定结果为0
  2. 考虑num1/num2,其中num2≠0,且num1<num2,则结果为0(大小判断符号基于减号实现)
  3. 考虑num1/num2,其中num2≠0,且num1num2,则问题可以转换为1+(num1-num2)/num2
  4. 递归进行第4步的转换,直到符合第2步的情形

根据以上步骤,每种算术都能以简洁、美观的递归形式写出。但为了考虑到利用Common Lisp的尾递归优化,所以笔者在函数内通过labels宏定义了内部函数loop-er。有些Common Lisp实现具有尾递归优化机制,例如笔者使用的SBCL。为了利用尾递归优化,所必须的是,函数体内在递归调用结束后不存在后续操作。

通过减法来实现大小判断符号,这里不再赘述。

斐波那契

(defun 展现-01 (自然数)
  (labels ((loop-er (自然数 disp-num)
             (lazy-if (*0*-p 自然数)
                      disp-num
                      (loop-er (*1*- 自然数) (1+ disp-num)))))
    (loop-er 自然数 0)))
(defun 构造 (digit)
  (labels ((loop-er (自然数 digit)
             (if (> digit 0)
                 (loop-er (*1*+ 自然数) (1- digit))
                 自然数)))
    (loop-er *0* digit)))
(defun 斐波那契 (第几个月)
  (lazy-if (op-or (*0*-p 第几个月)
                  (*1*-p 第几个月))
           *1*
           (*+* (斐波那契 (*-* 第几个月 *1*)) (斐波那契 (*-* 第几个月 *2*)))))
; (展现-01 (斐波那契 (构造 10)))
; => 89

链表

;; 双空间储物柜
(defmacro 柜 (物1 物2)
  `(lambda (勺) (funcall 勺 ,物1 ,物2)))
(defparameter 勺1 (lambda (物1 物2) 物2 物1))
(defparameter 勺2 (lambda (物1 物2) 物1 物2))

(defun 取1 (柜) (funcall 柜 勺1)) ; 精简指令
(defun 取2 (柜) (funcall 柜 勺2))
; (取1 (柜 'foo 'bar))
; => foo
; (取1 (取2 (柜 'foo (柜 'bar 'baz))))
; => bar

;; 链表
(defun 插 (链 elem) (柜 链 elem))
(defun 创-链 () (柜 *t* *t*))
(defun empty-p (链)
  (if (eql (取1 链) *t*) ; lazy-if 不能处理*t*, *f*以外的值
      *t* *f*))
; 展现-链 原实现方式: 已删除

(defun exist-链 (链 cond-p)
  (lazy-if (empty-p 链) *f*
           (let ((elem (取2 链))
                 (子 (取1 链)))
             (lazy-if (funcall cond-p elem) *t*
                      (exist-链 子 cond-p)))))
(defun find-链 (链 cond-p)
  (lazy-if (empty-p 链) *f*
           (let ((elem (取2 链))
                 (子 (取1 链)))
             (lazy-if (funcall cond-p elem) elem
                      (find-链 子 cond-p)))))
; map-链, 逆-链 原实现方式: 已删除

;; 优化
(defun reduce-链* (链 method init)
  "链表聚合;符合尾递归的写法"
  (labels ((loop-er (链 value)
             (lazy-if (empty-p 链) value ; 判断为空链
                      (let* ((子 (取1 链))
                             (elem (取2 链))
                             (new-value (funcall method elem value)))
                        (loop-er 子 new-value)))))
    (loop-er 链 init)))
; (展现-01 (reduce-链* *12345* (lambda (pre next) (*+* pre next)) *0*))
; => 15

(defun 展现-链* (链)
  "符合尾递归形式的展现链表
   tail(原head) 被置于最后,这只是很小的一个代价"
  (reduce-链* 链
              (lambda (elem value) (cons elem value))
              '(tail)))
; (展现-链* (插(插(插(插(插(创-链) '上) '海) '自) '来) '水))
; => (上 海 自 来 水 TAIL) 

(defun map-链* (链 cond-p)
  "符合尾递归形式的映射链表
   逻辑的值并不直观(显示为函数的内存地址),所以改以符号的形式显示"
  (reduce-链* 链
              (lambda (elem value)
                (let ((elem-boolean (if (eql *t* (funcall cond-p elem))
                                        '*t* '*f*)))
                  (cons elem-boolean value)))
              '(tail)))
; (defvar *12345* (插(插(插(插(插 (创-链) *1*) *2*) *3*) *4*) *5*))
; (map-链* *12345* (lambda (elem) (*>* elem *2*)))
; => (*F* *F* *T* *T* *T* TAIL)
(defun 逆-链* (链)
  "符合尾递归形式的逆序链表"
  (reduce-链* 链
              (lambda (elem value) (插 value elem))
              (创-链)))
; (展现-链* (逆-链* (插(插(插(插(插(创-链) '上) '海) '自) '来) '水)))
; => (水 来 自 海 上 TAIL)

标签:lazy,num1,num2,Lisp,defun,Common,opt1,lambda
From: https://www.cnblogs.com/suspended-monitor/p/17647635.html

相关文章

  • mybatisplus中lambdaQuery()与lambdaUpdate()的使用
    这篇“mybatisplus中lambdaQuery()与lambdaUpdate()怎么使用”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“mybatisplus中lambdaQuery()与lambdaUpdate()怎么使用”......
  • 一种基于clisp的Common Lisp集成开发环境可行简易搭建方案
    绪论背景CommonLisp是一种优美的、小巧的语言,然而新手在入门CommonLisp时往往会遇到集成开发环境搭建的门槛,为CommonLisp的入门造成了障碍。尽管技术人员的推荐中,存在一种常见的集成开发环境配置方案是Emacs+Slime+SBCL三件套的方案,但该方案存在一些问题:Emacs和CommonLis......
  • 哪篇论文宣布了 HTAP 数据库的诞生? StoneDB带您解读《A Common Database Approach for
    theme:condensed-night-purple开启掘金成长之旅!这是我参与「掘金日新计划·12月更文挑战」的第4天,点击查看活动详情本文是 StoneDB学术分享会专栏的第五篇,我们来分享一下HTAP学术界上比较经典的一篇论文《ACommonDatabaseApproachforOLTPandOLAPUsinganIn-M......
  • C++ 函数指针与Lambda匿名函数
    函数指针c语言学过了,这里简单记一下,下面举例一个用法就行:#include<iostream>#include<vector>template<classT>voidprint(Tv){ std::cout<<v<<std::endl;}template<classT1,classFunc>voidForEach(std::vector<T1>&v,Func......
  • 第二十三节 API(算法,lambda,练习)
    常见的七种查找算法:​ 数据结构是数据存储的方式,算法是数据计算的方式。所以在开发中,算法和数据结构息息相关。今天的讲义中会涉及部分数据结构的专业名词,如果各位铁粉有疑惑,可以先看一下哥们后面录制的数据结构,再回头看算法。1.基本查找​ 也叫做顺序查找​说明:顺序......
  • lambda
    在Java8中,引入了一种新的语法特性——Lambda表达式。Lambda表达式允许开发者以更简洁、更直观的方式编写代码,尤其在函数式编程和集合数据处理方面表现出色。它的引入大大提升了Java语言的表达能力和代码可读性。在本文中,我们将深入探讨JavaLambda表达式的概念、语法和实际应用。......
  • java:使用flexmark-java 实现 CommonMark(规范 0.28)解析
    文档https://github.com/vsch/flexmark-java依赖Java8<dependency><groupId>com.vladsch.flexmark</groupId><artifactId>flexmark-all</artifactId><version>0.62.2</version></dependency>Java9+&l......
  • C# 根据字段名称取得对象的Lambda表达式
    ///<summary>///根据字段名称取得对象的Lambda表达式///</summary>///<typeparamname="T"></typeparam>///<paramname="column"></param>///<returns></returns>publicExpression<Func<T,object......
  • 奇技淫巧:Lambda表达式
    最近学习到的奇技淫巧:Lambda表达式,将函数包括递归函数改为Lambda表达式写法,可节省大量时间,在大量调用下可能节省近一半时间。说明该语法过于复杂,见https://en.cppreference.com/w/cpp/language/lambda,本文仅写在算法竞赛下的应用。该语法在OIWiki中有所提及,但是十分抽象,而这里......
  • 使用swagger时出现Unable to infer base url. This is common when using dynamic ser
    在使用Swagger的时候访问地址后出现了错误,http://localhost:8001/swagger-ui.html一直在弹窗提示,还取消不了我这边自己的问题可能是因为Swagger类没有跟启动类在同一个模块当中,虽然我将Swagger所在的模块进入到启动类所在的模块,但是可能是idea没有识别到.还是报错,可以按照......