Clojure 语言基础

概览

Datatypes

MAP    {:key1 :val1, :key2 :val2}
VECTORS    [1 2 3 4 :a :b :c 1 2]
SETS    #{:a :b :c 1 2 3}
SCALARS    a-symbol, :a-keyword, "a string"
ARRAYS    (array 1 2 3)

Functions

CALLING    (<FUNCTION> <ARGS*>)
DEFINING NAMED FUNCTIONS    (defn <NAME> [<ARGS*>] |CONSTRAINTS| <ACTIONS*>)
ANONYMOUS FUNCTION    (fn |NAME| [<ARGS*>] |CONSTRAINTS| <ACTIONS*>)
ANONYMOUS INLINE FUNCTION    #(<ACTION> |%,%2,%3, OR %&|)

Useful Macros
CONDITIONALS    if if-let cond condp and or when when-let
NESTING, CHAINING, AND INTEROP    -> ->> doto .. .
DEFINING THINGS    def defn fn let binding defmulti defmethod deftype defrecord reify this-as


Useful Functions

MATH    + - * / quot rem mod inc dec max min
COMPARISON    = == not= < > <= >=
PREDICATES    nil? identical? zero? pos? neg? even? odd? true? false?
DATA PROCESSING    map reduce filter partition split-at split-with
DATA CREATE    vector vec hash-map set for list list*
DATA INSPECTION    first rest get get-in keys vals count get nth contains? find
DATA MANIPULATION    seq into conj cons assoc assoc-in dissoc zipmap merge merge-with select-keys update-in
ARRAYS    first rest get get-in keys vals count get nth contains? find

Lisp的语法

Lisp的语法很多人很喜欢,很多人很讨厌, 主要因为它大量的使用圆括号以及前置表达式. 如果你不喜欢这些,那么你要考虑一下是不是要学习Clojure了 。许多文件编辑器以及IDE会高亮显示匹配的圆括号, 所以你不用担心需要去人肉数有没有多加一个左括号,少写一个右括号. 同时Clojure的代码还要比java代码简洁. 一个典型的java方法调用是这样的:

methodName(arg1, arg2, arg3);

而Clojure的方法调用是这样的:

(function-name arg1 arg2 arg3)

左括号被移到了最前面;逗号和分号不需要了. 我们称这种语法叫: “form”. 这种风格是简单而又美丽:Lisp里面所有东西都是这种风格的.要注意的是clojure里面的命名规范是小写单词,如果是多个单词,那么通过中横线连接。

定义函数也比java里面简洁。Clojure里面的 println 会在它的每个参数之间加一个空格。如果这个不是你想要的,那么你可以把参数传给 str ,然后再传给 println .

// Java
public void hello(String name) {
    System.out.println("Hello, " + name);
}
; Clojure
(defn hello [name]
  (println "Hello," name))

Clojure里面大量之用了延迟计算. 这使得只有在我们需要函数结果的时候才去调用它. “懒惰序列” 是一种集合,我们之后在需要的时候才会计算这个集合理解面的元素. 只使得创建无限集合非常高效.

对Clojure代码的处理分为三个阶段:读入期,编译期以及运行期。

在读入期,读入期会读取clojure源代码并且把代码转变成数据结构,基本上来说就是一个包含列表的列表的列表。

在编译期,这些数据结构被转化成java的bytecode。

在运行期这些java bytecode被执行。

函数只有在运行期才会执行。而宏在编译期就被展开成实际对应的代码了。

笛卡尔积

在数学中,两个集合X和Y的笛卡尓积(Cartesian product),又称直积,表示为X × Y,第一个对象是X的成员而第二个对象是Y的所有可能有序对的其中一个成员。 假设集合

A={a, b}

集合

B={0, 1, 2}

则两个集合的笛卡尔积为

{(a, 0), (a, 1), (a, 2), (b, 0), (b, 1), (b, 2)}

Java 实现笛卡尔积

我们人脑的思维基本跟汇编语言的风格差不多,做一件事情只能一步一步来,发出一个指令,执行一个指令,这就是"命令式编程"的通俗说法.

package org.lightsword.basics;

/**
 * Hello world!
 *
 */
public class App {
    public static void main(String[] args) {
        String[] X = { "a", "b", "c" };
        int[] Y = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

        System.out.println("====================");
        for (String x : X) {
            for (int y : Y) {
                System.out.println(x + " " + y);
            }
        }
        System.out.println("====================");
        for (String x : X) {
            for (int y : Y) {
                if (isOdd(y))
                    System.out.println(x + " " + y);
            }
        }
        System.out.println("====================");
        for (String x : X) {
            for (int y : Y) {
                if (isEven(y))
                    System.out.println(x + " " + y);
            }
        }

    }

    private static boolean isEven(int y) {
        return y % 2 == 0;
    }

    private static boolean isOdd(int y) {
        return y % 2 == 1;
    }
}

运行输出:

====================
a 0
a 1
a 2
a 3
a 4
a 5
a 6
a 7
a 8
a 9
b 0
b 1
b 2
b 3
b 4
b 5
b 6
b 7
b 8
b 9
c 0
c 1
c 2
c 3
c 4
c 5
c 6
c 7
c 8
c 9
====================
a 1
a 3
a 5
a 7
a 9
b 1
b 3
b 5
b 7
b 9
c 1
c 3
c 5
c 7
c 9
====================
a 0
a 2
a 4
a 6
a 8
b 0
b 2
b 4
b 6
b 8
c 0
c 2
c 4
c 6
c 8

