clojure学习笔记(更新中)

在我们开始学习clojure语言之前,先选一个自己喜欢的ide吧,本人在尝试intellij失败后果断回到了eclipse的怀抱中(对于有强迫症的同学,可以搜下xumingming大侠的intellij leiningen的文章)。插件的安装步骤就不写了,在eclipse marketplace 中搜索counterclockwise安装重启即可。

File->New->other->clojure project

创建我们第一个clojure文件 helloworld.clj

clojure学习笔记(更新中)_第1张图片

core.clj是我们创建clojure项目时eclipse给我们创建的事例。

开始写helloworld了

在我们的helloworld.clj中写

(print "helloworld")

然后按F11运行,这时候console会打印出helloworld,同时eclipse为我们打开了一个新的视图:REPL

REPL:Read-Eval-Print Loop 交互式编程环境。如果你会用Python,那么这东西一定不陌生,只是现在才知道它叫这名字。

现在我们既能在helloworld.clj中写代码然后运行查看结果,又能直接在REPL中输入我们的代码了,看看在REPL中的helloworld吧

在REPL中运行时,需要将光标定位在行末再敲回车。查看历史command:Ctrl+方向键


clojure支持强类型和动态类型,clojure有一个基本构成部分,称作形式(form),form可以看作语法的一部分,布尔值、字符、字符串、集合(set)、映射(map)、向量(vector)这些都是form。从表面看form就是clojure的数据类型,我将form理解为clojure内置操作基本类型的函数。

字符和字符串
表示一个字符使用符号\
=>\a
\a
使用str函数可将字符拼接成字符串
=>(str \a \b \c)
"abc"
clojure中字符串使用双引号括起来,并使用C语言风格的转义字符
=>(println "hello\nworld")
hello
world
nil

使用str函数将其他类型数据转换成字符串,如果目标是Java类型,str会调用底层的toString()方法。该函数可接受多个参数,并且还能拼接非字符串。

=>(str 1)
"1"

字符和字符串是不相等的,好比java中char和String。

=> (= "a" \a)
false

布尔值和表达式

clojure支持强类型和动态类型,动态类型意味着类型在运行时求值。

clojure中的类型与java类型是统一的,可以使用class函数获得其底层类型

=> (class true)
java.lang.Boolean
=> (class True)
CompilerException java.lang.RuntimeException: Unable to resolve symbol: True in this context, compiling:(NO_SOURCE_PATH:1:1) 

来看看if语句的使用

=> (if true (print "True it is"))
True it is
nil

带上else的if:

=> (if false
   (print "True it is") (print "False it is"))
False it is
nil

从上面代码可以看出,clojure是可以自动识别多行的,检测的是()的对应。if函数只能接收一个或两个参数,如果超过就会报错:

=> (if true
   (print "True it is") (print "False it is") (print "11"))
CompilerException java.lang.RuntimeException: Too many arguments to if, compiling:(NO_SOURCE_PATH:1:1) 

list、vector、set、map

在学习集合、序列之前,我们先来看看他们的一些特殊成员及他们的常用操作,最后再来总结他们的共性。

list

列表list是元素的有序集合,对比java,我们可以看似Linkedlist。对表头表尾的操作是非常高效的。但是随机访问效率会差很多。

列表的两种表示:

=> (list 1 2 3)
(1 2 3)
=> '(1 2 3)
(1 2 3)

列表的主要操作有四个:first(头元素)、rest(除头部以外的列表)、last(最后一个元素)、cons(将元素添加到头部):

