尽管两年前刚投身代码这个行业之初,我就听过Lisp的鼎鼎大名,不过一直没有想去了解过。后来在王垠的博客里见到他大谈特谈Scheme,不明觉厉的我也只当是MySQL中的数据库的意思。

最近,我对各种语言都充满了好奇心,想要了解更多的语言,遵循着Alan J. Perlis的名言:

A language that doesn't affect the way you think about programming, is not worth knowing.

而寻找着。

这半年来,我学习了pythoncc++,尽管还未全部掌握,但对代码的理解已经发生了很大的改变。

2019年,我对自己的要求是,掌握一门静态语言、一门生态丰富的动态语言、一门前端框架。加深对mysqlredis的了解。

今天,在完成手头的工作后,我想起了慕名已久的lisp。迅速在Google上做了简短的了解,得知目前lisp主要分为Common LispScheme两大家族。而受王垠的影响,我打算从Scheme入手学习Lisp

学习的资料来源于Takafumi Shido编写的Yet Another Scheme Tutorial中文版。

首先,我采用的编译器是ChezScheme,安装方式很简单,在githubclone代码以后,进入到根目录,执行:

./configure --installprefix=/usr/local
make && make install
make clean

即可。

IDE方面,虽然网络上大家都建议使用Emacs,不过对我来说不太顺手(我主要使用Jetbrains全家桶,以及VIM)。鉴于VIM在这方面并没有什么特效,我便选择了微软的Visual Studio Code

只需要在VSC插件商店中下载vscode-schemeCode Runner就可以。 在安装完这两个插件后,打开设置,并搜索code-runner:executor map by file extension,然后选择在settings.json中编辑,并在右侧的用户设置中添加:

{
    "code-runner.executorMapByFileExtension": {
        ".vb": "cd $dir && vbc /nologo $fileName && $dir$fileNameWithoutExt",
        ".vbs": "cscript //Nologo",
        ".scala": "scala",
        ".jl": "julia",
        ".cr": "crystal",
        ".ml": "ocaml",
        ".exs": "elixir",
        ".hx": "haxe --cwd $dirWithoutTrailingSlash --run $fileNameWithoutExt",
        ".rkt": "racket",
        ".ahk": "autohotkey",
        ".au3": "autoit3",
        ".kt": "cd $dir && kotlinc $fileName -include-runtime -d $fileNameWithoutExt.jar && java -jar $fileNameWithoutExt.jar",
        ".kts": "kotlinc -script",
        ".dart": "dart",
        ".pas": "cd $dir && fpc $fileName && $dir$fileNameWithoutExt",
        ".pp": "cd $dir && fpc $fileName && $dir$fileNameWithoutExt",
        ".d": "cd $dir && dmd $fileName && $dir$fileNameWithoutExt",
        ".hs": "runhaskell",
        ".nim": "nim compile --verbosity:0 --hints:off --run",
        ".csproj": "dotnet run --project",
        ".fsproj": "dotnet run --project",
        ".ss": "scheme" // 这是新增,设置scheme编译.ss后缀文件
    }
}

即可着手学习Scheme

目前看的进度还不是很多,不过递归给我的印象很深刻。Scheme语言的神奇使我感觉不写一篇日志恐怕难以平复我胸中的兴奋之情。以下稍作一些今天学习的记录:

(define list_01 `(1 2 3 4 5))
;尾递归求表中元素和
(define (my-sum ls)
    (my-sum-rec ls 0))
(define (my-sum-rec ls n)
    (if (null? ls)
        n
        (my-sum-rec (cdr ls) (+ (car ls) n))))
;尾递归翻转表中元素
(define (my-reverse ls)
    (my-reverse-rec ls `()))
(define (my-reverse-rec ls0 ls1)
    (if (null? ls0)
        ls1
        (my-reverse-rec (cdr ls0) (cons (car ls0) ls1))))
;运行函数,输出结果
(display (my-sum list_01))  ;输出15
(display #\newline)  ;换行
(display (my-reverse list_01))  ;输出(5 4 3 2 1)
(exit)

求解一元二次方程ax² + bx + c = 0; 对let的应用

;ax² + bx + c = 0                                                                                                                  
;x² + (b/a)x + c/a = 0                                                                                                             
;(x + b/2a)² - b²/4a² + c/a = 0                                                                                                
;(x + (b/2a))² = b²/4a² - c/a                                                                                                    
;(x + (b/2a))² = (b² - 4ac)/4a²                                                                                                  
;x + (b/2a) = (+-)sqrt(b² - 4ac)/2a                                                                                                
;x = (+-)sqrt(b² - 4ac)/2a - b/2a                                                                                                  
;x = (+-)(sqrt(b² - 4ac) - b)/2a 
(define (quadric-equation a b c)
  (if (zero? a)
      'error
      (let ((d (- (* b b) (* 4 a c))))
        (if (negative? b)
            `()
            (let ((e (/ b -2 a)))
              (if (zero? d)
                  (list e)
                  (let ((f (/ (sqrt d) 2 a)))
                    (list (+ e f) (- e f)))))))))
(display (quadric-equation 1 4 4))
(display #\newline)

named let,可用于表达循环。

let同时具有定义函数的功能。以上文中的尾递归反转表中元素为例:

(define (my-reverse ls)
  (let my-reverse-rec((ls0 ls) (ls1 `()))
    (if (null? ls0)
        ls1
        (my-reverse-rec (cdr ls0) (cons (car ls0) ls1)))))
(display "the reverse of (1 2 3 4 5) is ")
(display (my-reverse `(1 2 3 4 5)))
(display #\newline)
(exit)

通过命名letmy-reverse-rec得以嵌入my-reverse函数体当中

letrec,允许局部变量在定义中调用自己

用法(letrec (函数定义) (调用函数))

;letrec                                                                                                                             
(define (my-reverse ls)
  (letrec ((my-reverse-rec (lambda (ls0 ls1)
                             (if (null? ls0)
                                 ls1
                                 (my-reverse-rec (cdr ls0) (cons (car ls0) ls1))))))
    (my-reverse-rec ls `())))
(display "the reverse of (1 2 3 4 5) is ")
(display (my-reverse `(1 2 3 4 5)))
(display #\newline)
(exit)