Clojure小教程(更新中)

  • 什麼是函數式編程?
特點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的值在()中可以被使用,但不能被改變.

* 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_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.*)
發佈了45 篇原創文章 · 獲贊 4 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章