您的位置:首页 > 其它

Clojure小教程

2016-05-30 00:07 225 查看
什么是函数式编程?

特点1:函数是一等公民,即与其他基本类型处于等价地位,可以被返回,可以被赋值也可以作为参数.python js clojure都支持这点.
特点2:更多的表达式,减少过程.即每个语句尽量都是计算并返回计算结果.所有的函数都要有返回值(可以为nil).
特点3:没有副作用(side-effect),即函数就是单纯的执行计算,不改变外部变量.(改变外部变量最简单的就是修改一个全局变量,但这在函数式编程是不允许的)
特点4:不修改变量.即不改变参数与全局变量,这也意味着函数的运行状态不能用变量(对象)保存.java可以通过传递值引用,c可以通过指针
来改变函数参数实际调用值的状态,这在函数式编程中是不允许的,函数式编程使用参数来保存状态(参数传递所有的运行参数),最好的例子就是递归.
(defn sum [x s] (if (pos? x) (recur (dec x) (+ s x)) s));每次运行时都把当前状态传递进去,作为函数的必要运行信息.
特点5:引用透明(Referential transparency),指的是函数的运行不依赖于外部变量或"状态",只依赖于输入的参数,任何时候只要参数相同,引用函数所得到的返回值总是相同的。

优势1:函数式编程不需要考虑"死锁"(deadlock),因为它不修改变量,所以根本不存在"锁"线程的问题。
优势2:函数式编程没有副作用,只要保证接口不变,内部实现是外部无关的。所以,可以在运行状态下直接升级代码.即函数的独立性高.


Clojure的简易语法

* 基本类型:整型,浮点,有理数(分数),字符串,字符(\a \A \u2014),符号,关键字
* 集合类型:
list --> (1 2 3),一般第一个元素为函数或者宏,如果第一个为函数,则往后每个元素(可能为另外的list)依次计算后将值传给函数处理.可以用(list 1 2 3)生成.
vector --> [1 2 3]
map --> {"key1" 1,"key2" 2}
set --> #{1 2 3 4}

* 函数定义