Clojure 实现笛卡尔积

前缀表达式的风格, 跟我们数学里面教的四则运算,代数公式的表达风格不大一样.

比如说,我们计算加减乘除,在数学中我们通常这么写(常规表达式):

1 + 1 = 2
1 * 1 = 1
1 / 1 = 1
1 - 1 = 0

但是,天晓得有一群怪胎,思维能脱离这样的惯性束缚,直接看透问题的本质. 有点像爱因斯坦的广义相对论时空观. 常识告诉我们,时间空间似乎是均衡不变的, 但是深入本质才发现, 运动跟时间空间是一个体系里面的, 时间不是均匀流逝的, 空间也不是均衡分布的.

但是在 Lisp 中,我们使用前缀表达式来表达

(+ 1 1)=2
(* 1 1)=1
(/ 1 1)=1
(- 1 1)=0

在 Clojure 中,实现上述的笛卡尔积的代码如下:

user=> (for [x [:a :b :c], y (range 10)] (prn x y) )
:a 0
:a 1
:a 2
:a 3
:a 4
:a 5
:a 6
:a 7
:a 8
:a 9
:b 0
:b 1
:b 2
:b 3
:b 4
:b 5
:b 6
:b 7
:b 8
:b 9
:c 0
:c 1
:c 2
:c 3
:c 4
:c 5
:c 6
:c 7
:c 8
:c 9
(nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil)
user=> (for [x [:a :b :c], y (range 10) :when(odd? y)] (prn x y) )
:a 1
:a 3
:a 5
:a 7
:a 9
:b 1
:b 3
:b 5
:b 7
:b 9
:c 1
:c 3
:c 5
:c 7
:c 9
(nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil)
user=> (for [x [:a :b :c], y (range 10) :when(even? y)] (prn x y) )
:a 0
:a 2
:a 4
:a 6
:a 8
:b 0
:b 2
:b 4
:b 6
:b 8
:c 0
:c 2
:c 4
:c 6
:c 8
(nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil)
user=>

思维风格立马不一样了. 这就是函数式编程风格.

Clojure 语言基础

由于 Clojure 是 Lisp 的一门方言, 这里我们简单介绍一下 Lisp 的语言基础知识.

下面这个表格简要地列举了Clojure里面的一些语法糖, 这些语法糖我们会在后面详细讲解的,所以如果你现在没办法完全理解它们不用担心。

作用 语法糖 对应函数
注释 ; text  单行注释 宏(comment text)可以用来写多行注释
字符 (Java char 类型) \char \tab \newline \space \uunicode-hex-value (char ascii-code) (char \uunicode)
字符串 (Java String 对象) "text" (str char1 char2 ...) 可以把各种东西串成一个字符串
关键字是一个内部字符串; 两个同样的关键字指向同一个对象; 通常被用来作为map的key :name (keyword "name")
当前命名空间的关键字 ::name N/A
正则表达式 #"pattern" (re-pattern pattern)
逗号被当成空白(通常用在集合里面用来提高代码可读性) , (逗号) N/A
链表(linked list) '(items) (不会evaluate每个元素) (list items) 会evaluate每个元素
vector(和数组类似) [items] (vector items)
set #{items} 建立一个hash-set (hash-set items) (sorted-set items)
map {key-value-pairs} 建立一个hash-map (hash-map key-value-pairs) (sorted-map key-value-pairs)
给symbol或者集合绑定元数据 #^{key-value-pairs} object 在读入期处理 (with-meta object metadata-map) 在运行期处理
获取symbol或者集合的元数据 ^object (meta object)
获取一个函数的参数列表(个数不定的) & name N/A
函数的不需要的参数的默认名字 _ (下划线) N/A
创建一个java对象(注意class-name后面的点) (class-name. args) (new class-name args)
调用java方法 (. class-or-instance method-name args) 或者 (.method-name class-or-instance args) N/A
串起来调用多个函数,前面一个函数的返回值会作为后面一个函数的第一个参数;你还可以在括号里面指定额外参数;注意前面的两个点 (.. class-or-object (method1 args) (method2 args) ...) N/A
创建一个匿名函数 #(single-expression)% (等同于 %1), %1, %2来表示参数 (fn [arg-names] expressions)
获取Ref, Atom 和Agent对应的valuea @ref (deref ref)
get Var object instead of the value of a symbol (var-quote) #'name (var name)
syntax quote (使用在宏里面) ` none
unquote (使用在宏里面) ~value (unquote value)
unquote splicing (使用在宏里面) ~@value none
auto-gensym (在宏里面用来产生唯一的symbol名字) prefix# (gensym prefix )

对于二元操作符比如 +和*, Lisp方言使用前缀表达式而不是中缀表达式,这和一般的语言是不一样的。比如在java里面你可能会写 a + b + c , 而在Lisp里面它相当于 (+ a b c) 。这种表达方式的一个好处是如果操作数有多个,那么操作符只用写一次 . 其它语言里面的二元操作符在lisp里面是函数,所以可以有多个操作数。

Lisp代码比其它语言的代码有更多的小括号的一个原因是Lisp里面不使用其它语言使用的大括号,比如在java里面,方法代码是被包含在大括号里面的,而在lisp代码里面是包含在小括号里面的。

results matching ""

    No results matching ""