=> (first '("a" "b" "c"))
"a"
=> (last '("a" "b" "c"))
"c"
=> (rest '("a" "b" "c"))
("b" "c")
=> (cons "d" '("a" "b" "c"))
("d" "a" "b" "c")'

vector

向量也是元素的有序集合。对比java,可以看似ArrayList。它对最后一个的元素的操作或访问是非常高效的。vector对于以索引的方式访问某个元素或者修改某个元素来说非常高效,函数定义的时候指定参数列表用的就是vector。通过索引访问元素效率非常高(nth方法)。

向量的两种表示:

=> (vector "a" "b" "c")
["a" "b" "c"]
=> ["a" "b" "c"]
["a" "b" "c"]

下面是vector的常见函数

=> (first ["a" "b" "c"])
"a"
=> (last ["a" "b" "c"])
"c"
=> ( nth ["a" "b" "c"] 2)
"c"
=> ( nth ["a" "b" "c"] 4 "default")
"default"
=> (get ["a" "b" "c"] 4 "default")
"default"

get函数和nth很相似。他们都接收一个可选的默认值参数:如果给定的索引超出边界,那么会返回这个默认值。如果没有指定默认值而索引又超出边界,get函数会返回nil,nth函数会抛出一个异常。

向量也是函数,取下标为参数:

=> (["a" "b" "c"] 2)
"c"

也可以合并两个vector:

=> (concat ["a" "b" "c"] ["d" "e" "f"])
("a" "b" "c" "d" "e" "f")

这里需要注意的是:合并后的返回值类型是列表而不是向量。许多返回集合的函数都返回序列这个抽象类型(很像java中的iterator思想)。

set

set集合是元素的无序集合,不包含重复元素(和我们java中的set集合相似)。

set的表示:

=> #{"a" "b" "c"}
#{"a" "b" "c"}

我们给它们个变量名myset再来操作它们:

=> (def myset #{"a" "b" "c"})
=> (count myset)
3

如果要合并set,可以用上文提到的concat函数,不过它返回的是序列。也可以用set自己的方法union,返回合并后的set。

=> (clojure.set/union #{"a" "b" "c"} #{"d" "e" "f"})
#{"a" "b" "c" "d" "e" "f"}

求差集(第一个set为主导):

=> (clojure.set/difference #{"a" "b" "c"} #{"a" "b" "f"})
#{"c"}

同向量vector一样,set不仅是集合也是函数。该函数可以判断元素是否属于该set的成员,如果属于返回该成员,不属于返回nil:

=> (#{"a" "b" "c"} "a")
"a"
=> (#{"a" "b" "c"} "d")
nil

判断set是否包含某个元素还有个方法就是:contains? ,该函数返回布尔值。

=> (contains? #{"a" "b" "c"} "d")
false

在clojure.set命名空间里还有一些其他函数:index,intersection,join,mapinvert,project,rename,rename-keys,select等。其中有些操作的对象是map,但返回的是set。

Map

和其他语言一样map就是键值对集合。键和值可以为任意对象。也是无序的。

下面是Map的表示,其中逗号“,”是可选的,解析的时候会被当作空格处理。

=> {"a" :1,"b":2,"c":3}
{"a" :1, "b" :2, "c" :3}

如果我们分行来显示,逗号就没必要了:

=>(def person{
            :name "littlered"
            :age 25
            :city "kunming"})
=> (person :name)
"littlered"

可以使用merge函数合并两个map:

=> (merge {"a" :1,"b":2,"c":3} {"d":4,"e":5,"f":5})
{"f" :5, "e" :5, "d" :4, "a" :1, "b" :2, "c" :3}

集合

clojure提供一下集合类型:list、vector、set、map。虽然clojure中可以使用java的所有集合类型,但是一般不会这样做,因为clojure自带的集合类型更适合函数式编程。

对于clojure集合注意以下几点:

1) clojure集合是不可修改的。一旦集合产生之后,就不能从集合里删除/添加元素。所以clojure没有提供对集合进行修改的函数。如果我们要给一个集合进行修改,其实是在创建一个新的集合(共享旧的集合内存)。

2) clojure集合是异源的。集合中可以装任何东西,它们的类型不必相同。

3) clojure集合是持久的。持久意味着当一个新的版本产生后,旧的版本还是在的,clojure以一种非常高效、共享内存的方式来实现,比如给一个map添加一个新元素,新的map将共享旧map的内存和新元素的内存组成。

下面是集合的一些常用操作:

=> (count [1 2 3 4])
4
=> (reverse [1 2 3 4])
(4 3 2 1)

conj函数是conjoin的缩写,添加一个元素到集合里去。添加的位置取决具体的集合。

//todo

map

apply

序列

序列是与具体实现无关的抽象层。序列封装了所有clojure集合(list、vector、set、map)、字符串、甚至包括文件系统结构(流、目录)。一般来说,只要支持函数first、rest、cons,就可以用序列封装起来。

1)测试

如果要测试序列,可以用判定函数。

=> (every? number? [1 2 3 :four])
false

关键字以冒号打头,被用来当作唯一标示符,通常用在map里面 (比如:red, :green和 :blue)。

2)修改序列

序列函数库中包含一系列用于以各种方式转换序列的函数。比如filter函数:

取出长度大于4的单词:

=> (def words ["luke" "chewie" "han" "lando"])
   (filter (fn [word] (> (count word) 4)) words)
#'littlered.helloworld/words
("chewie" "lando")

上面代码中内部的(fn [word] (> (count word) 4)) 这是一个匿名函数。我们也可以将它分解开:

=> (def words ["luke" "chewie" "han" "lando"])
=> (defn foo [word] (> (count word) 4))
#'littlered.helloworld/foo
=> (filter foo words)
("chewie" "lando")

定义函数

之前我们已经看见过很多次函数了,这里系统的总结下:
一)使用defn宏用来定义一个函数

注意如果你经常用python,会很习惯的敲def,在clojure中def的作用是:在当前命名空间里定义(或重定义)一个var(在定义的时候你可以给它赋值或者不赋值):

=> (def p "hello")
=> (print p)
hello
nil
二)函数的参数可以是不定的,可选的参数必须放在最后面,通过&符号把不定参数放在一个list中:
=> (defn defntest [para1 para2] (print (+ para1 para2)))
=> (defntest 1 2)
3
nil
=> (defn defntest [para1 para2 &para &para4] (print (+ para1 para2 &para3 &para4)))
=> (defntest 1 2 3 4)
10
nil

三)函数定义可以包含多个参数列表以及对应的方法体,每个参数列表必须对应不同个数的参数。

=> (defn defntest "这是定义函数可选的注释,会自动生成doc"
     ([] (print "default para"))
     ([para1] (print "this is para1 func"))
     ([para1 para2](print "this is two paras func"))
     )
=> (defntest)
default para
nil
=> (defntest "hello")
this is para1 func
nil
=> (defntest "hello" "word")
this is two paras func
nil
匿名函数

前文中已经提到了fn函数,这里再详细介绍下。匿名函数通常被当作参数传递给其他有名函数,对于那些只在一个地方使用的函数比较有用。

匿名函数的两种表示:

=> (print ((fn [name] (str "my name is ",name)) "lb"))
my name is lb
nil
=> (print (#(str "my name is ",%) "lb"))
my name is lb
nil

通过fn定义的匿名函数可以包含任意个数的表达式;通过#()定义的匿名函数只能包含一个表达式,#()中的%代表参数,如果有多个参数用%1 %2 ...表示:

=> (print (#(str "our name are ",%1," ",%2) "lb" "cxh"))
our name are lb cxh
nil
需要注意的是java里面的overload可以根据参数类型或参数个数来重载,但是clojure中只能根据参数个数来重载。不过clojure中的multimethods技术可以实现任意类型的重载(还没看到,后面再补充)


你可能感兴趣的:(clojure)