forked from mvolkmann/Clojure-Article
-
Notifications
You must be signed in to change notification settings - Fork 0
/
article_cn.html
2916 lines (2229 loc) · 181 KB
/
article_cn.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<title>Clojure - Functional Programming for the JVM中文版</title>
<style type="text/css">
body { color: rgb(0, 0, 0); background-color: rgb(255, 255, 255);
font-family: Verdana, sans-serif;
margin-left: 0.25in;
margin-right: 0.25in; }
a:hover { color: rgb(0, 0, 255); background-color: rgb(255, 253, 160); }
code { font-family: "Courier New", sans-serif; }
div.center { text-align: center; }
h1 { text-align: center; }
h2 { text-align: left; }
h3 { text-align: left; }
hr { height: 1px; color: rgb(122, 96, 86); background-color: transparent; }
p { text-align: justify; }
p.author { text-align: center; }
p.footer { text-align: justify; }
pre { font-family: "Courier New", sans-serif; }
.educationquicklinks { text-align: center; }
.quicklinks { text-align: right; }
.red { color: rgb(255, 0, 0); background-color: rgb(255, 255, 255); }
.green { color: rgb(0, 128, 0); background-color: rgb(255, 255, 255); }
.blue { color: rgb(0, 0, 192); background-color: rgb(255, 255, 255); }
.code { background-color: #FFFFF0; border: dashed black 1px; padding-left: 10px; }
</style>
</head>
<body>
<h1>Clojure - Functional Programming for the JVM中文版</h1>
<p class="author">
作者<br />
<a href="http://www.ociweb.com/mark/" onclick="window.open(this.href,'_blank');return false;" title="Author Bio">R. Mark Volkmann</a>,
Partner <br />Object Computing, Inc. (OCI)<br />
最后更新: 12/4/11 <br/><br/>
译者<br />
<a href="http://xumingming.sinaapp.com">徐明明</a>
</p>
<h2><a name="contents">内容列表</a></h2>
<table border="1">
<tbody>
<tr>
<td style="padding:5px;"><a href="#introduction">简介</a></td>
<td style="padding:5px;"><a href="#condition-processing">条件处理</a></td>
<td style="padding:5px;"><a href="#reference-types">引用类型</a></td>
</tr>
<tr>
<td style="padding:5px;"><a href="#functional-programming">函数式编程</a></td>
<td style="padding:5px;"><a href="#iteration">迭代</a></td>
<td style="padding:5px;"><a href="#compiling">编译</a></td>
</tr>
<tr>
<td style="padding:5px;"><a href="#clojure-overview">Clojure概述</a></td>
<td style="padding:5px;"><a href="#recursion">递归</a></td>
<td style="padding:5px;"><a href="#automated-testing">自动化测试</a></td>
</tr>
<tr>
<td style="padding:5px;"><a href="#get-started">开始吧</a></td>
<td style="padding:5px;"><a href="#predicates">谓词</a></td>
<td style="padding:5px;"><a href="#editor-and-ides">编辑器和IDE</a></td>
</tr>
<tr>
<td style="padding:5px;"><a href="#clojure-syntax">Clojure语法</a></td>
<td style="padding:5px;"><a href="#sequences">序列</a></td>
<td style="padding:5px;"><a href="#desktop-applications">桌面程序</a></td>
</tr>
<tr>
<td style="padding:5px;"><a href="#repl">REPL</a></td>
<td style="padding:5px;"><a href="#input-output">输入输出</a></td>
<td style="padding:5px;"><a href="#web-application">Web应用</a></td>
</tr>
<tr>
<td style="padding:5px;"><a href="#bindings">Bindings</a></td>
<td style="padding:5px;"><a href="#destructuring">解构</a></td>
<td style="padding:5px;"><a href="#databases">数据库</a></td>
</tr>
<tr>
<td style="padding:5px;"><a href="#collections">集合</a></td>
<td style="padding:5px;"><a href="#namespaces">名字空间</a></td>
<td style="padding:5px;"><a href="#libraries">类库</a></td>
</tr>
<tr>
<td style="padding:5px;"><a href="#struct-maps">StructMaps</a></td>
<td style="padding:5px;"><a href="#metadata">元数据</a></td>
<td style="padding:5px;"><a href="#conclusion">结论</a></td>
</tr>
<tr>
<td style="padding:5px;"><a href="#defining-functions">定义函数</a></td>
<td style="padding:5px;"><a href="#macros">宏</a></td>
<td style="padding:5px;"><a href="#references">引用</a></td>
</tr>
<tr>
<td style="padding:5px;"><a href="#java-interoperability">和Java的互操作</a></td>
<td style="padding:5px;"><a href="#concurrency">并发</a></td>
<td style="padding:5px;"></td>
</tr>
</tbody>
</table>
<h2><a name="introduction">简介</a></h2>
<p>这篇文章的目的是以通俗易懂的方式引导大家进入Clojure的世界。文章涵盖了cojure的大量的特性, 对每一个特性的介绍我力求简洁。你不用一条一条往下看,尽管跳到你感兴趣的条目。</p>
<p>请把你的意见、建议发送到<a href="mailto:mark@ociweb.com">mark@ociweb.com</a>。我对下面这样的建议特别感兴趣:</p>
<ul>
<li>你说是X, 其实是Y</li>
<li>你说是X, 但其实说Y会更贴切</li>
<li>你没有提到X, 但是我认为X是一个非常重要的话题</li>
</ul>
<p>对这篇文章的更新可以在<a href="http://www.ociweb.com/mark/clojure/">http://www.ociweb.com/mark/clojure/</a>找到,同时你也可以在<a href="http://www.ociweb.com/mark/stm/">http://www.ociweb.com/mark/stm/</a>找到有关<code>Software Transactional Memory</code>的介绍, 以及Clojure对STM的实现。</p>
<p>这篇文章里面的代码示例里面通常会以注释的形式说明每行代码的结果/输出,看下面的例子:</p>
<div class="code">
<pre xml:space="preserve">
(+ 1 2) ; showing return value: 3
(println "Hello") ; return nil, showing output:Hello
</pre>
</div><br/>
<em><a href="#contents">回到上面</a></em>
<h2><a name="functional-programming">函数式编程</a></h2>
<p><a href="http://en.wikipedia.org/wiki/Functional_programming">函数式编程</a>是一种强调函数必须被当成第一等公民对待, 并且这些函数是“纯”的编程方式。这是受<a href="http://en.wikipedia.org/wiki/Lambda_calculus">lambda表达式</a>启发的。纯函数的意思是同一个函数对于同样的参数,它的返回值始终是一样的 -- 而不会因为前一次调用修改了某个全局变量而使得后面的调用和前面调用的结果不一样。这使得这种程序十分容易理解、调试、测试。它们没有副作用 -- 修改某些全局变量, 进行一些IO操作(文件IO和数据库)。状态被维护在方法的参数上面, 而这些参数被存放在栈(stack)上面(通常通过递归调用), 而不是被维护在全局的堆(heap)上面。这使得方面可以被执行多次而不用担心它会更改什么全局的状态(这是非常重要的特征,等我们讨论事务的时候你就会意识到了)。这也使得高级编译器为了提高代码性能而对代码进行重排(reording)和并行化(parallelizing)成为可能。(并行化代码现在还很少见)</p>
<p>在实际生活中,我们的程序是需要一定的副作用的。Haskel的主力开发<code>Simon Peyton-Jones</code>曾经曰过:</p>
<blockquote>“到最后,任何程序都需要修改状态,一个没有副作用的程序对我们来说只是一个黑盒, 你唯一可以感觉到的是:这个黑盒在变热。。”(<a href="http://oscon.blip.tv/file/324976">http://oscon.blip.tv/file/324976</a>)</blockquote>
<p>问题的关键是我们要控制副作用的范围, 清晰地定位它们,避免这种副作用在代码里面到处都是。</p>
<p>把函数当作“第一公民”的语言可以把函数赋值给一个变量,作为参数来调用别的函数, 同时一个函数也可以返回一个函数。可以把函数作为返回值的能力使得我们选择之后程序的行为。接受函数作为参数的函数我们称为“高阶函数”。从某个方面来说,高阶函数的行为是由传进来的函数来配置的,这个函数可以被执行任意次,也可以从不执行。</p>
<p>函数式语言里面的数据是不可修改的。这使得多个线程可以在不用锁的情况下并发地访问这个数据。因为数据不会改变,所以根本不需要上锁。随着多核处理器的越发流行,函数式语言对并发语言的简化可能是它最大的优点。如果所有这些听起来对你来说很有吸引力而且你准备来学学函数式语言,那么你要有点心理准备。许多人觉得函数式语言并不比面向对象的语言难,它们只是风格不同罢了。而花些时间学了函数式语言之后可以得到上面说到的那些好处,我想还是值得的。比较流行的函数式语言有:<a href="http://clojure.org/">Clojure</a>, <a href="http://en.wikipedia.org/wiki/Common_Lisp">Common Lisp</a>, <a href="http://erlang.org/">Erlang</a>, <a href="http://research.microsoft.com/en-us/um/cambridge/projects/fsharp/default.aspx">F#</a>, <a href="http://www.haskell.org/">Haskell</a>, <a href="http://en.wikipedia.org/wiki/ML_(programming_language)">ML</a>, <a href="http://caml.inria.fr/ocaml/index.en.html">OCaml</a>, <a href="http://en.wikipedia.org/wiki/Scheme_(programming_language)">Scheme</a>, <a href="http://www.scala-lang.org/">Scala</a>. Clojure和Scala是Java Virtual Machine (JVM)上的语言. 还有一些其它基于JVM的语言: <a href="http://common-lisp.net/project/armedbear/">Armed Bear Common Lisp (ABCL)</a>, <a href="http://ocamljava.x9c.fr/">OCaml-Java</a> and <a href="http://www.gnu.org/software/kawa/">Kawa (Scheme).</a></p>
<em><a href="#contents">回到上面</a></em>
<h2><a name="clojure-overview">Clojure概述</a></h2>
<p>Clojure是一个动态类型的,运行在JVM(JDK5.0以上),并且可以和java代码互操作的函数式语言。这个语言的主要目标之一是使得编写一个有多个线程并发访问数据的程序变得简单。</p>
<p>Clojure的发音和单词closure是一样的。Clojure之父是这样解释Clojure名字来历的:</p>
<blockquote>“我想把这就几个元素包含在里面: C (C#), L (Lisp) and J (Java). 所以我想到了 Clojure, 而且从这个名字还能想到closure;它的域名又没有被占用;而且对于搜索引擎来说也是个很不错的关键词,所以就有了它了."</blockquote>
<p>很快Clojure就会移植到.NET平台上了. ClojureCLR是一个运行在Microsoft的CLR的Clojure实现. 在我写这个入门教程的时候ClojureCLR已经处于alpha阶段了.</p>
<p>在2011年7月, ClojureScript项目开始了,这个项目把Clojure代码编译成Javascript代码:看这里<a href="https://github.com/clojure/clojurescript">https://github.com/clojure/clojurescript</a>.</p>
<p>Clojure是一个开源语言, licence:<a href="http://www.eclipse.org/legal/epl-v10.html">Eclipse Public License v 1.0</a> (EPL). 这是一个非常自由的Licence. 关于EPL的更多信息看这里:<a href="http://www.eclipse.org/legal/eplfaq.php">http://www.eclipse.org/legal/eplfaq.php</a> .</p>
<p>运行在JVM上面使得Clojure代码具有可移植性,稳定性,可靠的性能以及安全性。同时也使得我们的Clojure代码可以访问丰富的已经存在的java类库:文件 I/O, 多线程, 数据库操作, GUI编程, web应用等等等等.</p>
<p>lojure里面的每个操作被实现成以下三种形式的一种: 函数(function), 宏(macro)或者special form. 几乎所有的函数和宏都是用Clojure代码实现的,它们的主要区别我们会在后面解释。Special forms不是用clojure代码实现的,而且被clojure的编译器识别出来. special forms的个数是很少的, 而且现在也不能再实现新的special forms了. 它们包括:<a href="http://clojure.org/special_forms#try">catch</a>,<a href="http://clojure.org/special_forms#toc1">def</a>,<a href="http://clojure.org/special_forms#toc3">do</a>,<a href="http://clojure.org/java_interop#dot">dot</a> ('.'),<a href="http://clojure.org/special_forms#try">finally</a>,<a href="http://clojure.org/special_forms#toc7">fn</a>,<a href="http://clojure.org/special_forms#toc2">if</a>,<a href="http://clojure.org/special_forms#toc4">let</a>,<a href="http://clojure.org/special_forms#toc9">loop</a>,<a href="http://clojure.org/special_forms#toc13">monitor-enter</a>,<a href="http://clojure.org/special_forms#toc14">monitor-exit</a>,<a href="http://clojure.org/java_interop#new">new</a>,<a href="http://clojure.org/special_forms#toc5">quote</a>,<a href="http://clojure.org/special_forms#toc10">recur</a>,<a href="http://clojure.org/java_interop#set">set!</a>,<a href="http://clojure.org/special_forms#try">throw</a>,<a href="http://clojure.org/special_forms#try">try</a> 和<a href="http://clojure.org/special_forms#toc6">var</a>.</p>
<p>Clojure提供了很多函数来操作序列(sequence), 而序列是集合的逻辑视图。很多东西可以被看作序列:Java集合, Clojure的集合, 字符串, 流, 文件系统结构以及XML树. 从已经存在的clojure集合来创建新的集合的效率是非常高的,因为这里使用了<a href="http://en.wikipedia.org/wiki/Persistent_data_structure">persistent data structures</a>的技术(这对于clojure在数据不可更改的情况下,同时要保持代码的高效率是非常重要的)。</p>
<p>Clojure提供三种方法来安全地共享可修改的数据。所有三种方法的实现方式都是持有一个可以改变的引用指向一个不可改变的数据。<a href="#reference-types">Refs</a> 通过使用<a href="http://en.wikipedia.org/wiki/Software_transactional_memory">Software Transactional Memory</a>(STM)来提供对于多块共享数据的同步访问。<a href="#Atoms">Atoms</a> 提供对于单个共享数据的同步访问。<a href="#Agents">Agents</a> 提供对于单个共享数据的异步访问。这个我们会在 "<a href="#reference-types">引用类型</a>”一节详细讨论。</p>
<p>Clojure是<a href="http://en.wikipedia.org/wiki/Lisp_(programming_language)">Lisp</a>的一个方言. 但是Clojure对于传统的Lisp有所发展。比如, 传统Lisp使用<code>car</code> 来获取链表里面的第一个数据。而Clojure使用<code>first</code>。有关更多Clojure和Lisp的不同看这里 <a href="http://clojure.org/lisps">http://clojure.org/lisps</a>.</p>
<p>Lisp的语法很多人很喜欢,很多人很讨厌, 主要因为它大量的使用圆括号以及前置表达式. 如果你不喜欢这些,那么你要考虑一下是不是要学习Clojure了 。许多文件编辑器以及IDE会高亮显示匹配的圆括号, 所以你不用担心需要去人肉数有没有多加一个左括号,少写一个右括号. 同时Clojure的代码还要比java代码简洁. 一个典型的java方法调用是这样的:</p>
<div class="code">
<pre>methodName(arg1, arg2, arg3);</pre>
</div>
<p>而Clojure的方法调用是这样的:</p>
<div>
<pre>(function-name arg1 arg2 arg3)</pre>
</div>
<p>左括号被移到了最前面;逗号和分号不需要了. 我们称这种语法叫: "form". 这种风格是简单而又美丽:Lisp里面所有东西都是这种风格的.要注意的是clojure里面的命名规范是小写单词,如果是多个单词,那么通过中横线连接。</p>
<p>定义函数也比java里面简洁。Clojure里面的<code>println</code> 会在它的每个参数之间加一个空格。如果这个不是你想要的,那么你可以把参数传给<code>str</code>,然后再传给<code>println</code> .</p>
<div class="code">
<pre>// Java
public void hello(String name) {
System.out.println("Hello, " + name);
}</pre>
<pre>; Clojure
(defn hello [name]
(println "Hello," name))</pre>
</div>
<p>Clojure里面大量之用了延迟计算. 这使得只有在我们需要函数结果的时候才去调用它. "懒惰序列" 是一种集合,我们之后在需要的时候才会计算这个集合里面的元素. 这使得创建无限集合非常高效.</p>
<p>对Clojure代码的处理分为三个阶段:读入期,编译期以及运行期。在读入期,读入期会读取clojure源代码并且把代码转变成数据结构,基本上来说就是一个包含列表的列表的列表。。。。在编译期,这些数据结构被转化成java的bytecode。在运行期这些java bytecode被执行。函数只有在运行期才会执行。而宏在编译期就被展开成实际对应的代码了。</p>
<p>Clojure代码很难理解么?想想每次你看到java代码里面那些复杂语法比如: <code>if</code>,<code>for</code> , 以及匿名内部类, 你需要停一下来想想它们到底是什么意思(不是那么的直观),同时如果想要做一个高效的Java工程师,我们有一些工具可以利用来使得我们的代码更容易理解。同样的道理,Clojure也有类似的工具使得我们可以更高效的读懂clojure代码。比如:<code>let</code>,<code>apply</code>,<code>map</code>,<code>filter</code>,<code>reduce</code> 以及匿名函数 ... 所有这些我们会在后面介绍.</p>
<em><a href="#contents">回到上面</a></em>
<h2><a name="get-started">让我们开始吧</a></h2>
<p>Clojure是一个相对来说很新的语言。在经过一些年的努力之后,Clojure的第一版是在2007年10月16日发布的。Clojure的主要部分被称为 "Clojure proper" 或者 "core"。你可以从这里下载:<a href="http://clojure.org/downloads">http://clojure.org/downloads</a>. 你也可以使用<a href="http://github.com/technomancy/leiningen/">Leiningen</a>。最新的源代码可以从它的Git库下载.</p>
<p>"<a href="#libraries">Clojure Contrib</a>"是一个大家共享的类库列表。其中有些类库是成熟的,被广泛使用的并且最终可能会被加入Clojure Proper的。但是也有些库不是很成熟,没有被广泛使用,所以也就不会被包含在Conjure Proper里面。所以Clojure Proper里面是鱼龙混杂,使用的时候要自己斟酌,文档在这里:<a href="http://richhickey.github.com/clojure-contrib/index.html">http://richhickey.github.com/clojure-contrib/index.html</a></p>
<p>对于一个Clojure Contrib, 有三种方法可以得到对应的jar包. 首先你可以下载一个打包好的jar包。其次你可以用maven 来自己打个jar包. Maven可以从这里下载<a href="http://maven.apache.org/">http://maven.apache.org/</a>. 打包命令是 "<code>mvn package</code>". 再其次你可以用ant. ant可以从这里下载<a href="http://ant.apache.org/">http://ant.apache.org/</a>。命令是: "<code>ant -Dclojure.jar={path}</code>".</p>
<p>要从最小的源代码来编译clojure, 我们假设你已经安装了<a href="http://git-scm.com/">Git</a> 和<a href="http://ant.apache.org/">Ant</a> , 运行下面的命令来下载并且编译打包Clojure Proper和Clojure Contrib:</p>
<div class="code">
<pre>git clone git://github.com/richhickey/clojure.git
cd clojure
ant clean jar
cd ..
git clone git://github.com/richhickey/clojure-contrib.git
cd clojure-contrib
ant -Dclojure.jar=../clojure/clojure.jar clean jar</pre>
</div>
<p>下一步,写一个脚本来运行Read/Eval/Print Loop (REPL) 以及运行 Clojure 程序. 这个脚本通常被命名为"clj". 怎么使用REPL我们等会再介绍. Windows下面,最简单的clj脚本是这样的(UNIX, Linux以及 Mac OS X下面把 %1 改成 $1):</p>
<div class="code">
<pre>java -jar /path/clojure.jar %1</pre>
</div>
<p>这个脚本假定<code>java</code> 在你的<code>PATH</code> 环境变量里面. 为了让这个脚本更加有用:</p>
<ul>
<li>把经常使用的JAR包比如 "<a href="#libraries">Clojure Contrib</a>" 以及数据库driver添加到classpath里面去(<code>-cp</code>).</li>
<li>使clj更好用:用<a href="http://utopia.knoware.nl/~hlub/uck/rlwrap/">rlwrap</a>(利用keystrokes来支持的) 或者<a href="http://jline.sourceforge.net/">JLine</a>来得到命令提示以及命令历史提示。</li>
<li>添加一个启动脚本来设置一些特殊变量(比如<code>*print-length*和</code> <code>*print-level*</code>), 加载一些常用的、不再<code>java.lang 里面的包</code> 加载一些常用的不再<code>clojure.core</code>里面的函数并且定义一些常用自定义的函数.</li>
</ul>
<p>使用这个脚本来启动REPL我们会等会介绍. 用下面这个命令来运行一个clojure脚本(通常以clj为后缀名):</p>
<div class="code">
<pre>clj source-file-path</pre>
</div>
<p>更多细节看这里<a href="http://clojure.org/getting_started">http://clojure.org/getting_started</a> 以及这里:<a href="http://clojure.org/repl_and_main">http://clojure.org/repl_and_main</a> 同时Stephen Gilardi 还提供了一个脚本:<a href="http://github.com/richhickey/clojure-contrib/raw/master/launchers/bash/clj-env-dir">http://github.com/richhickey/clojure-contrib/raw/master/launchers/bash/clj-env-dir</a>。</p>
<p>为了更充分的利用机器的多核,你应该这样来调用: "<code>java -server ...</code>".</p>
<p>提供给Clojure的命令行参数被封装在预定义的变量<code>*command-line-args*里面。</code></p>
<em><a href="#contents">回到上面</a></em>
<h2><a name="clojure-syntax">Clojure语法</a></h2>
<p>Lisp方言有一个非常简洁的语法 -- 有些人觉得很美的语法。数据和代码的表达形式是一样的,一个列表的列表很自然地在内存里面表达成一个tree。(a b c)表示一个对函数a的调用,而参数是b和c。如果要表示数据,你需要使用<code>'(a b c)</code> o或者<code>(quote (a b c))</code>。通常情况下就是这样了,除了一些特殊情况 -- 到底有多少特殊情况取决于你所使用的方言。</p>
<p>我们把这些特殊情况称为语法糖。语法糖越多代码写起来越简洁,但是同时我们也要学习更多的东西以读懂这些代码。这需要找到一个平衡点。很多语法糖都有对应的函数可以调用。到底语法糖是多了还是少了还是你们自己来判断吧。</p>
<p>下面这个表格简要地列举了Clojure里面的一些语法糖, 这些语法糖我们会在后面详细讲解的,所以如果你现在理解不了的话,那么完全不用担心</p>
<table border="1">
<tbody>
<tr>
<th>作用</th>
<th>语法糖</th>
<th>对应函数</th>
</tr>
<tr>
<td>注释</td>
<td><code>; <em>text</em></code>
<em>单行注释</em></td>
<td><code>宏(comment <em>text</em>)可以用来写多行注释</code></td>
</tr>
<tr>
<td>字符 (Java <code>char</code> 类型)</td>
<td><code>\<em>char</em></code> <code>\tab</code>
<code>\newline</code> <code>\space</code>
<code>\u<em>unicode-hex-value</em></code></td>
<td><code>(char <em>ascii-code</em>)</code>
<code>(char \u<em>unicode</em></code>)</td>
</tr>
<tr>
<td>字符串 (Java <code>String</code> 对象)</td>
<td><code>"<em>text</em>"</code></td>
<td><code>(str <em>char1</em> <em>char2</em> ...)</code>
可以把各种东西串成一个字符串</td>
</tr>
<tr>
<td>关键字是一个内部字符串; 两个同样的关键字指向同一个对象; 通常被用来作为map的key</td>
<td><code>:<em>name</em></code></td>
<td><code>(keyword "<em>name</em>")</code></td>
</tr>
<tr>
<td>当前命名空间的关键字</td>
<td><code>::<em>name</em></code></td>
<td>N/A</td>
</tr>
<tr>
<td>正则表达式</td>
<td><code>#"<em>pattern</em>"</code></td>
<td><code>(re-pattern <em>pattern</em>)</code></td>
</tr>
<tr>
<td>逗号被当成空白(通常用在集合里面用来提高代码可读性)</td>
<td><code>,</code> (逗号)</td>
<td>N/A</td>
</tr>
<tr>
<td>链表(linked list)</td>
<td><code>'(<em>items</em>)</code>
(不会evaluate每个元素)</td>
<td><code>(list <em>items</em>)</code>
会evaluate每个元素</td>
</tr>
<tr>
<td>vector(和数组类似)</td>
<td><code>[<em>items</em>]</code></td>
<td><code>(vector <em>items</em>)</code></td>
</tr>
<tr>
<td>set</td>
<td><code>#{<em>items</em>}</code>
建立一个hash-set</td>
<td><code>(hash-set <em>items</em>)</code>
<code>(sorted-set <em>items</em>)</code></td>
</tr>
<tr>
<td>map</td>
<td><code>{<em>key-value-pairs</em>}</code>
建立一个hash-map</td>
<td><code>(hash-map <em>key-value-pairs</em>)</code>
<code>(sorted-map <em>key-value-pairs</em>)</code></td>
</tr>
<tr>
<td>给symbol或者集合绑定元数据</td>
<td><code>#^{<em>key-value-pairs</em>} <em>object</em></code>
在读入期处理</td>
<td><code>(with-meta <em>object</em> <em>metadata-map</em>)</code>
在运行期处理</td>
</tr>
<tr>
<td>获取symbol或者集合的元数据</td>
<td><code>^<em>object</em></code></td>
<td><code>(meta <em>object</em>)</code></td>
</tr>
<tr>
<td>获取一个函数的参数列表(个数不定的)</td>
<td><code>& <em>name</em></code></td>
<td>N/A</td>
</tr>
<tr>
<td>函数的不需要的参数的默认名字</td>
<td><code>_</code> (下划线)</td>
<td>N/A</td>
</tr>
<tr>
<td>创建一个java对象(注意class-name后面的点)</td>
<td><code>(<em>class-name</em>. <em>args</em>)</code></td>
<td><code>(new <em>class-name</em> <em>args</em>)</code></td>
</tr>
<tr>
<td>调用java方法</td>
<td><code>(. <em>class-or-instance</em> <em>method-name </em><em>args</em>)</code> 或者
<code>(.<em>method-name</em> <em>class-or-instance </em><em>args</em>)</code></td>
<td>N/A</td>
</tr>
<tr>
<td>串起来调用多个函数,前面一个函数的返回值会作为后面一个函数的第一个参数;你还可以在括号里面指定额外参数;注意前面的两个点</td>
<td><code>(.. <em>class-or-object</em> (<em>method1 args</em>) (<em>method2 args</em>) ...)</code></td>
<td>N/A</td>
</tr>
<tr>
<td>创建一个匿名函数</td>
<td><code>#(<em>single-expression</em>)</code>
用<code>%</code> (等同于 <code>%1</code>), <code>%1</code>, <code>%2来表示参数</code></td>
<td><code>(fn [<em>arg-names</em>] <em>expressions</em>)</code></td>
</tr>
<tr>
<td>获取Ref, Atom 和Agent对应的valuea</td>
<td><code>@<em>ref</em></code></td>
<td><code>(deref <em>ref</em>)</code></td>
</tr>
<tr>
<td>get <code>Var</code> object instead of
the value of a symbol (var-quote)</td>
<td><code>#'<em>name</em></code></td>
<td><code>(var <em>name</em>)</code></td>
</tr>
<tr>
<td>syntax quote (使用在宏里面)</td>
<td><code>`</code></td>
<td>none</td>
</tr>
<tr>
<td>unquote (使用在宏里面)</td>
<td><code>~<em>value</em></code></td>
<td><code>(unquote <em>value</em>)</code></td>
</tr>
<tr>
<td>unquote splicing (使用在宏里面)</td>
<td><code>~@<em>value</em></code></td>
<td>none</td>
</tr>
<tr>
<td>auto-gensym (在宏里面用来产生唯一的symbol名字)</td>
<td><code><em>prefix</em>#</code></td>
<td><code>(gensym <em>prefix</em> )</code></td>
</tr>
</tbody>
</table>
<p>对于二元操作符比如<code>+</code>和<code>*</code>, Lisp方言使用前置表达式而不是中置表达式,这和一般的语言是不一样的。比如在java里面你可能会写<code>a + b + c</code>, 而在Lisp里面它相当于<code>(+ a b c)</code> 。这种表达方式的一个好处是如果操作数有多个,那么操作符只用写一次. 其它语言里面的二元操作符在lisp里面是函数,所以可以有多个操作数。</p>
<p>Lisp代码比其它语言的代码有更多的小括号的一个原因是Lisp里面不使用其它语言使用的大括号,比如在java里面,方法代码是被包含在大括号里面的,而在lisp代码里面是包含在小括号里面的。</p>
<p>比较下面两段简单的Java和Clojure代码,它们实现相同的功能。它们的输出都是: "edray" 和 "orangeay".</p>
<div class="code">
<pre>// This is Java code.
public class PigLatin {
public static String pigLatin(String word) {
char firstLetter = word.charAt(0);
if ("aeiou".indexOf(firstLetter) != -1) return word + "ay";
return word.substring(1) + firstLetter + "ay";
}
public static void main(String args[]) {
System.out.println(pigLatin("red"));
System.out.println(pigLatin("orange"));
}
}</pre>
</div>
<div class="code">
<pre>; This is Clojure code.
; When a set is used as a function, it returns a boolean
; that indicates whether the argument is in the set.
(def vowel? (set "aeiou"))
(defn pig-latin [word] ; defines a function
; word is expected to be a string
; which can be treated like a sequence of characters.
(let [first-letter (first word)] ; assigns a local binding
(if (vowel? first-letter)
(str word "ay") ; then part of if
(str (subs word 1) first-letter "ay")))) ; else part of if
(println (pig-latin "red"))
(println (pig-latin "orange"))</pre>
</div>
<p>Clojure支持所有的常见数据类型比如 booleans (<code>true</code> and <code>false</code>), 数字, 高精度浮点数, 字符(上面表格里面提到过 ) 以及字符串. 同时还支持分数 --- 不是浮点数,因此在计算的过程中不会损失精度.</p>
<p>Symbols是用来给东西命名的. 这些名字是被限制在名字空间里面的,要么是指定的名字空间,要么是当前的名字空间. Symbols的值是它所代表的名字的值. 要使用Symbol的值,你必须把它用引号引起来.</p>
<p>关键字以冒号打头,被用来当作唯一标示符,通常用在map里面 (比如<code>:red</code>, <code>:green</code>和 <code>:blue</code>).</p>
<p>和任何语言一样,你可以写出很难懂的Clojure代码。遵循一些最佳实践可以避免这个。写一些简短的,专注自己功能的函数可以使函数变得容易读,测试以及重复利用。经常使用“抽取方法”的模式来对你的代码进行重构。高度内嵌的函数是非常难懂得,千万不要这么写, 你可以使用let来帮助你。把匿名函数传递给命名函数是非常常见的,但是不要把一个匿名函数传递给另外一个匿名函数, 这样代码就很难懂了。</p>
<em><a href="#contents">回到上面</a></em>
<h2><a name="repl">REPL</a></h2>
<p>REPL 是read-eval-print loop的缩写. 这是Lisp的方言提供给用户的一个标准交互方式,如果用过python的人应该用过这个,你输入一个表达式,它立马再给你输出结果,你再输入。。。如此循环。这是一个非常有用的学习语言,测试一些特性的工具。</p>
<p>为了启动REPL, 运行我们上面写好的clj脚本。成功的话会显示一个"<code>user=></code>". "<code>=></code>" 前面的字符串表示当前的默认名字空间。“=>"后面的则是你输入的form以及它的输出结果。 下面是个简单的例子:</p>
<div class="code">
<pre>user=> (def n 2)
#'user/n
user=> (* n 3)
6</pre>
</div>
<p><code>def</code> 是一个 special form, 它相当于java里面的定义加赋值语句. 它的输出表示一个名字叫 "<code>n</code>" 的symbol被定义在当前的名字空间 "<code>user</code>" 里面。</p>
<p>要查看一个函数,宏或者名字空间的文档输入<code>(doc <em>name</em>)</code>。看下面的例子:</p>
<div class="code">
<pre>(require 'clojure.contrib.str-utils)
(doc clojure.contrib.str-utils/str-join) ; ->
; -------------------------
; clojure.contrib.str-utils/str-join
; ([separator sequence])
; Returns a string of all elements in 'sequence', separated by
; 'separator'. Like Perl's 'join'.</pre>
</div>
<p>如果要找所有包含某个字符串的所有的函数的,宏的文档,那么输入这个命令<code>(find-doc "<em>text</em>")</code>.</code>
<p>如果要查看一个函数,宏的源代码<code>(source <em>name</em>)</code>. <code>source</code> 是一个定义在<code>clojure.contrib.repl-utils</code> 名字空间里面的宏,REPL会自动加载这个宏的。</p>
<p>如果要加载并且执行文件里面的clojure代码那么使用这个命令<code>(load-file "<em>file-path</em>")</code>. Clojure源文件一般以.clj作为后缀。</p>
<p>如果要退出REPL,在Windows下面输出ctrl-z然后回车, 或者直接 ctrl-c; 在其它平台下 (包括UNIX, Linux 和 Mac OS X), 输入 ctrl-d.</p>
<em><a href="#contents">回到上面</a></em>
<h2><a name="bindings">Bindings</a></h2>
<p>Clojure里面是不支持变量的。它跟变量有点像,但是在被赋值之后是不允许改的,包括:全局binding, 线程本地(thread local)binding, 以及函数内的本地binding, 以及一个表达式内部的binding。</p>
<p><code>def</code> 这个special form 定义一个全局的 binding,并且你还可以给它一个"root value" ,这个root value在所有的线程里面都是可见的,除非你给它赋了一个线程本地的值.<code>def</code> 也可以用来改变一个已经存在的binding的root value ------ 但是这是不被鼓励的,因为这会牺牲不可变数据所带来的好处。</p>
<p>函数的参数是只在这个函数内可见的本地binding。</p>
<p><code>let</code> 这个special form 创建局限于一个当前form的bindings. 它的第一个参数是一个vector, 里面包含名字-表达式的对子。表达式的值会被解析然后赋给左边的名字。这些binding可以在这个vector后面的表达式里面使用。这些binding还可以被多次赋值以改变它们的值,let命令剩下的参数是一些利用这个binding来进行计算的一些表达式。注意:如果这些表达式里面有调用别的函数,那么这个函数是无法利用let创建的这个binding的。</p>
<p><span style="line-height: 6px;">宏</span> <code>binding</code> 跟<code>let</code>类似, 但是它创建的本地binding会暂时地覆盖已经存在的全局binding. 这个binding可以在创建这个binding的form以及这个form里面调用的函数里面都能看到。但是一旦跳出了这个<code>binding</code> 那么被覆盖的全局binding的值会回复到之前的状态。</p>
<p>从 Clojure 1.3开始, binding只能用在动态变量(dynamic var)上面了. 下面的例子演示了怎么定一个dynamic var。另一个区别是<code>let</code> 是串行的赋值的, 所以后面的binding可以用前面binding的值, 而<code>binding</code> 是不行的.</p>
<p>要被用来定义成新的、本地线程的、用binding来定义的binding有它们自己的命名方式:她们以星号开始,以星号结束。在这篇文章里面你会看到:<code>*command-line-args*</code>,<code>*agent*</code>,<code>*err*</code>,<code>*flush-on-newline*</code>,<code>*in*</code>,<code>*load-tests*</code>,<code>*ns*</code>,<code>*out*</code>,<code>*print-length*</code>,<code>*print-level*</code> and<code>*stack-trace-depth*</code>.要使用这些binding的函数会被这些binding的值影响的。比如给*out*一个新的binding会改变println函数的输出终端。</p>
<p>下面的例子介绍了<code>def</code>,<code>let</code> 和<code>binding</code>的用法。</p>
<div class="code">
<pre>(def ^:dynamic v 1) ; v is a global binding
(defn f1 []
(println "f1: v =" v)) ; global binding
(defn f2 []
(println "f2: before let v =" v) ; global binding
(let [v 2] ; creates local binding v that shadows global one
(println "f2: in let, v =" v) ; local binding
(f1))
(println "f2: after let v =" v)) ; global binding
(defn f3 []
(println "f3: before binding v =" v) ; global binding
(binding [v 3] ; same global binding with new, temporary value
(println "f3: in binding, v =" v) ; global binding
(f1))
(println "f3: after binding v =" v)) ; global binding
(defn f4 []
(def v 4)) ; changes the value of the global binding
(f2)
(f3)
(f4)
(println "after calling f4, v =" v)</pre>
</div>
<p>上面代码的输出是这样的:</p>
<div class="code">
<pre>f2: before let v = 1
f2: in let, v = 2
f1: v = 1 (let DID NOT change value of global binding)
f2: after let v = 1
f3: before binding v = 1
f3: in binding, v = 3
f1: v = 3 (binding DID change value of global binding)
f3: after binding v = 1 (value of global binding reverted back)
after calling f4, v = 4</pre>
</div>
<em><a href="#contents">回到上面</a></em>
<h2><a name="collections">集合</a></h2>
<p>Clojure提供这些集合类型: list, vector, set, map。同时Clojure还可以使用Java里面提供的将所有的集合类型,但是通常不会这样做的, 因为Clojure自带的集合类型更适合函数式编程。</p>
<p>Clojure集合有着java集合所不具备的一些特性。所有的clojure集合是不可修改的、异源的以及持久的。不可修改的意味着一旦一个集合产生之后,你不能从集合里面删除一个元素,也不能向集合里面添加一个元素。异源意味着一个集合里面可以装进任何东西(而不必须要这些东西的类型一样)。持久的意味着当一个集合新的版本产生之后,旧的版本还是在的。CLojure以一种非常高效的,共享内存的方式来实现这个的。比如有一个map里面有一千个name-valuea pair, 现在要往map里面加一个,那么对于那些没有变化的元素, 新的map会共享旧的map的内存,而只需要添加一个新的元素所占用的内存。</p>
<p>有很多核心的函数可以用来操作所有这些类型的集合。多得以至于无法在这里全部描述。其中的一小部分我们会在下面介绍vector的时候介绍一下。要记住的是,因为clojure里面的集合是不可修改的,所以也就没有对集合进行修改的函数。相反clojure里面提供了一些函数来从一个已有的集合来高效地创建新的集合 -- 使用<a href="http://en.wikipedia.org/wiki/Persistent_data_structure">persistent data structures</a>。同时也有一些函数操作一个已有的集合(比如vector)来产生另外一种类型的集合(比如LazySeq), 这些函数有不同的特性。</p>
<p>提醒: 这一节里面介绍的Clojure集合对于学习clojure来说是非常的重要。但是这里介绍一个函数接着一个函数,所以你如果觉得有点烦,有点乏味,你可以跳过,等用到的时候再回过头来查询。</p>
<p><code>count</code> 返回集合里面的元素个数,比如:</p>
<div class="code">
<pre>(count [19 "yellow" true]) ; -> 3</pre>
</div>
<p><code>conj</code> 函数是 conjoin的缩写, 添加一个元素到集合里面去,到底添加到什么位置那就取决于具体的集合了,我们会在下面介绍具体集合的时候再讲。</p>
<p><code>reverse</code> 把集合里面的元素反转。</p>
<div class="code">
<pre>(reverse [2 4 7]) ; -> (7 4 2)</pre>
</div>
<p><code>map</code> 对一个给定的集合里面的每一个元素调用一个指定的方法,然后这些方法的所有返回值构成一个新的集合(LazySeq)返回。这个指定了函数也可以有多个参数,那么你就需要给map多个集合了。如果这些给的集合的个数不一样,那么执行这个函数的次数取决于个数最少的集合的长度。比如:</p>
<div class="code">
<pre>; The next line uses an anonymous function that adds 3 to its argument.
(map #(+ % 3) [2 4 7]) ; -> (5 7 10)
(map + [2 4 7] [5 6] [1 2 3 4]) ; adds corresponding items -> (8 12)</pre>
</div>
<p><code>apply</code> 把给定的集合里面的所有元素一次性地给指定的函数作为参数调用,然后返回这个函数的返回值。所以apply与map的区别就是map返回的还是一个集合,而apply返回的是一个元素, 可以把apply看作是SQL里面的聚合函数。比如:</p>
<div class="code">
<pre>(apply + [2 4 7]); -> 13</pre>
</div>
<p>有很多函数从一个集合里面获取一个元素,比如:
<div>
<pre>(def stooges ["Moe" "Larry" "Curly" "Shemp"])
(first stooges) ; -> "Moe"
(second stooges) ; -> "Larry"
(last stooges) ; -> "Shemp"
(nth stooges 2) ; indexes start at 0 -> "Curly"</pre>
</div>
<p>也有一些函数从一个集合里面获取多个元素,比如:</p>
<div class="code">
<pre>(next stooges) ; -> ("Larry" "Curly" "Shemp")
(butlast stooges) ; -> ("Moe" "Larry" "Curly")
(drop-last 2 stooges) ; -> ("Moe" "Larry")
; Get names containing more than three characters.
(filter #(> (count %) 3) stooges) ; -> ("Larry" "Curly" "Shemp")
(nthnext stooges 2) ; -> ("Curly" "Shemp")</pre>
</div>
<p>有一些谓词函数测试集合里面每一个元素然后返回一个布尔值,这些函数都是"short-circuit"的,一旦它们的返回值能确定它们就不再继续测试剩下的元素了,有点像java的<code>and</code>和<code>or</code>, 比如:</p>
<div class="code">
<pre>(every? #(instance? String %) stooges) ; -> true
(not-every? #(instance? String %) stooges) ; -> false
(some #(instance? Number %) stooges) ; -> nil
(not-any? #(instance? Number %) stooges) ; -> true</pre>
</div>
<h3><a name="Lists">Lists</a></h3>
<p>Lists是一个有序的元素的集合 -- 相当于java里面的LinkedList。这种集合对于那种一直要往最前面加一个元素,干掉最前面一个元素是非常高效的(O(1)) -- 想到于java里面的堆栈, 但是没有高效的方法来获取第N个元素, 也没有高效的办法来修改第N个元素。</p>
<p>下面是创建同样的list的多种不同的方法:</p>
<div class="code">
<pre>(def stooges (list "Moe" "Larry" "Curly"))
(def stooges (quote ("Moe" "Larry" "Curly")))
(def stooges '("Moe" "Larry" "Curly"))</pre>
</div>
<p><code>some</code> 可以用来检测一个集合是否含有某个元素. 它的参数包括一个谓词函数以及一个集合。你可以能会想了,为了要看一个list到底有没有某个元素为什么要指定一个谓词函数呢?其实我们是故意这么做来让你尽量不要这么用的。从一个list里面搜索一个元素是线性的操作(不高效),而要从一个set里面搜索一个元素就容易也高效多了,看下面的例子对比:</p>
<div class="code">
<pre>(some #(= % "Moe") stooges) ; -> true
(some #(= % "Mark") stooges) ; -> nil
; Another approach is to create a set from the list
; and then use the contains? function on the set as follows.
(contains? (set stooges) "Moe") ; -> true</pre>
</div>
<p><code>conj</code> 和<code>cons</code> 函数的作用都是通过一个已有的集合来创建一个新的包含更多元素的集合 -- 新加的元素在最前面。<code>remove</code> 函数创建一个只包含所指定的谓词函数测试结果为false的元素的集合:</p>
<div class="code">
<pre>(def more-stooges (conj stooges "Shemp")) -> ("Shemp" "Moe" "Larry" "Curly")
(def less-stooges (remove #(= % "Curly") more-stooges)) ; -> ("Shemp" "Moe" "Larry")</pre>
</div>
<p><code>into</code> 函数把两个list里面的元素合并成一个新的大list</p>
<div class="code">
<pre>(def kids-of-mike '("Greg" "Peter" "Bobby"))
(def kids-of-carol '("Marcia" "Jan" "Cindy"))
(def brady-bunch (into kids-of-mike kids-of-carol))
(println brady-bunch) ; -> (Cindy Jan Marcia Greg Peter Bobby)</pre>
</div>
<p><code>peek</code> 和<code>pop</code> 可以用来把list当作一个堆栈来操作. 她们操作的都是list的第一个元素。</p>
<h3><a name="Vectors">Vectors</a></h3>
<p>Vectors也是一种有序的集合。这种集合对于从最后面删除一个元素,或者获取最后面一个元素是非常高效的(O(1))。这意味着对于向vector里面添加元素使用conj被使用cons更高效。Vector对于以索引的方式访问某个元素(用nth命令)或者修改某个元素(用assoc)来说非常的高效。函数定义的时候指定参数列表用的就是vector。</p>
<p>下面是两种创建vector的方法:</p>
<div class="code">
<pre>(def stooges (vector "Moe" "Larry" "Curly"))
(def stooges ["Moe" "Larry" "Curly"])</pre>
</div>
<p>除非你要写的程序要特别用到list的从前面添加/删除效率很高的这个特性, 否则一般来说我们鼓励你们用vector而不是lists。这主要是因为语法上<code>[...]</code> 比 '<code>(...) </code>更自然,更不容易弄混淆。因为函数,宏以及special form的语法也是<code>(...)</code>。</p>
<p><code>get</code> 获取vector里面指定索引的元素. 我们后面会看到get也可以从map里面获取指定key的value。索引是从0开始的。<code>get</code> 函数和函数<code>nth</code> 类似. 它们都接收一个可选的默认值参数 -- 如果给定的索引超出边界,那么会返回这个默认值。如果没有指定默认值而索引又超出边界了,<code>get</code> 函数会返回<code>nil</code> 而<code>nth</code> 会抛出一个异常. 看例子:</p>
<div class="code">
<pre>(get stooges 1 "unknown") ; -> "Larry"
(get stooges 3 "unknown") ; -> "unknown"</pre>
</div>
<p><code>assoc</code> 可以对 vectors 和 maps进行操作。 当用在 vector上的时候, 它会从给定的vector创建一个新的vector, 而指定的那个索引所对应的元素被替换掉。如果指定的这个索引等于vector里面元素的数目,那么我们会把这个元素加到新vector的最后面去;如果指定的索引比vector的大小要大,那么一个<code>IndexOutOfBoundsException</code> 异常会被抛出来。看代码:</p>
<div class="code">
<pre>(assoc stooges 2 "Shemp") ; -> ["Moe" "Larry" "Shemp"]</pre>
</div>
<p><code>subvec</code> 获取一个给定vector的子vector。它接受三个参数,一个vectore, 一个起始索引以及一个可选的结束索引。如果结束索引没有指定,那么默认的结束索引就是vector的大小。新的vector和原来的vector共享内存(所以高效)。</p>
<p>所有上面的对于list的例子代码对于vector同样适用。<code>peek</code> 和<code>pop</code> 函数对于vector同样适用, 只是它们操作的是vector的最后一个元素,而对于list操作的则是第一个元素。<code>conj</code> 函数从一个给定的vector创建一个新的vector -- 添加一个元素到新的vector的最后面去. <code>cons</code> 函数从一个给定的vector创建一个新的vector -- 添加一个新的元素到vector的最前面去。</p>
<h3><a name="Sets">Sets</a></h3>
<p>Sets 是一个包含不重复元素的集合。当我们要求集合里面的元素不可以重复,并且我们不要求集合里面的元素保持它们添加时候的顺序,那么sets是比较适合的。 Clojure 支持两种不同的set: 排序的和不排序的。如果添加到set里面的元素相互之间不能比较大小,那么一个<code>ClassCastException</code> 异常会被抛出来。下面是一些创建set的方法:</p>
<div class="code">
<pre>(def stooges (hash-set "Moe" "Larry" "Curly")) ; not sorted
(def stooges #{"Moe" "Larry" "Curly"}) ; same as previous
(def stooges (sorted-set "Moe" "Larry" "Curly"))</pre>
</div>
<p><code>contains?</code> 函数可以操作sets和maps. 当操作set的时候, 它返回给定的set是否包含某个元素。这比在list和vector上面使用的<code>some函数就简单多了</code>. 看例子:</p>
<div class="code">
<pre>(contains? stooges "Moe") ; -> true
(contains? stooges "Mark") ; -> false</pre>
</div>
<p>Sets 可以被当作它里面的元素的函数来使用. 当以这种方式来用的时候,返回值要么是这个元素,要么是nil. 这个比起contains?函数来说更简洁. 比如:
<div class="code">
<pre>(stooges "Moe") ; -> "Moe"
(stooges "Mark") ; -> nil
(println (if (stooges person) "stooge" "regular person"))</pre>
</div>
<p>在介绍list的时候提到的函数<code>conj</code> 和<code>into</code> 对于set也同样适用. 只是元素的顺序只有对sorted-set才有定义.</p>
<p><code>disj</code> 函数通过去掉给定的set里面的一些元素来创建一个新的set. 看例子:</p>
<div class="code">
<pre>(def more-stooges (conj stooges "Shemp")) ; -> #{"Moe" "Larry" "Curly" "Shemp"}
(def less-stooges (disj more-stooges "Curly")) ; -> #{"Moe" "Larry" "Shemp"}</pre>
</div>
<p>你也可以看看<code>clojure.set</code> 名字空间里面的一些函数:<code>difference</code>,<code>index</code>,<code>intersection</code>,<code>join</code>,<code>map-invert</code>,<code>project</code>,<code>rename</code>,<code>rename-keys</code>,<code>select</code> 和<code>union</code>. 其中有些函数的操作的对象是map而不是set。</p>
<h3><a name="Maps">Maps</a></h3>
<p>Maps 保存从key到value的a对应关系 --- key和value都可以是任意对象。key-value 组合被以一种可以按照key的顺序高效获取的方式保存着。</p>
<p>下面是创建map的一些方法, 其中逗号是为了提高可读性的,它是可选的,解析的时候会被当作空格忽略掉的。</p>
<div clas="code">
<pre>(def popsicle-map
(hash-map :red :cherry, :green :apple, :purple :grape))
(def popsicle-map
{:red :cherry, :green :apple, :purple :grape}) ; same as previous
(def popsicle-map
(sorted-map :red :cherry, :green :apple, :purple :grape))</pre>
</div>
<p>map可以作为它的key的函数,同时如果key是keyword的话,那么key也可以作为map的函数。下面是三种获取:green所对应的值的方法:</p>
<div class="code">
<pre>(get popsicle-map :green)
(popsicle-map :green)
(:green popsicle-map)</pre>
</div>
<p><code>contains?</code> 方法可以操作 sets 和 maps. 当被用在map上的时候,它返回map是否包含给定的key. <code>keys</code> 函数返回map里面的所有的key的集合. <code>vals</code> 函数返回map里面所有值的集合. 看例子:</p>
<div class="code">
<pre>(contains? popsicle-map :green) ; -> true
(keys popsicle-map) ; -> (:red :green :purple)
(vals popsicle-map) ; -> (:cherry :apple :grape)</pre>
</div>
<p><code>assoc</code> 函数可以操作 maps 和 vectors. 当被用在map上的时候,它会创建一个新的map, 同时添加任意对新的name-value pair, 如果某个给定的key已经存在了,那么它的值会被更新。看例子:</p>
<div class="code">
<pre>(assoc popsicle-map :green :lime :blue :blueberry)
; -> {:blue :blueberry, :green :lime, :purple :grape, :red :cherry}</pre>
</div>
<p><code>dissoc</code> 创建一个新的map, 同时忽略掉给定的那么些key, 看例子:</p>
<div class="code">
<pre>(dissoc popsicle-map :green :blue) ; -> {:purple :grape, :red :cherry}</pre>
</div>
<p>我们也可以把map看成一个简单的集合,集合里面的每个元素是一个pair: name-value:<code>clojure.lang.MapEntry</code> 对象. 这样就可以和<a href="#ListComprehension">doseq</a> 跟<a href="#destructuring">destructuring</a>一起使用了, 它们的作用都是更简单地来遍历map, 我们会在后面详细地介绍这些函数. 下面的这个例子会遍历<code>popsicle-map</code> 里面的所有元素,把key bind到<code>color, </code>把value bind到<code>flavor。</code> <code>name函数返回一个keyword的字符串名字。</code></p>
<div class="code">
<pre>(doseq [[color flavor] popsicle-map]
(println (str "The flavor of " (name color)
" popsicles is " (name flavor) ".")))</pre>
</div>
<p>上面的代码的输出是这样的:</p>
<div class="code">
<pre>The flavor of green popsicles is apple.
The flavor of purple popsicles is grape.
The flavor of red popsicles is cherry.</pre>
</div>
<p><code>select-keys</code> 函数接收一个map对象,以及一个key的集合的参数,它返回这个集合里面key在那个集合里面的一个子map。看例子:</p>
<div class="code">
<pre>(select-keys popsicle-map [:red :green :blue]) ; -> {:green :apple, :red :cherry}</pre>
</div>
<p><code>conj</code> 函数添加一个map里面的所有元素到另外一个map里面去。如果目标map里面的key在源map里面也有,那么目标map的值会被更新成源map里面的值。</p>
<p>map里面的值也可以是一个map, 而且这样嵌套无限层。获取嵌套的值是非常简单的。同样的,更新一个嵌套的值也是很简单的。为了证明这个, 我们会创建一个描述人(person)的map。其中内嵌了一个表示人的地址的map,同时还有一个叫做employer的内嵌map。</p>
<div class="code">
<pre>(def person {
:name "Mark Volkmann"
:address {
:street "644 Glen Summit"
:city "St. Charles"
:state "Missouri"
:zip 63304}
:employer {
:name "Object Computing, Inc."
:address {
:street "12140 Woodcrest Executive Drive, Suite 250"
:city "Creve Coeur"
:state "Missouri"
:zip 63141}}})</pre>
</div>
<p><code>get-in</code> 函数、宏<code>-></code> 以及函数<code>reduce</code> 都可以用来获得内嵌的key. 下面展示了三种获取这个人的employer的address的city的值的方法:</p>
<div class="code">
<pre>(get-in person [:employer :address :city])
(-> person :employer :address :city) ; explained below
(reduce get person [:employer :address :city]) ; explained below</pre>
</div>
<p>宏<code>-></code> 我们也称为 "thread" 宏, 它本质上是调用一系列的函数,前一个函数的返回值作为后一个函数的参数. 比如下面两行代码的作用是一样的:</p>
<div class="code">
<pre>(f1 (f2 (f3 x)))
(-> x f3 f2 f1)</pre>
</div>
<p>在名字空间<code>clojure.contrib.core</code> 里面还有个<code>-?></code>宏, 它会马上返回nil, 如果它的调用链上的任何一个函数返回nil (short-circiut)。这会避免抛出<code>NullPointerException</code>异常。</p>
<p><code>reduce</code> 函数接收一个需要两个参数的函数, 一个可选的value以及一个集合。它会以value以及集合的第一个元素作为参数来调用给定的函数(如果指定了value的话), 要么以集合的第一个元素以及第二个元素为参数来调用给定的函数(如果没有指定value的话)。接着就以这个返回值以及集合里面的下一个元素为参数来调用给定的函数,知道集合里面的元素都被计算了 -- 最后返回一个值. 这个函数与ruby里面的<code>inject</code> 以及Haskell里面的<code>foldl</code> 作用是一样的。</p>
<p><code>assoc-in</code> 函数可以用来修改一个内嵌的key的值。看下面的例子把person的employer->address->city修改成Clayton了。</p>
<div class="code">
<pre>(assoc-in person [:employer :address :city] "Clayton")</pre>
</div>
<p><code>update-in</code> 函数也是用来更新给定的内嵌的key对应的值,只是这个新值是通过一个给定的函数来计算出来。下面的例子里面会把person的employer->address->zip改成旧的zip + "-1234"。看例子:</p>
<div class="code">
<pre>(update-in person [:employer :address :zip] str "-1234") ; using the str function</pre>
</div>
<em><a href="#contents">回到上面</a></em>
<h2><a name="struct-maps">StructMaps</a></h2>
<p>StructMap和普通的map类似,它的作用其实是用来模拟java里面的javabean, 所以它比普通的map的优点就是,它把一些常用的字段抽象到一个map里面去,这样你就不用一遍一遍的重复了。并且和java类似,他会帮你生成合适的<code>equals</code> 和<code>hashCode</code> 方法。并且它还提供方式让你可以创建比普通map里面的hash查找要快的字段访问方法(javabean里面的getXXX方法)。</p>
<p><code>create-struct</code> 函数和<code>defstruct</code> 宏都可以用来定义StructMap, defstruct内部调用的也是<code>create-struct</code>。map的key通常都是用keyword来指定的。看例子:</p>
<div class="code">
<pre>(def vehicle-struct (create-struct :make :model :year :color)) ; long way
(defstruct vehicle-struct :make :model :year :color) ; short way</pre>
</div>
<p><code>struct</code> 实例化StructMap的一个对象,相当于java里面的new关键字. 你提供给struct的参数的顺序必须和你定义的时候提供的keyword的顺序一致,后面的参数可以忽略, 如果忽略,那么对应key的值就是nil。看例子:</p>
<div class="code">
<pre>(def vehicle (struct vehicle-struct "Toyota" "Prius" 2009))</pre>
</div>
<p><code>accessor</code> 函数可以创建一个类似java里面的getXXX的方法, 它的好处是可以避免hash查找, 它比普通的hash查找要快。看例子:</p>
<div class="code">
<pre>; Note the use of def instead of defn because accessor returns
; a function that is then bound to "make".
(def make (accessor vehicle-struct :make))
(make vehicle) ; -> "Toyota"
(vehicle :make) ; same but slower
(:make vehicle) ; same but slower</pre>
</div>
<p>在创建一个StructMap之后, 你还可以给它添加在定义struct的时候没有指定的key。但是你不能删除定义时候已经指定的key。</p>
<em><a href="#contents">回到上面</a></em>
<h2><a name="defining-functions">定义函数</a></h2>
<p><code>defn</code> 宏用来定义一个函数。它的参数包括一个函数名字,一个可选的注释字符串,参数列表,然后一个方法体。而函数的返回值则是方法体里面最后一个表达式的值。所有的函数都会返回一个值, 只是有的返回的值是nil。看例子:</p>
<div class="code">
<pre>(defn parting
"returns a String parting"
[name]
(str "Goodbye, " name)) ; concatenation
(println (parting "Mark")) ; -> Goodbye, Mark</pre>
</div>
<p>函数必须先定义再使用。有时候可能做不到, 比如两个方法项目调用,clojure采用了和C语言里面类似的做法: declare, 看例子:</p>
<div class="code">
<pre>(declare <em>function-names</em>)</pre>
</div>
<p>通过宏<code>defn-</code> 定义的函数是私有的. 这意味着它们只在定义它们的名字空间里面可见. 其它一些类似定义私有函数/宏的还有:<code>defmacro-</code> 和<code>defstruct-</code> (在<code>clojure.contrib.def</code>里面)。</p>
<p>函数的参数个数可以是不定的。可选的那些参数必须放在最后面(这一点跟其它语言是一样的), 你可以通过加个&符号把它们收集到一个list里面去。</p>
<div class="code">
<pre>(defn power [base & exponents]
; Using java.lang.Math static method pow.
(reduce #(Math/pow %1 %2) base exponents))
(power 2 3 4) ; 2 to the 3rd = 8; 8 to the 4th = 4096</pre>
</div>
<p>函数定义可以包含多个参数列表以及对应的方法体。每个参数列表必须包含不同个数的参数。这通常用来给一些参数指定默认值。看例子:</p>
<div class="code">
<pre>(defn parting
"returns a String parting in a given language"
([] (parting "World"))
([name] (parting name "en"))
([name language]
; condp is similar to a case statement in other languages.
; It is described in more detail later.
; It is used here to take different actions based on whether the
; parameter "language" is set to "en", "es" or something else.
(condp = language
"en" (str "Goodbye, " name)
"es" (str "Adios, " name)
(throw (IllegalArgumentException.
(str "unsupported language " language))))))
(println (parting)) ; -> Goodbye, World
(println (parting "Mark")) ; -> Goodbye, Mark
(println (parting "Mark" "es")) ; -> Adios, Mark
(println (parting "Mark", "xy"))
; -> java.lang.IllegalArgumentException: unsupported language xy</pre>
</div>
<p>匿名函数是没有名字的。他们通常被当作参数传递给其他有名函数(相对于匿名函数)。匿名函数对于那些只在一个地方使用的函数比较有用。下面是定义匿名函数的两种方法:</p>
<div class="code">
<pre>(def years [1940 1944 1961 1985 1987])
(filter (fn [year] (even? year)) years) ; long way w/ named arguments -> (1940 1944)
(filter #(even? %) years) ; short way where % refers to the argument</pre>
</div>
<p>通过<code>fn</code> 定义的匿名函数可以包含任意个数的表达式; 而通过<code>#(...)</code>, 定义的匿名函数则只能包含一个表达式,如果你想包含多个表达式,那么把它用<code>do</code>包起来。如果只有一个参数, 那么你可以通过<code>%</code>来引用它; 如果有多个参数, 那么可以通过<code>%1</code>,<code>%2</code> 等等来引用。 看例子:</p>
<div class="code">
<pre>(defn pair-test [test-fn n1 n2]
(if (test-fn n1 n2) "pass" "fail"))
; Use a test-fn that determines whether
; the sum of its two arguments is an even number.
(println (pair-test #(even? (+ %1 %2)) 3 5)) ; -> pass</pre>
</div>
<p>Java里面的方法可以根据参数的类型来进行重载。而Clojure里面则只能根据参数的个数来进行重载。不过Clojure里面的multimethods技术可以实现任意 类型的重载。</p>
<p>宏<code>defmulti</code> 和<code>defmethod</code> 经常被用在一起来定义 multimethod. 宏<code>defmulti</code> 的参数包括一个方法名以及一个dispatch函数,这个dispatch函数的返回值会被用来选择到底调用哪个重载的函数。宏<code>defmethod</code> 的参数则包括方法名,dispatch的值, 参数列表以及方法体。一个特殊的dispatch值<code>:default</code> 是用来表示默认情况的 -- 即如果其它的dispatch值都不匹配的话,那么就调用这个方法。<code>defmethod</code> 多定义的名字一样的方法,它们的参数个数必须一样。传给multimethod的参数会传给dipatch函数的。</code></p>
<p>下面是一个用multimethod来实现基于参数的类型来进行重载的例子:</p>
<div class="code">
<pre>(defmulti what-am-i class) ; class is the dispatch function
(defmethod what-am-i Number [arg] (println arg "is a Number"))
(defmethod what-am-i String [arg] (println arg "is a String"))
(defmethod what-am-i :default [arg] (println arg "is something else"))
(what-am-i 19) ; -> 19 is a Number
(what-am-i "Hello") ; -> Hello is a String
(what-am-i true) ; -> true is something else</pre>
</div>
<p>因为dispatch函数可以是任意一个函数,所以你也可以写你自己的dispatch函数。比如一个自定义的dispatch函数可以会根据一个东西的尺寸大小来返回<code>:small</code>,<code>:medium</code> 以及<code>:large</code>。然后对应每种尺寸有一个方法。</p>
<p>下划线可以用来作为参数占位符 -- 如果你不要使用这个参数的话。这个特性在回调函数里面比较有用, 因为回调函数的设计者通常想把尽可能多的信息给你, 而你通常可能只需要其中的一部分。看例子:</p>
<div class="code">
<pre>(defn callback1 [n1 n2 n3] (+ n1 n2 n3)) ; uses all three arguments
(defn callback2 [n1 _ n3] (+ n1 n3)) ; only uses 1st & 3rd arguments
(defn caller [callback value]
(callback (+ value 1) (+ value 2) (+ value 3)))
(caller callback1 10) ; 11 + 12 + 13 -> 36
(caller callback2 10) ; 11 + 13 -> 24</pre>
</div>
<p><code>complement</code> 函数接受一个函数作为参数,如果这个参数返回值是true, 那么它就返回false, 相当于一个取反的操作。看例子:</p>
<div class="code">
<pre>(defn teenager? [age] (and (>= age 13) (< age 20)))
(def non-teen? (complement teenager?))
(println (non-teen? 47)) ; -> true</pre>
</div>
<p><code>comp</code>把任意多个函数组合成一个,前面一个函数的返回值作为后面一个函数的参数。<strong>调用的顺序是从右到左(注意不是从左到右)</strong>看例子:</p>
<div class="code">
<pre>(defn times2 [n] (* n 2))
(defn minus3 [n] (- n 3))
; Note the use of def instead of defn because comp returns
; a function that is then bound to "my-composition".
(def my-composition (comp minus3 times2))
(my-composition 4) ; 4*2 - 3 -> 5</pre>
</div>
<p><code>partial</code> 函数创建一个新的函数 -- 通过给旧的函数制定一个初始值, 然后再调用原来的函数。比如<code>*</code> 是一个可以接受多个参数的函数,它的作用就是计算它们的乘积,如果我们想要一个新的函数,使的返回结果始终是乘积的2倍,我们可以这样做:</p>
<div class="code">
<pre>; Note the use of def instead of defn because partial returns
; a function that is then bound to "times2".
(def times2 (partial * 2))
(times2 3 4) ; 2 * 3 * 4 -> 24</pre>
</div>
<p>下面是一个使用<code>map</code> 和<code>partial</code> 的有趣的例子.</p>