写给 Java 程序员的 Lice 教程 3

2017/08/26 Lisp

写给 Java 程序员的 Lice 教程

本教程适用于 Lice 3.1.X

本文大部分内容都是一本正经介绍语言,有少量的不易察觉的卖萌。
—— by 千里冰封

Lice 是什么

这是一门运行在 JVM 上的解释性 Lisp 方言。

Lice 有哪些特色

  • 轻量级
  • 熟悉语法只需要五分钟
  • 没有运行时,标准库可定制
  • 解释器是纯 Kotlin 实现,跨到所有 JVM 存在的平台
  • 和 JVM 语言有良好的互操作性
  • 支持动态元编程,比如 undef
  • 动态解释性语言不需要编译,可以代替一些频繁改动的逻辑
  • 支持全角括号,全角分号,全角引号,全角逗号等初学者易混淆的东西的解析
  • 做 DSL 挺好的
  • 没有保留字, if while 都是函数
  • 支持函数式编程(通过 defexpr )

Lice 的不足之处

  • 解释性语言效率都屎一样
  • 所有符号都视为零参数函数调用,也就是说+的值是0(== 0 +)的结果是true

可以看看它长啥样吗

可以。简单的程序:

(print (str-con "Hello World"))

普通的程序:

(load "lice/math.lice")

(print PI) ; It's in lice/math.lice

(while (> 10 (<-> i 0))
  (|> (print i)
   (type i)
   (-> i (+ 1 i))))

复杂的程序:

(for-each
  i
  (.. 1000 1500)
  (thread|> (force|>
    (println i)
    (write-file
      (file (format "%d.html" i))
      (read-url (url (str-con
        "https://vijos.org/p/" i)))))))

支持 UTF-8 的字符:

Lice > ( + 1 1 ) ; UTF-8
2 => java.lang.Integer

Lice > ( + 1 , 2 )
3 => java.lang.Integer

学习 Lice 需要什么基础

这取决于你想做到什么程度。

将 Lice 作为学习 Lisp 所采用的语言

  • 那么你不需要拥有编程基础
  • 但是你需要会用电脑,以及会安装 JRE。

将 Lice 用于平时写一些小玩具用

  • 少量的编程基础
  • 知道啥是函数调用,啥是返回值

将 Lice 作为自己项目的脚本语言

  • 最基本的 Java 基础,会写匿名内部类或者 Lambda 表达式。

参与 Lice 语言的开发

  • 扎实的 Kotlin 基础
  • 函数式编程基础
  • 面对几千行高度压缩过的代码还能硬着头皮去啃的精神
  • 怒怼@千里冰封@Glavo的勇气

环境搭建

Linux

有 JRE

提供 deb 包下载, 安装后直接使用 lice-repl 命令打开交互式解释器。

无 JRE

提供 带 JRE 的 deb 包下载, 安装后直接使用 lice-repl 命令打开交互式解释器。

Zip 格式

提供 带 JRE 的 zip 包下载, 安装后直接使用

java -jar lice-repl-2.0.jar

命令打开交互式解释器。

Windows

Jar 包

提供可执行的 jar 包, 使用

java -jar lice-repl-2.0.jar

运行。

Zip 包(内置 JRE)

提供内置 JRE 的 zip 包, 解压后使用

java -jar lice-repl-2.0.jar

运行。

通过源码构建

Linux

$ git clone https://github.com/lice-lang/ldk.git
$ cd lice
$ chmod a+x ./gradlew
$ ./gradlew build

Windows

$ git clone https://github.com/lice-lang/ldk.git
$ cd lice
$ gradlew build

使用教程

根据上面的教程,你现在应该在命令行看到了这样的信息:

Welcome to Lice REPL v2.0  (Lice v3.1.1, Java 1.8.0_131)
see: https://github.com/lice-lang/lice-repl
see also: https://github.com/lice-lang/lice

Type in expressions for evaluation. Or try :help.

lice>

这就是 lice 语言的交互式解释器。我们可以输入一段代码(+ 1 1)来测试它是否工作正常:

lice> (+ 1 1)
res0: java.lang.Integer = 2

这是一段基于括号的 S 表达式的代码,其意义等同于 Java 中的

1 + 1

但是,这里有两个区别。

  1. (+ 1 1)是合法的、可以运行的代码。1 + 1只是你 Java 代码中的一部分,不能单独运行。
  2. (+ 1 1)中的+符号是一个函数,它接收多个数值参数,返回他们相加的结果。而1 + 1的+不是函数,是 Java 语言语法的一部分。

我们还能进行更多操作:

lice> (println res0)
2

lice> (-> 我的妈呀 "233")
res1: java.lang.String = 233

lice> 我的妈呀 
res2: java.lang.String = 233

可以看到, Lice 是支持使用一些奇奇怪怪的字符作为函数名的。

好了废话了半天,我们开始使用 Lice 编程吧。

Lice 的语法

Lice 的语法很简单。

  1. 注释只有一种: 分号开头,直到行末都是注释。 ; 比如这就是一个注释。
  2. 函数调用: (函数名 参数 1 参数 2 ...)
  3. 字面量: 100 0xFF 0b1010 011 true false null () 233N "字符串" 以上是 Lice 仅有的几种字面量类型。
  4. 空格和逗号用于区分不同元素(就是函数名啦,字面量啦,下同)。空格和逗号语义完全相同,你可以按照自己喜欢的方式使用。

注意:

  1. 字符串不支持逃逸字符,但是支持在源码中写成跨多行的形式。
  2. 整数字面量支持 2 8 10 16 四种进制, 0b 开头是 2 进制, 0 开头是 8 进制 0x 开头是 16 进制(字母不区分大小写)其余被视为十进。
  3. 整数后面加个 n (模糊大小写),可以变成 BigIntegerBigInteger 和浮点类型互操作会变成 BigDecimal
  4. 不能直接在括号中写一个值,括号里第一个元素必须是函数名,后面是参数。
  5. 每个函数都有返回值。
  6. 不能(+ 1(+ 1 1)),必须(+ 1 (+ 1 1))。否则1(+会被视为一个单独的词法元素。

语法就这么点。你现在已经学会 Lice 这门语言了,但是你肯定还不知道怎么编程。毕竟 if while 这些结构你都不知道咋整。 其实这些都是标准库函数,将在下文介绍。

你还可以通过 Lice 强(la)大(ji)的动态元编程能力来把他们取消定义掉,这样这语言就没法用了。

基本的标准库函数

数值运算

Lice 中的 (数值运算符号 参数1 参数2 ... 参数n) 等价于 Java 中的 参数1 数值运算符号 参数2 数值运算符号 ... 参数n 数值运算符号, 并且大整数运算也被算作是数值运算的一部分。

其中,数值运算符号可以是 + - * / %

布尔运算

和数值运算规则相同,只是符号是 && || !

字符串操作

lice> (str-con "deep " "dark " "fantasy")
res4: java.lang.String = deep dark fantasy

lice> (str-con (str-con "My name " "is Van") " I'm an artist")
res5: java.lang.String = My name is Van I'm an artist

str-con 就是连接字符串, string-connect 的缩写。

还有更多的。这里介绍了大量标准库函数,读者不必完全记忆:

lice> (split "My name is Van, I'm an artist, I'm an performance artist" " ")
res6: java.util.ArrayList = [My, name, is, Van,, I'm, an, artist,, I'm, an, performance, artist]

lice> (int->oct 100)
res7: java.lang.String = 0144

lice> (int->hex 100)
res8: java.lang.String = 0x64

lice> (str->int "02345")
res10: java.lang.Integer = 1253

lice> (str->int "100")
res11: java.lang.Integer = 100

lice> (str->int "-0xFF23333")
res12: java.lang.Integer = -267531059

lice> (str->int "-0b10101010")
res13: java.lang.Integer = -170

lice> (->chars "ice1000")
res15: java.util.ArrayList = [i, c, e, 1, 0, 0, 0]

lice> (->chars "glavo baka")
res16: java.util.ArrayList = [g, l, a, v, o,  , b, a, k, a]

流程控制语句

你现在终于要学会怎么使用流程控制了。讲道理,这里看完,你就可以使用 Lice 写出稍微复杂一点的代码了。

lice> (if true 1 2)
res1: java.lang.Integer = 1

lice> (if true 1)
res2: java.lang.Integer = 1

lice> (if false 1)

lice> (if false 1 2)
res3: java.lang.Integer = 2

while 同理,第一个参数是条件,第二个是函数体。

还有一个对应的类似 C 系列语言的 switch、Scheme 的 cond、 不过更加类似 Kotlin 的 when 语句的语句: when。

这个语句接受任意数量的参数。如果参数数量是偶数, 那么每两个一组,作为条件和返回语句。如果参数数量是奇数,那么最后一个会被视为是 default 值。

比如:

lice> (def judge-score score (when
    | (>= score 100), "full score! congratulations!"
    | (>= score 90), "nice job!"
    | (>= score 80), "come on! you can do it!"
    | (>= score 70), "please study hard!"
    | (>= score 60), "fortunately, you passed."
    | (>= score 0 ), "you die!"
    | "WTF! you must be kidding."))

有了它,你就不需要巨大的 if 嵌套了。

就语法来讲还是很简单的。

什么? for 循环是什么?

一些奇奇怪怪的 API

(print (read-url (url "http://ice1000.org")))

引入副作用

有时你需要同时执行多个语句。比如, if 和 while 每个条件分支都只提供了一个参数来提供给你执行代码。如果你需要实现以下 Java 代码:

long a = 2333333L;
long b = new Random(System.currentTimeMillis())
    .nextLong();
if (a > b) {
  System.out.println("I have a mmp");
  System.out.println(" and ");
  System.out.println("I have not idea wtf I should say");
}

在这里你需要用到一个这样的函数:

lice> (|> (print "billy") (type "billy"))
billyres0: java.lang.Class = class java.lang.String

|>,看起来很奇怪是不是?这和 Clojure 中的 run 含义完全相同。我给它起这个名字是因为这个符号看起来像各种 IDE 中表示运行的三角形箭头:

run

而且在 Fira Code 字体打开 ligature 的时候可以看到这两个字符的 ligature :

ligature

于是有:

lice> (|> (print "I have a mmp") (print " and ") (println "I have not idea whether I should say"))  
I have a mmp and I have not idea whether I should say

同时,还有两个比较类似的函数,一个是thread|>,传入的所有表达式将会被在一个单独的线程里求值,并返回最后一个。

lice> (thread|> (print 1) (print 2) (print 3)) 

1lice> 23

看到了没?由于求值被放在了另外的线程中, repl 的输出被打乱了(你看到的可能会不一样,但反正应该不是正常的)。

还有一个是force|>,执行里面所有的代码并在产生 Exception 的时候退出执行,返回最后一次成功求值的结果。 可以用于强制执行一段代码,比如爬取网页的时候配合thread|>使用。

(for-each i
  (.. 1000 1500)
  (thread|> (force|>
    (println i)
    (write-file
      (file (format "%d.html" i))
      (read-url (url (str-con "https://vijos.org/p/" i)))))))

上面的代码可以爬取 vijos 的 1000 到 1500 号题目到当前目录。

那么你可能会问了,上面的->是啥意思啊?

变量

Lice 有两个函数,它们长得非常形象:

  • ->,用于设置全局变量的值
  • <->,用于取出一个值,并提供默认值。如果变量未定义,那么就定义,填入默认值并返回该值。

我们可以直接从 repl 里面看到它的能力。

lice> (-> name-of-variable "Pachouli Go")
res18: java.lang.String = Pachouli Go

lice> name-of-variable 
res19: java.lang.String = Pachouli Go

lice> name-of-variable-and-it-is-wrong
undefined variable: name-of-variable-and-it-is-wrong
at line: 1


lice> (<-> undefined-but-with-default "deep")
res20: java.lang.String = deep

lice> (<-> name-of-variable "Let us go")
res21: java.lang.String = Pachouli Go

lice> (<-> undefined-but-with-default "dark")
res22: java.lang.String = deep

讲道理我觉得还是很简单的。

函数

这里就需要用到“局部变量”和“作用域”的概念了。函数定义语法是这样的:

(def function-name param1 param2 body)

解释一下:第一个参数是函数名,最后一个参数是函数体,中间的就是参数表,这样写的话比起 Scheme 就少了一个括号。

比如:

lice> (def abs a (if (> a 0) a (- 0 a)))
defined abs

lice> (abs 10)
res23: java.lang.Integer = 10

lice> (abs -10)
res24: java.lang.Integer = 10

然后这里, abs 内部的 a 就是局部变量了,你可以对它赋值,函数内部赋值后对外不可见。

元编程

讲道理,目前的元编程只有一点点。比如有一个 Ruby 风格的 undef :

lice> (print "Hello" )
Hello
lice> (undef print)
res26: java.lang.Boolean = true

lice> (print "Hello" )
undefined variable: print
at line: 1

虽然我感觉没什么卵用,但是拿来作为奇技淫巧玩玩还是可以的。

还有函数别名 alias ,判断函数是否定义的 def?函数等。

我说完了

好了本篇教程就到这里啦。

语言完整的参考

这个 GitHub 仓库里面。 README 就是了,详细地讲述了三种定义函数的方法。


Search

    Post Directory



    如果觉得这篇文章给您带来了收获或者说它值得您这么做,您可以选择对我进行捐助。
    下面是微信支付哟