学习一门语言最好的方法就是去使用它。我们就从一个小例子来学习 Clojure语法.
首先先来看一下Clojure的核心语法。
Clojure的使用的是Lisp语法,又叫S表达式。核心语法非常的简单。但是对于熟悉c系语法(c,c++,java)的朋友来说,第一次接触会非常的不习惯。
以前面的hello world程序为例。
(println "Hello World")
它以”(“开始,后面跟的是函数println,接着空格跟的是参数”Hello World”,最后”)”结尾。了解了如上规则,你就基本学会了Clojure的大部分语法。下面比较一下Clojure,Java,Python,Ruby的一些语法.
Clojure 表达式 | 对应的 Java 语法 | 对应的 Python 语法 | 对应的 Ruby 语法 |
---|---|---|---|
(not k) | !k | not k | not k or !k |
(inc a) | a++、++a、a += 1、a + 1a | a += 1、a + 1 | a += 1 |
(/ (+ x y) 2) | (x + y) / 2 | (x + y) / 2 | (x + y) / 2 |
(instance? java. util.List al) | al instanceof java.util.List | isinstance(al,list) | al.isa? Array |
(if (not a) (inc b) (dec b)) | !a ? b + 1 : b – 1 | b + 1 if not a else b-1 | !a ? b + 1 : b – 1 |
(Math/pow 2 10)c | Math.pow(2, 10) | pow(2, 10) | 2 ** 10 |
(.someMethod someObj “foo” (.otherMethod otherObj 0)) | someObj.someMethod(“foo” , otherObj.otherMethod(0)) | someObj.someMethod(“foo” , otherObj.otherMethod(0)) | someObj.someMethod(“foo” , otherObj.otherMethod(0)) |
可以看出Clojure的语法有高度的一致性,即使你不熟悉S表达式,但是依据上面的原则,可以看懂它想表达的是一个什么意思。而对于其他三门语言,如果你没有一个个的学习相应的语法,你还是比较难理解它的意思的。
了解了核心语法,我们就可以来编写代码了。我们要编写的代码功能很简单,进行简繁翻译,其中过滤不需要翻译的文字。我们将分几步来完成:
我们先看第一个功能。我们要读取简繁字典。简繁字典其实就是简繁对照的文件,我这里叫jfmap.clj
格式如下:
万 萬 与 與 丑 醜 专 專 业 業 丛 叢 东 東 丝 絲 丢 丟 两 兩 严 嚴 丧 喪 个 個 丬 爿
这里只是简单的列了一点。具体内容请见附件。有了这个文件,我们如何把内容读出来呢?熟悉Java的都知道,我们要创建文件流来读取,然后要打开流,循环读取,最后关闭流,还要抓异常。很繁琐。在Clojure中如何处理呢?Clojure提供了slurp函数,可以根据提供的路径将文件内容读入。API如下:
clojure.core/slurp ([f & opts]) Opens a reader on f and reads all its contents, returning a string. See clojure.java.io/reader for a complete list of supported arguments.
OK。我们知道了要用什么函数。那么根据API和上面说的总规则,我们来写代码.
(slurp "jfmap-path")
你可以在REPL里面去实验这行代码的执行结果。执行此行代码,clojure会将jfmap.clj的内容以字符串的形式全部读入。接着呢?要做简繁翻译,字符串肯定不方便我们的操作。很明显map才是最适合的数据结构。那么我们如何将字符串变成map呢?
我们只能求助于API了,你可以在Clojure的Index页面搜索map,可以找到hash-map函数。它的API说明如下:
hash-map function Usage: (hash-map) (hash-map & keyvals) keyval => key val Returns a new hash map with supplied mappings. If any keys are equal, they are handled as if by repeated uses of assoc.
根据提供的映射关系返回一个新的hashmap。而这里我们是一个字符串,如何提供映射关系呢?按照空格将文字切开就行了嘛!!继续找API。Java里有split方法,Clojure里有没有相应的函数呢?试试再说。。有了!在clojure.string的Namespace1中,我们找到了叫split的函数!
split function Usage: (split s re) (split s re limit) Splits string on a regular expression. Optional argument limit is the maximum number of splits. Not lazy. Returns vector of the splits.
通过正则表达式来切割字符串。看着挺像,先用再说!我们有repl嘛!直接在repl里面输入
(split "万 萬" #" ")
执行!Oops,报错了!
CompilerException java.lang.RuntimeException: Unable to resolve symbol: split in this context
找不到split?!如果在Java中报类似的错误,你会想到什么?没有引入包阿!这里也是。在Java中会默认引入java.lang包,同理在Clojure中会引入clojure.core包。其他包则要自己引入,这里split在clojure.string包中。所以你需要引入clojure.string包。
(require 'clojure.string)
这也就是调用了require函数来进行引入!为什么clojure.string前面有个单引号呢?想想核心语法!这里暂不展开说!给大家留个思考题!!后续会专门对NameSpace引入做详细介绍!光引入还没用!调用代码也需要修改!
(clojure.string/split "万 萬" #" ")
你可能要吐槽了!既然引入了,为什么还要加包名前缀?!我们可以和Java作个比较!如果这里是Java的话,那么我们在调用split的时候,实际上是需要一个类作为前缀的,比如StringUtils.split()!但是在clojure中并没有类的概念!包(Clojure叫NameSpace)下面只有函数,所以它使用NameSpace来确保函数的唯一性引用!
当然了每次都要写这么长的NameSpace的名字也是挺烦人的。Clojure提供了简写.
(require ['clojure.stirng :as 'cstr]) (cstr/split "万 萬" #" ")
:as是Keyword,是Clojure字面量的一种。它和String很类似,不过有些区别,它比String有更多的功能。下表是Clojure所包含的字面量。
Type | Example(s) |
---|---|
Boolean | true,false |
Character | \a |
Keyword | :tag,:doc |
List | (1 2 3),(println “foo”) |
Map | {:name “Bill”,:age 42} |
Nil | nil |
Number | 1,4.2 |
Set | #{:snap :crackle :pop} |
String | “hello” |
Symbol | user/foo,java.lang.String |
Vector | [1 2 3] |
ok.终于得到了我们要的结果。这里的#” “是正则表达式(这是你遇到的第一个特殊语法,学习方法—死记!!),它构建了Java中的Pattern,所以正则表达式内容和Java完全相同,这里就不废话了。你只需要记住其语法就行了。
切开了字符串,我们来生成map吧!如何生成呢?你应该有答案了吧?
(hash-map (cstr/split "万 萬" #" "))
又报错了!
IllegalArgumentException No value supplied for key: ["万" "萬"] clojure.lang.PersistentHashMap.create (PersistentHashMap.java:77)
不合法的参数!!split得到的是个Vector([]包裹的数据结构是Vector),而hash-map要的参数类似于Java中的可变参数!如何匹配这两者呢?Clojure中提供了apply函数!API如下
clojure.core/apply ([f args] [f x args] [f x y args] [f x y z args] [f a b c d & args]) Applies fn f to the argument list formed by prepending intervening arguments to args.
此函数有点特别!它的第一个参数是函数,后面是该函数所需要的参数!知道怎么调用吗?
(apply hash-map (cstr/split "万 萬" #" "))
终于成功了!!我们看到了结果
{"万" "萬"} ;以{}包裹的数据结构是map
最后呢!我们需要对jfmap.clj的内容进行处理!So easy!
(apply hash-map (cstr/split (slurp "jfmap-path") #" "))
1 Namespace和Java中的包类似,但是在Clojure中叫Namespace。这里没有将其翻
译为命名空间,主要是怕有误解。