Clojure小教程(更新中)

来源:互联网 发布:知乎 电动车 脚蹬 编辑:程序博客网 时间:2024/05/17 22:13
  • 什么是函数式编程?
特点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) --> 返回53.defn直接给函数取外部别名,直接替换上面的1中的fn即可.(defn outer_add [x y] (+ x y))(outer_add 1 2) --> 34.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的值在()中可以被使用,但不能被改变.* ifwhen(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_fieldeg:(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)) --> 返回104.设置实例属性,如果未提供setter可以使用set!eg:(def point (java.awt.Point 10 10))(defn setX [po xval] (set! (.x po) xval))(setX point 15)(.x point) --> 返回155.链式调用支持,使用..宏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.只引用其他命名空间的部分函数使用:useeg:(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.*)
0 0
原创粉丝点击