1.普通的匿名形式
1.1(fn inner_add [x y] (+ x y))
其中inner_add为内部别名不是真正的函数名称,可以不要.
1.2(fn inner_add ([x] x) ([x y] (+ x y))
重载一个函数为多种实现,即将每个实现的参数(vector)与方法体(list)拿出来放到一个list中,实际调用时根据参数个数调用.
1.3(fn mul [& x] #{x})
支持变长参数,&+"space"后的参数名为变长参数,将作为一个list传入函数定义中.
eg:((fn mul [& x] #{x}) 1 2 3) --> 返回 #{(1 2 3)}

2.#()匿名函数形式,直接在#(),实现函数,参数应用分别为 %1 %2...
eg (#(+ %1 %2) 2 3) --> 返回5

3.defn直接给函数取外部别名,直接替换上面的1中的fn即可.
(defn outer_add [x y] (+ x y))
(outer_add 1 2) --> 3

4.def一般用于给一个值取别名(symbol),也可以用于匿名函数.
(def my_add (fn [x y] (+ x y))) || (def my_add #(+ %1 %2))

* 符号赋值
(def x 1)
(def y x)

* 序列块:使用do将一些操作序列组成一个块,只有最后一个元素的值被返回
(do 1 (+ 1 2) (- 9 2) 3) --> 3

* 设定全局不变量(let [] ())
(let [x 10 y 100] (do (println x) (println y))); -->则xy的值在()中可以被使用,但不能被改变.

* if与when
(if (cond) (thenclause) (elseclause));cond必须返回bool值,若为true则执行thenclause,否则执行elseclause
(when (cond) (sta1) (sta2) (sta3)) ;当cond为true时,执行所有的语句,默认支持do语句(sta1 ... sta3),但不存在分支else语句.

* 使用recur尾递归实现循(recur只能进行尾递归)
;不用loop的写法,相当于在内部匿名函数后直接给出参数值.
(defn px [line]
((fn [cur]
(when (<= cur line)
((fn [col]
(if (<= col cur)
(do (print "*")
(recur (inc col)))
(println "")))
1)
(recur (inc cur)))
)
1))
;loop写法,可以在loop的第一个参数给定循环的初始值 --> eg:(loop [x 1 y 2] (...))
;loop循环等价于一个有初始参数的匿名函数调用
;(loop [x 10] (when (pos? x) (println x) (recur (dec x)))) <==>((fn [x] (when (pos? x) (println x) (recur (dec x)))) 10)
(defn py [line]
(loop [cur 1]
(when (<= cur line)
(loop [col 1]
(if (<= col cur)
(do (print "*")
(recur (inc col)))
(println))
)
(recur (inc cur))
)))

* 非尾递归可以直接使用函数名调用
;eg 汉诺塔,可以看到x的值在某个特定函数调用上,保持不变
(defn hanoi [x] (+ (+ (hanoi (dec x)) 1) (hanoi (dec x))))

* quote,用于保证一个list及其子list不被解析,()空list不用加quote.
;eg (quote (+ 1 (+ 2 3))) -->返回 (+ 1 (+ 2 3))
;eg (quote (1 2)) --> 返回(1 2)不报错.
也可以使用'(1 2) '(+ 1 2) .

语法quote --> 使用`
`(+ 1 2)
与quote不同,其会自动展开
(map even? [1 2 3])
;=> (clojure.core/map clojure.core/even? [1 2 3])

* unquote(~),quote递归使得子list不解析,可以unquote某个子表达式,让它计算.
`(+ 1 ~(* 2 3))
=> (clojure.core/+ 1 6)
反quote拼接:
(let [x '(2 3)] `(1 ~@x))
;=> (1 2 3)
~@里的@,它告诉 Clojure,不要解开序列 x,将它拼装到最终的 list 里,而不是作为嵌套 list 插入。

* 与java互操作
1.静态域与静态方法ClassName/static_method_or_field
eg:(println Math/PI) (println (Math/sqrt 9))
2.创建实例(ClassName. )
eg:(java.util.HashMap. ["x" 1 "y" 2])或(new java.util.HashMap ["x" 1 "y" 2])
3.调用对象方法或实例域(.method_or_field)
eg:
(.divide (java.math.BigDecimal. "42") 2M)
(.x (java.awt.Point. 10 0)) --> 返回10
4.设置实例属性,如果未提供setter可以使用set!
eg:
(def point (java.awt.Point 10 10))
(defn setX [po xval] (set! (.x po) xval))
(setX point 15)
(.x point) --> 返回15
5.链式调用支持,使用..宏
eg:new java.util.Date().toString().endsWith("2016")可以表示为
(.. (java.util.Date) toString (endsWith "2016"))
6.doto宏,用于设置一个java对象的多个实例域.
eg:
Date time = new Date();
time.setYear(1);
time.setMonth(2);
time.setDate(3);等价于
(doto (java.util.Date.) (.setYear 1) (.setMonth 2) (.setDate 3))

* 异常处理
1.抛出异常(throw (Exception. "I done throwed"))
2.异常捕获
eg:
(defn throw-catch [f]   (try (f) (catch ArithmeticException e "No dividing by zero!")     (catch Exception e (str "You are so bad " (.getMessage e)))  (finally (println "returning... "))))
调用(throw-catch #(/ 10 2))将传递一个匿名函数作为throw-catch的参数.

* 命名空间
1.Clojure命名空间的创建
(ns my.clojure)
2.引用其他命名空间(获得函数/宏定义),使用:require
(ns my.core (:require my.clojure))
3.只引用其他命名空间的部分函数使用:use
eg:
(ns my.util (:use [my.core :only [myfunc1 myfunc2]]));只导入myfunc1与myfunc2
(ns my.util (:use [my.core :exclude [myfunc1 myfunc2]]));只有myfunc1与myfunc2不导入
4.导入java类库.(java.lang.*默认导入)
(import java.util.*)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: