-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy path5language-advanced.re
4238 lines (3090 loc) · 191 KB
/
5language-advanced.re
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
= ClojureScript 発展編
#@# Language Advanced
#@# Page103
== Transducer
=== データの変換
#@# Data transformation
#@# ClojureScript offers a rich vocabulary for data transformation in terms of the sequence abstraction, which makes such transformations highly general and composable. Let's see how we can combine several collection processing functions to build new ones. We will be using a simple problem throughout this section: splitting grape clusters, filtering out the rotten ones, and cleaning them. We have a collection of grape clusters like the following:
ClojureScript では、シーケンス抽象化の観点から、データ変換のための豊富な語彙が用意されています。コレクション処理の関数を組み合わせて新しい関数を作成する方法を見てみましょう。この章では、ブドウの房(cluster)を分け、腐った(rotten)房を分別して、きれいにするという単純な問題を扱います。次のようなブドウの房のコレクションがあります。
//emlist{
(def grape-clusters
[{:grapes [{:rotten? false :clean? false}
{:rotten? true :clean? false}]
:color :green}
{:grapes [{:rotten? true :clean? false}
{:rotten? false :clean? false}]
:color :black}])
//}
#@# We are interested in splitting the grape clusters into individual grapes, discarding the rotten ones and cleaning the remaining grapes so they are ready for eating. We are well-equipped in ClojureScript for this data transformation task; we could implement it using the familiar map , filter and mapcat functions:
私たちは、ブドウの房を個々のブドウに分け、腐ったものを捨て、残りのブドウを食べられるようにきれいにしようとしています。このデータ変換の作業を行うための装備が ClojureScript には十分に備わっています。おなじみの @<code>{map} 、@<code>{filter}、@<code>{mapcat} 関数を使って実装できます。
//emlist{
(defn split-cluster
[c]
(:grapes c))
(defn not-rotten
[g]
(not (:rotten? g)))
(defn clean-grape
[g]
(assoc g :clean? true))
//}
#@# Page104
#@# @<embed>{|latex|\vspace{-0.4\Cvs\}}
//embed[latex]{
\enlargethispage{0mm}
//}
//embed[latex]{
\clearpage
//}
//emlist{
(->> grape-clusters
(mapcat split-cluster)
(filter not-rotten)
(map clean-grape))
;; => ({rotten? false :clean? true}
;; {:rotten? false :clean? true})
//}
#@# In the above example we succintly solved the problem of selecting and cleaning the grapes, and we can even abstract such transformations by combining the mapcat , filter and map operations using partial application and function composition:
上記の例では、ブドウの選択と洗浄の問題を上手に解決していますが、partial application と 関数合成を使いながら @<code>{mapcat}、@<code>{filter}、@<code>{map} を組み合わせることによって、このような変換を抽象化することもできます。
//emlist{
(def process-clusters
(comp
(partial map clean-grape)
(partial filter not-rotten)
(partial mapcat split-cluster)))
(process-clusters grape-clusters)
;; => ({rotten? false :clean? true}
;; {:rotten? false :clean? true})
//}
#@# The code is very clean, but it has a few problems. For example, each call to map , filter and mapcat consumes and produces a sequence that, although lazy, generates intermediate results that will be discarded. Each sequence is fed to the next step, which also returns a sequence. Wouldn't be great if we could do the transformation in a single transversal of the grape-cluster collection?
コードは非常にきれいですが、いくつか問題があります。たとえば、@<code>{map} @<code>{filter} @<code>{mapcat} の各呼び出しは、遅延はあっても破棄される中間結果を生成するシーケンスを消費して生成します。各シーケンスは次のステップに渡され、シーケンスを返します。ブドウの房のコレクションを1回の横断で変換できるとしたら素晴らしいと思いませんか。
#@# Another problem is that even though our process-clusters function works with any sequence, we can't reuse it with anything that is not a sequence. Imagine that instead of having the grape cluster collection available in memory it is being pushed to us asynchronously in a stream. In that situation we couldn't reuse process-clusters since usually map , filter and mapcat have concrete implementations depending on the type.
@<code>{process-clusters} 関数は、どんなシーケンスにも機能しますが、シーケンス以外には再利用できないことも問題です。ブドウの房のコレクションをメモリ上で利用可能にするのではなく、ストリームの中で非同期にプッシュされることを想像してみてください。このような状況では、@<code>{map} @<code>{filter} @<code>{mapcat} などは、型に対応した具体的な実装があるため、@<code>{process-clusters} 関数を再利用できなかったのです。
=== プロセス変換への一般化
#@# Generalizing to process transformations
#@# The process of mapping, filtering or mapcatting isn't necessarily tied to a concrete type, but we keep reimplementing them for different types. Let's see how we can generalize such processes to be context independent. We'll start by implementing naive versions of map and filter first to see how they work internally:
map filter mapcat を行うプロセスは、必ずしも具体的な型に結び付けられているわけではありませんが、各々の型に対して再実装していきます。文脈に依存しないように、このようなプロセスを一般化する方法を見てみましょう。まず、@<code>{map} と @<code>{filter} の単純なバージョンを実装して、内部でどのように動作するかを見てみましょう。
#@# Page105
#@# @<embed>{|latex|\vspace{-0.4\Cvs\}}
//embed[latex]{
\enlargethispage{0mm}
//}
//embed[latex]{
\clearpage
//}
//emlist{
(defn my-map
[f coll]
(when-let [s (seq coll)]
(cons (f (first s)) (my-map f (rest s)))))
(my-map inc [0 1 2])
;; => (1 2 3)
(defn my-filter
[pred coll]
(when-let [s (seq coll)]
(let [f (first s)
r (rest s)]
(if (pred f)
(cons f (my-filter pred r))
(my-filter pred r)))))
(my-filter odd? [0 1 2])
;; => (1)
//}
@<embed>{|latex|\vspace{-0.5\Cvs\}}
#@# As we can see, they both assume that they receive a seqable and return a sequence. Like many recursive functions they can be implemented in terms of the already familiar reduce function. Note that functions that are given to reduce receive an accumulator and an input and return the next accumulator. We'll call these types of functions reducing functions from now on.
どちらも seqable を受け取り、シーケンスを返すことを前提としています。多くの再帰関数と同様に、これらはおなじみの @<code>{reduce} 関数として実装できます。@<code>{reduce} 関数はアキュムレータと入力を受け取り、次のアキュムレータを返します。今後は、このような関数を reducing 関数(reduce を行う関数) と呼びます。
@<embed>{|latex|\vspace{-0.5\Cvs\}}
//emlist{
(defn my-mapr
[f coll]
;; reducing function
(reduce (fn [acc input]
(conj acc (f input)))
;; initial value
[]
;; collection to reduce
coll))
(my-mapr inc [0 1 2])
;; => [1 2 3]
(defn my-filterr
[pred coll]
;; reducing function
(reduce (fn [acc input]
(if (pred input)
(conj acc input)
acc))
;; initial value
[]
;; collection to reduce
coll))
(my-filterr odd? [0 1 2])
;; => [1]
//}
#@# Page106
#@# @<embed>{|latex|\vspace{-0.4\Cvs\}}
//embed[latex]{
\enlargethispage{15mm}
//}
//embed[latex]{
\clearpage
//}
#@# We've made the previous versions more general since using reduce makes our functions work on any thing that is reducible, not just sequences. However you may have noticed that, even though my-mapr and my-filterr don't know anything about the source ( coll ) they are still tied to the output they produce (a vector) both with the initial value of the reduce ( [] ) and the hardcoded conj operation in the body of the reducing function. We could have accumulated results in another data structure, for example a lazy sequence, but we'd have to rewrite the functions in order to do so.
@<code>{reduce} を使用すると、シーケンスだけでなく、還元可能なあらゆるものに対して関数が動作するようになるので、以前のバージョンはより一般的なものになりました。しかし、@<code>{my-mapr} と @<code>{my-filterr} はソース (coll) について何も知らないにもかかわらず、 @<code>{reduce} の初期値 ( [] ) と @<code>{reduce} 関数の本体でハードコードされた @<code>{conj} 演算によって、出力(ベクタ)と結びついていることに気づかれたかも知れません。結果を別のデータ構造、例えば遅延シーケンスに蓄積することもできますが、そのためには関数を書き直さなければなりません。
#@# How can we make these functions truly generic? They shouldn't know about either the source of inputs they are transforming nor the output that is generated. Have you noticed that conj is just another reducing function? It takes an accumulator and an input and returns another accumulator. So, if we parameterise the reducing function that my-mapr and my-filterr use, they won't know anything about the type of the result they are building. Let's give it a shot:
これらの関数を本当の意味で汎用的にするにはどうすればよいのでしょうか。これらの関数は、変換中の入力のソースや、生成された出力について知るべきではありません。@<code>{conj} は reducing 関数の1つであることにお気づきでしょうか。アキュムレータと入力を受け取り、別のアキュムレータを返します。したがって、@<code>{my-mapr} と @<code>{my-filterr} が使用する reducing 関数をパラメータ化しても、ビルドする結果の型については何も知りません。試してみましょう。
//emlist{
(defn my-mapt
;; function to map over inputs
[f]
;; parameterised reducing function
(fn [rfn]
;; transformed reducing function, now it maps f !
(fn [acc input]
(rfn acc (f input)))))
(def incer (my-mapt inc))
(reduce (incer conj) [] [0 1 2])
;; => [1 2 3]
(defn my-filtert
;; predicate to filter out inputs
[pred]
;; parameterised reducing function
(fn [rfn]
;; transformed reducing function,
;; now it discards values based on pred !
(fn [acc input]
(if (pred input)
(rfn acc input)
acc))))
(def only-odds (my-filtert odd?))
(reduce (only-odds conj) [] [0 1 2])
;; => [1]
//}
#@# That's a lot of higher-order functions so let's break it down for a better understanding of what's going on. We'll examine how my-mapt works step by step. The mechanics are similar for my-filtert , so we'll leave it out for now.
高階関数が多いので、何が起こっているのかを理解するために分解してみましょう。@<code>{my-mapt} がどのように機能するかを段階的に検証していきます。@<code>{my-filtert} の仕組みは似ているので、ここでは省略します。
#@# First of all, my-mapt takes a mapping function; in the example we are giving it inc and getting another function back. Let's replace f with inc to see what we are building:
まず、@<code>{my-mapt} はマッピング関数を取ります。この例では、@<code>{inc} を指定して別の関数を返しています。@<code>{f} を @<code>{inc} に置き換えて、build しているものを確認しましょう。
#@# Page107
//embed[latex]{
\enlargethispage{10mm}
//}
//embed[latex]{
\clearpage
//}
//emlist{
(def incer (my-mapt inc))
;; (fn [rfn]
;; (fn [acc input]
;; (rfn acc (inc input))))
;; ^^^
//}
#@# The resulting function is still parameterised to receive a reducing function to which it will delegate, let's see what happens when we call it with conj :
@<embed>{|latex|\vspace{-0.3\Cvs\}}
結果として得られる関数は、それがデリゲートする reducing 関数を受け取るようにパラメータ化されています。それを cond と一緒に呼んだときに何が起こるか見てみましょう。
@<embed>{|latex|\vspace{-0.3\Cvs\}}
//emlist{
(incer conj)
;; (fn [acc input]
;; (conj acc (inc input)))
;; ^^^^
//}
@<embed>{|latex|\vspace{-0.3\Cvs\}}
#@# We get back a reducing function which uses inc to transform the inputs and the conj reducing function to accumulate the results. In essence, we have defined map as the transformation of a reducing function. The functions that transform one reducing function into another are called transducers in ClojureScript.
入力の変換のために @<code>{inc} 、結果を蓄積するために @<code>{conj} を使用する reducing 関数を取り戻します。本質的に、reducing 関数の変換として map 定義しました。ある reducing 関数を別の reducing 関数に変換する関数のことを ClojureScript では transducers と呼びます。
#@# To ilustrate the generality of transducers, let's use different sources and destinations in our call to reduce :
transducer の一般性を示すために、reduce の呼び出しにおいて、異なるソースと出力先を使用してみましょう。
@<embed>{|latex|\vspace{-0.3\Cvs\}}
//emlist{
(reduce (incer str) "" [0 1 2])
;; => "123"
(reduce (only-odds str) "" '(0 1 2))
;; => "1"
//}
@<embed>{|latex|\vspace{-0.3\Cvs\}}
#@# The transducer versions of map and filter transform a process that carries inputs from a source to a destination but don't know anything about where the inputs come from and where they end up. In their implementation they contain the _essence_ of what they accomplish, independent of context.
@<code>{map} 及び @<code>{filter} の transducer版は、入力をソースから出力先に運ぶプロセスを変換しますが、入力がどこから来てどこで終わるかは知りません。これらの実装においては、文脈に関係なく目的を達成することの本質が含まれています。
#@# Now that we know more about transducers we can try to implement our own version of mapcat . We already have a fundamental piece of it: the map transducer. What mapcat does is map a function over an input and flatten the resulting structure one level. Let's try to implement the catenation part as a transducer:
transducer についての知識が増えたので、独自のバージョンの @<code>{mapcat} を実装できます。私たちはすでにその基本的な部分を持っています。@<code>{map} transducer です。@<code>{mapcat} が行うことは、関数を入力上にマップし、結果の構造を1レベルの平らさにすることです。結合する箇所を transducer として実装してみましょう。
@<embed>{|latex|\vspace{-0.3\Cvs\}}
//emlist{
(defn my-cat
[rfn]
(fn [acc input]
(reduce rfn acc input)))
(reduce (my-cat conj) [] [[0 1 2] [3 4 5]])
;; => [0 1 2 3 4 5]
//}
@<embed>{|latex|\vspace{-0.3\Cvs\}}
#@# Page108
//embed[latex]{
\enlargethispage{10mm}
//}
//embed[latex]{
\clearpage
//}
#@# The my-cat transducer returns a reducing function that catenates its inputs into the accumulator. It does so reducing the input reducible with the rfn reducing function and using the accumulator ( acc ) as the initial value for such reduction. mapcat is simply the composition of map and cat . The order in which transducers are composed may seem backwards but it'll become clear in a moment.
my-cat transducer は、その入力をアキュムレータに結合する reducing 関数を返します。このようにして、rfn 関数で reduce 可能な入力を reduce して、このような reduce のための初期値としてアキュムレータ(acc)を使用します。mapcat は map と cat の合成です。transducer が構成される順序は逆向きに見えるかもしれませんが、この点については後に明らかにします。
//emlist{
(defn my-mapcat
[f]
(comp (my-mapt f) my-cat))
(defn dupe
[x]
[x x])
(def duper (my-mapcat dupe))
(reduce (duper conj) [] [0 1 2])
;; => [0 0 1 1 2 2]
//}
=== ClojureScript core における Transducer
#@# Transducers in ClojureScript core
#@# Some of the ClojureScript core functions like map , filter and mapcat support an arity 1 version that returns a transducer. Let's revisit our definition of process-cluster and define it in terms of transducers:
@<code>{map} @<code>{filter} @<code>{mapcat} のような ClojureScript の core 関数は、transducer を返す 1 バージョンの項数をサポートします。@<code>{process-cluster} の定義を再考して、transducer の観点から定義してみましょう。
//emlist{
(def process-clusters
(comp
(mapcat split-cluster)
(filter not-rotten)
(map clean-grape)))
//}
#@# A few things changed since our previous definition process-clusters . First of all, we are using the transducer-returning versions of mapcat , filter and map instead of partially applying them for working on sequences.
前の @<code>{process-clusters} の定義からいくつかの点が変わりました。まず、シーケンスの処理に部分的に適用する代わりに、transducer を返すバージョンの @<code>{mapcat} @<code>{filter} @<code>{map} を使用します。
#@# Also you may have noticed that the order in which they are composed is reversed, they appear in the order they are executed. Note that all map , filter and mapcat return a transducer. filter transforms the reducing function returned by map , applying the filtering before proceeding; mapcat transforms the reducing function returned by filter , applying the mapping and catenation before proceeding.
合成される順序が逆であり、実行された順序で表示されることにお気づきでしょうか。@<code>{map}、@<code>{filter}、@<code>{mapcat} は全て transducer を返すことに注目してください。@<code>{filter} は @<code>{map} が返す reducing 関数を変換して、次に進む前にフィルタリングを適用します。@<code>{mapcat} は @<code>{filter} が返す reducing 関数を変換して、マッピングと結合を適用してから処理を進めます。
#@# One of the powerful properties of transducers is that they are combined using regular function composition. What's even more elegant is that the composition of various transducers is itself a transducer! This means that our process-cluster is a transducer too, so we have defined a composable and context-independent algorithmic transformation.
transducer の強力な特性の一つは、それらが規則的な関数合成を用いて合成されることです。さらにエレガントなのは、様々な transducer の合成そのものが transducer であることです。これは、定義した @<code>{process-cluster} も transducer であることを意味するので、合成可能で文脈に依存しないアルゴリズムの変換を定義したことになります。
#@# Page109
//embed[latex]{
\enlargethispage{10mm}
//}
//embed[latex]{
\clearpage
//}
#@# Many of the core ClojureScript functions accept a transducer, let's look at some examples with our newly created process-cluster :
ClojureScript の core 関数の多くは transducer を受けとりますが、新しく作成した process-cluster でいくつか例を見てみましょう。
//emlist{
(into [] process-clusters grape-clusters)
;; => [{:rotten? false, :clean? true}
;; {:rotten? false, :clean? true}]
(sequence process-clusters grape-clusters)
;; => ({:rotten? false, :clean? true} {:rotten? false, :clean? true})
(reduce (process-clusters conj) [] grape-clusters)
;; => [{:rotten? false, :clean? true} {:rotten? false, :clean? true}]
//}
#@# Since using reduce with the reducing function returned from a transducer is so common, there is a function for reducing with a transformation called transduce . We can now rewrite the previous call to reduce using transduce :
transducer から返される reducing 関数を用いて @<code>{reduce} を使用することは非常に一般的であるため、transduce と呼ばれる変換(transformation)で reduce を行う関数が存在します。これで、reduce への前の呼び出しを @<code>{transduce} を用いて書き直すことができます。
//emlist{
(transduce process-clusters conj [] grape-clusters)
;; => [{:rotten? false, :clean? true} {:rotten? false, :clean? true}]
//}
=== 初期化(Initialisation)
#@# Initialisation
#@# In the last example we provided an initial value to the transduce function ( [] ) but we can omit it and get the same result:
最後の例では、@<code>{transduce} 関数(@<code>{[]})に初期値を指定しましたが、省略しても同じ結果が得られます。
//emlist{
(transduce process-clusters conj grape-clusters)
;; => [{:rotten? false, :clean? true} {:rotten? false, :clean? true}]
//}
#@# What's going on here? How can transduce know what initial value use as an accumulator when we haven't specified it? Try calling conj without any arguments and see what happens:
何が起こっているのでしょうか。アキュムレータとして使用する初期値を指定していないのに、その値が何であるかを @<code>{transduce} はどのようにして知ることができるのでしょうか。引数なしで @<code>{conj} を呼び出して何が起こるかを確認してみます。
//emlist{
(conj)
;; => []
//}
#@# The conj function has a arity 0 version that returns an empty vector but is not the only reducing function that supports arity 0. Let's explore some others:
@<code>{conj} 関数には空のベクタを返す引数が必要ないバージョンがありますが、これだけが引数なしを許す reducing 関数という訳ではありません。他の関数も見てみましょう。
#@# Page110
//embed[latex]{
\enlargethispage{10mm}
//}
//embed[latex]{
\clearpage
//}
//emlist{
(+)
;; => 0
(*)
;; => 1
(str)
;; => ""
(= identity (comp))
;; => true
//}
#@# The reducing function returned by transducers must support the arity 0 as well, which will typically delegate to the transformed reducing function. There is no sensible implementation of the arity 0 for the transducers we have implemented so far, so we'll simply call the reducing function without arguments. Here's how our modified my-mapt could look like:
transducer によって返される reducing 関数は、同様に引数がない場合をサポートしなければならず、これは通常、変換された reducing 関数にデリゲートされます。これまでに実装した transducer には、引数なし版の実用的な実装はありませんでした。引数なし版で単に reducing 関数を呼び出してみます。修正した my-mapt は以下のようになります。
//emlist{
(defn my-mapt
[f]
(fn [rfn]
(fn
;; arity 0 that delegates to the reducing fn
([] (rfn))
([acc input]
(rfn acc (f input))))))
//}
#@# The call to the arity 0 of the reducing function returned by a transducer will call the arity 0 version of every nested reducing function, eventually calling the outermost reducing function. Let's see an example with our already defined process-clusters transducer:
transducer によって返される reducing 関数の引数なし版の呼び出しは、ネストされたすべての reducing 関数の引数なし版を呼び出し、最終的に最も外側の reducing 関数を呼び出します。定義済みの process-clusters transducer の例を見てみましょう。
//emlist{
((process-clusters conj))
;; => []
//}
#@# The call to the arity 0 flows through the transducer stack, eventually calling (conj) .
引数がない場合の呼び出しは transducer のスタックを経由して、最終的に @<code>{(conj)} を呼び出します。
=== ステートフルな Transducer
#@# So far we've only seen purely functional transducer; they don't have any implicit state and are very predictable. However, there are many data transformation functions that are inherently stateful, like take . take receives a number n of elements to keep and a collection and returns a collection with at most n elements.
ここまでは純粋に関数的な transducer だけを見てきました。つまり、暗黙的な状態を持たず、状態の予測は容易です。しかし、take のような本質的に状態をもつデータを変換する関数が多くあります。take は保持する n 個の要素と 1 つのコレクションを受け取り、最大で n 個の要素を持つコレクションを返します。
#@# Page111
//embed[latex]{
\enlargethispage{10mm}
//}
//embed[latex]{
\clearpage
//}
//emlist{
(take 10 (range 100))
;; => (0 1 2 3 4 5 6 7 8 9)
//}
@<embed>{|latex|\vspace{-0.3\Cvs\}}
#@# Let's step back for a bit and learn about the early termination of the reduce function. We can wrap an accumulator in a type called reduced for telling reduce that the reduction process should terminate immediately. Let's see an example of a reduction that aggregates the inputs in a collection and finishes as soon as there are 10 elements in the accumulator:
少し話題を変えて、reduce 関数の早期終了について学びましょう。アキュムレータを reduced と呼ばれる型にラップして、reduce の処理をすぐに終了するように指示することができます。コレクション内の入力を集約し、アキュムレータに 10 個の要素が入ると終了する reduce の例を見てみましょう。
@<embed>{|latex|\vspace{-0.3\Cvs\}}
//emlist{
(reduce (fn [acc input]
(if (= (count acc) 10)
(reduced acc)
(conj acc input)))
[]
(range 100))
;; => [0 1 2 3 4 5 6 7 8 9]
//}
@<embed>{|latex|\vspace{-0.3\Cvs\}}
#@# Since transducers are modifications of reducing functions they also use reduced for early termination. Note that stateful transducers may need to do some cleanup before the process terminates, so they must support an arity 1 as a "completion" step. Usually, like with arity 0, this arity will simply delegate to the transformed reducing function's arity 1.
transducer は reducing 関数の変形であるため、早期終了のために reduced を使用します。状態をもつ transducer はプロセスが終了する前に、何らかの掃除を行う必要があるかもしれないので、完成のためのステップとして、引数を1つとる場合をサポートしなければならないことに注意してください。通常、引数を取らない場合と同様に引数は、引数が 1 つである変形された reducing 関数にデリゲートされます。
#@# Knowing this we are able to write stateful transducers like take . We'll be using mutable state internally for tracking the number of inputs we have seen so far, and wrap the accumulator in a reduced as soon as we've seen enough elements:
これを知っていると、take のような状態をもつ transducer を書くことができます。これまでに見てきたような入力の数を追跡するために、可変な状態を内部的に使用して、必要な要素数を確認したらすぐに、アキュムレータを reduced にラップします。
@<embed>{|latex|\vspace{-0.3\Cvs\}}
//emlist{
(defn my-take
[n]
(fn [rfn]
(let [remaining (volatile! n)]
(fn
([] (rfn))
([acc] (rfn acc))
([acc input]
(let [rem @remaining
nr (vswap! remaining dec)
;; we still have items to take
result (if (pos? rem)
;; we're done,
;; acc becomes the result
(rfn acc input)
acc)]
;; wrap result in reduced if not already
(if (not (pos? nr))
(ensure-reduced result)
result)))))))
//}
#@# Page112
//embed[latex]{
\enlargethispage{15mm}
//}
//embed[latex]{
\clearpage
//}
#@# This is a simplified version of the take function present in ClojureScript core. There are a few things to note here so let's break it up in pieces to understand it better.
これは ClojureScript の core にある @<code>{take} 関数を単純化したものです。注意すべき点がいくつかあるので、少しずつ分けて見ていきましょう。
#@# The first thing to notice is that we are creating a mutable value inside the transducer. Note that we don't create it until we receive a reducing function to transform. If we created it before returning the transducer we couldn't use my-take more than once. Since the transducer is handed a reducing function to transform each time it is used, we can use it multiple times and the mutable variable will be created in every use.
まず最初に注意しなければならないのは、transducer 内で変更可能な値を作成している点です。変換を行う reducing 関数を受け取るまでは値を作成していないことに注意してください。transducer を返す前に作ってしまうと、my-take 関数を 2 回以上使うことはできません。transducer は、使用の度に変換を行う reducing 関数を渡されるので、何度でも使うことができます。また、変更可能な変数は、毎回使うたびに作成されます。
//emlist{
(fn [rfn]
;; make sure to create mutable variables
;; inside the transducer
(let [remaining (volatile! n)]
(fn
;; ...
)))
(def take-five (my-take 5))
(transduce take-five conj (range 100))
;; => [0 1 2 3 4]
(transduce take-five conj (range 100))
;; => [0 1 2 3 4]
//}
#@# Let's now dig into the reducing function returned from my-take . First of all we deref the volatile to get the number of elements that remain to be taken and decrement it to get the next remaining value. If there are still remaining items to take, we call rfn passing the accumulator and input; if not, we already have the final result.
ここで、@<code>{my-take} から返される reducing 関数について詳しく見ていきましょう。まず volatile に @<code>{deref} を行い、取得されるべき残りの要素の数を取得して、それをデクリメントして次の残りの値を取得します。もし、まだ取るべきアイテムが残っているなら、アキュムレータと入力を渡して @<code>{rfn} を呼びます。そうでなければ、すでに最終的な結果をもっています。
//emlist{
([acc input]
(let [rem @remaining
nr (vswap! remaining dec)
result (if (pos? rem)
(rfn acc input)
acc)]
;; ...
))
//}
#@# The body of my-take should be obvious by now. We check whether there are still items to be processed using the next remainder ( nr ) and, if not, wrap the result in a reduced using the ensure-reduced function. ensure-reduced will wrap the value in a reduced if it's not reduced already or simply return the value if it's already reduced. In case we are not done yet, we return the accumulated result for further processing.
@<code>{my-take} の本体は明らかになっているはずです。次の剰余(nr)を使用して、処理されるアイテムがまだ存在するかどうかをチェックします。存在しない場合は、ensure-reduced 関数を使用して、結果を reduced にラップします。ensure-reduced は、まだ reduce されていない場合は、値を reduced でラップして、すでに削減されている場合には単に値を返します。まだ完了していない場合は、累積した結果を返して処理します。
#@# Page113
//embed[latex]{
\enlargethispage{10mm}
//}
//embed[latex]{
\clearpage
//}
//emlist{
(if (not (pos? nr))
(ensure-reduced result)
result)
//}
@<embed>{|latex|\vspace{-0.3\Cvs\}}
#@# We've seen an example of a stateful transducer but it didn't do anything in its completion step. Let's see an example of a transducer that uses the completion step to flush an accumulated value. We'll implement a simplified version of partition-all , which given a n number of elements converts the inputs in vectors of size n . For understanding its purpose better let's see what the arity 2 version gives us when providing a number and a collection:
ステートフルな transducer の例を見てきましたが、完了の段階では何もしませんでした。累積値を表示するために完了のステップを使用する transducer の例を見てみましょう。要素の数が n の要素あれば、サイズが n 個のベクタの入力を変換する @<code>{partition-all} の簡素版を実装しましょう。目的をよく理解するために、引数を 2 つとる場合で、数字とコレクションを与えると何が得られるかを見てみましょう。
@<embed>{|latex|\vspace{-0.3\Cvs\}}
//emlist{
(partition-all 3 (range 10))
;; => ((0 1 2) (3 4 5) (6 7 8) (9))
//}
@<embed>{|latex|\vspace{-0.3\Cvs\}}
#@# The transducer returning function of partition-all will take a number n and return a transducer that groups inputs in vectors of size n . In the completion step it will check if there is an accumulated result and, if so, add it to the result. Here's a simplified version of ClojureScript core partition-all function, where array-list is a wrapper for a mutable JavaScript array:
@<code>{partition-all} の transducer を返す関数は、数値 @<code>{n} を取り、@<code>{n} 個のサイズのベクタで入力をグループ化する transducer を返しまる。完了する段階では、累積結果があるかどうかを確認して、ある場合は結果に追加します。以下は、ClojureScript の core 関数である @<code>{partition-all} を単純化したもので、@<code>{array-list} は変更可能な JavaScript の配列のラッパーです。
@<embed>{|latex|\vspace{-0.3\Cvs\}}
//emlist{
(defn my-partition-all
[n]
(fn [rfn]
(let [a (array-list)]
(fn
([] (rfn))
([result]
;; no inputs accumulated, don't have to modify result
(let [result (if (.isEmpty a)
result
(let [v (vec (.toArray a))]
;; flush array contents for garbage collection
(.clear a)
;; pass to rfn , removing the reduced wrapper if present
(unreduced (rfn result v))))]
(rfn result)))
([acc input]
(.add a input)
;; got enough results for a chunk
(if (== n (.size a))
(let [v (vec (.toArray a))]
(.clear a)
;; the accumulated chunk
;; becomes input to rfn
(rfn acc v))
acc))))))
//}
#@# Page114
#@# @<embed>{|latex|\vspace{-0.4\Cvs\}}
//embed[latex]{
\enlargethispage{10mm}
//}
//embed[latex]{
\clearpage
//}
//emlist{
(def triples (my-partition-all 3))
(transduce triples conj (range 10))
;; => [[0 1 2] [3 4 5] [6 7 8] [9]]
//}
=== Eduction
#@# Eductions are a way to combine a collection and one or more transformations that can be reduced and iterated over, and that apply the transformations every time we do so. If we have a collection that we want to process and a transformation over it that we want others to extend, we can hand them a eduction, encapsulating the source collection and our transformation. We can create an eduction with the eduction function:
Eduction とは、コレクションと 1 つ以上の変換を組み合わせる方法であり、それらに対して reduce したり、繰り返したりすることができ、そのたびに変換を適用します。処理したいコレクションとそれに対する変換があり、他に拡張してほしい場合、私たちはソースコレクションと私たちの変換をカプセル化したEductionをそれらに渡すことができます。eduction 関数を使用して eduction を作成できます。
//emlist{
(def ed (eduction (filter odd?) (take 5) (range 100)))
(reduce + 0 ed)
;; => 25
(transduce (partition-all 2) conj ed)
;; => [[1 3] [5 7] [9]]
//}
=== ClojureScript core における他の Transducer
#@# More transducers in ClojureScript core
#@# We learned about map , filter , mapcat , take and partition-all , but there are a lot more transducers available in ClojureScript. Here is an incomplete list of some other intersting ones:
@<code>{map} 、@<code>{filter} 、@<code>{mapcat} 、@<code>{take} 、@<code>{partition-all} について学びましたが、ClojureScript にはもっと多くの transducer があります。以下は、他の興味深いものの一部です。
@<embed>{|latex|\vspace{0.5\Cvs\}}
#@# - drop is the dual of take , dropping up to n values before passing inputs to the reducing function
#@# - distinct only allows inputs to occur once
#@# - dedupe removes succesive duplicates in input values
- @<code>{drop} は take の双対であり、入力を reducing 関数に渡す前に @<code>{n} の値まで小さくします
- @<code>{distinct} は、入力を 1 回だけ行うことを許可します。
- @<code>{dedupe} は入力値の連続する重複を削除します
@<embed>{|latex|\vspace{0.5\Cvs\}}
#@# We encourage you to explore ClojureScript core to see what other transducers are out there.
他にどんな transducer があるのかを知るために、ClojureScript core を調べてみることをお勧めします。
=== Transducer を定義する
#@# Defining our own transducers
#@# There a few things to consider before writing our own transducers so in this section we will learn how to properly implement one. First of all, we've learned that the general structure of a transducer is the following:
独自の transducer を作成する前に考慮すべき点がいくつかあるので、この章では、transducer を適切に実装する方法を学びます。まず、transducer の一般的な構造は次の通りであることを学びました。
#@# Page115
#@# @<embed>{|latex|\vspace{-0.4\Cvs\}}
//embed[latex]{
\enlargethispage{10mm}
//}
//embed[latex]{
\clearpage
//}
//emlist{
(fn [xf]
(fn
;; init
([]
...)
;; completion
([r]
...)
;; step
([acc input]
...)))
//}
#@# Usually only the code represented with ... changes between transducers, these are the invariants that must be preserved in each arity of the resulting function:
通常、transducer の間では ... の箇所のコードのみが変化します。これらは、結果として得られる関数のそれぞれの引数で保存されなければならない不変の式です。
@<embed>{|latex|\vspace{1\Cvs\}}
#@# * arity 0 (init): must call the arity 0 of the nested transform xf
#@# * arity 1 (completion): used to produce a final value and potentially flush state, must call the arity 1 of the nested transform xf *exactly once*
#@# * arity 2 (step): the resulting reducing function which will call the arity 2 of the nested transform xf zero, one or more times
- 0 番目の引数(init): ネストされた変換 @<code>{xf} の 0 番目の引数を呼び出す必要があります。
- 1 番目の引数(completion): 最終値を生成し、潜在的にフラッシュ状態を生成するために使用され、ネストされたトランスフォーム @<code>{xf} の 1 番目の引数を正確に 1 度だけ呼び出す必要があります。
- 2 番目の引数(ステップ): 結果を導く reducing 関数であり、ネストされたトランスフォーム @<code>{xf} の 2 番目の引数を 0 回以上呼び出します。
=== トランスデゥシブルなプロセス
#@# Transducible processes
#@# A transducible process is any process that is defined in terms of a succession of steps ingesting input values. The source of input varies from one process to another. Most of our examples dealt with inputs from a collection or a lazy sequence, but it could be an asynchronous stream of values or a core.async channel. The outputs produced by each step are also different for every process; into creates a collection with every output of the transducer, sequence yields a lazy sequence, and asynchronous streams would probably push the outputs to their listeners.
トランスデゥシブルなプロセスとは、入力値を取り込む一連のステップによって定義される任意のプロセスです。入力のソースは、プロセスによって異なります。私たちの例の大半は、コレクションまたは遅延シーケンスからの入力を扱っていますが、非同期の値のストリーム、または core.async のチャンネルである可能性があります。各ステップで生成される出力もプロセスごとに異なります。into は transducer のすべての出力を持つコレクションを作成して、シーケンスは遅延シーケンスを生成し、非同期ストリームは出力をリスナーに通知します。
#@# In order to improve our understanding of transducible processes, we're going to implement an unbounded queue, since adding values to a queue can be thought in terms of a succession of steps ingesting input. First of all we'll define a protocol and a data type that implements the unbounded queue:
変換可能なプロセスについての理解を深めるために、無限の列(unbounded queue)を実装します。列に値を追加することは、入力を取り込む一連のステップと捉えることができるため、まず、無限の列を実装するプロトコルとデータ型を定義します。
//emlist{
(defprotocol Queue
(put! [q item] "put an item into the queue")
(take! [q] "take an item from the queue")
(shutdown! [q] "stop accepting puts in the queue"))
//}
#@# Page116
#@# @<embed>{|latex|\vspace{-0.4\Cvs\}}
//embed[latex]{
\enlargethispage{10mm}
//}
//embed[latex]{
\clearpage
//}
//emlist{
(deftype UnboundedQueue [^:mutable arr ^:mutable closed]
Queue
(put! [_ item]
(assert (not closed))
(assert (not (nil? item)))
(.push arr item)
item)
(take! [_]
(aget (.splice arr 0 1) 0))
(shutdown! [_]
(set! closed true)))
//}
#@# We defined the Queue protocol and as you may have noticed the implementation of UnboundedQueue doesn't know about transducers at all. It has a put! operation as its step function and we're going to implement the transducible process on top of that interface:
私たちは @<code>{Queue} プロトコルを定義しましたが、お気付きかもしれませんが、@<code>{UnboundedQueue} の実装は transducer をまったく認識しません。そのステップ関数として @<code>{put!} があるので、このインターフェースの上に transducible プロセスを実装します。
//emlist{
(defn unbounded-queue
([]
(unbounded-queue nil))
([xform]
(let [put! (completing put!)
xput! (if xform (xform put!) put!)
q (UnboundedQueue. #js [] false)]
(reify
Queue
(put! [_ item]
(when-not (.-closed q)
(let [val (xput! q item)]
(if (reduced? val)
(do
;; call completion step
(xput! @val)
;; respect reduced
(shutdown! q)
@val)
val))))
(take! [_]
(take! q))
(shutdown! [_]
(shutdown! q))))))
//}
#@# As you can see, the unbounded-queue constructor uses an UnboundedQueue instance internally, proxying the take! and shutdown! calls and implementing the transducible process logic in the put! function. Let's deconstruct it to understand what's going on.
@<code>{unbounded-queue} コンストラクタは、内部的に @<code>{UnboundedQueue} インスタンスを使用して、@<code>{take!} と @<code>{shutdown!} の呼び出しをプロキシして、@<code>{put!} 関数の中のトランスデゥシブルなロジックを実装します。何が起こっているのか理解するために、少しずつ見ていきましょう。
#@# Page117
#@# @<embed>{|latex|\vspace{-0.4\Cvs\}}
//embed[latex]{
\enlargethispage{0mm}
//}
//embed[latex]{
\clearpage
//}
//emlist{
(let [put! (completing put!)
xput! (if xform (xform put!) put!)
q (UnboundedQueue. #js [] false)]
;; ...
)
//}
@<embed>{|latex|\vspace{-0.4\Cvs\}}
#@# First of all, we use completing for adding the arity 0 and arity 1 to the Queue protocol's put! function. This will make it play nicely with transducers in case we give this reducing function to xform to derive another. After that, if a transducer ( xform ) was provided, we derive a reducing function applying the transducer to put! . If we're not handed a transducer we will just use put! . q is the internal instance of UnboundedQueue .
まず、Queue プロトコルの put! 関数に、引数をとらない場合と引数を 1 つとる場合を追加するために @<code>{completing} を使います。これにより、この reducing 関数を @<code>{xform} に与えて別のものを引き出す場合に、transducer とうまく動作するようになります。その後、もし transducer(@<code>{xform})が与えられると、transducer を put! に適用しながら reducing 関数を引き出します。transducer が渡されなければ、@<code>{put!} を使います。@<code>{q} は @<code>{UnboundedQueue} の内部インスタンスです。
@<embed>{|latex|\vspace{-0.4\Cvs\}}
//emlist{
(reify
Queue
(put! [_ item]
(when-not (.-closed q)
(let [val (xput! q item)]
(if (reduced? val)
(do
;; call completion step
(xput! @val)
;; respect reduced
(shutdown! q)
@val)
val))))
;; ...
)
//}
@<embed>{|latex|\vspace{-0.4\Cvs\}}
#@# The exposed put! operation will only be performed if the queue hasn't been shut down. Notice that the put! implementation of UnboundedQueue uses an assert to verify that we can still put values to it and we don't want to break that invariant. If the queue isn't closed we can put values into it, we use the possibly transformed xput! for doing so.
expose された @<code>{put!} 操作は queue がシャットダウンされていない場合にのみ実行されます。UnboundedQueue の @<code>{put!} の実装では、新たな値を入れることができるか、不変性を壊さないかを検証するために assert を用います。もし queue が閉じられておらず、値を入れることができる場合は、変換される可能性がある @<code>{xput!} を用います。
#@# If the put operation gives back a reduced value it's telling us that we should terminate the transducible process. In this case that means shutting down the queue to not accept more values. If we didn't get a reduced value we can happily continue accepting puts.
put の操作が reduce された値を返した場合、transducible なプロセスを終了する必要があることを示しています。この場合、queue をシャットダウンして、追加の値を受け入れないようにします。もし reduce された値をえられなければ、puts を引き受け続けることができます。
#@# Let's see how our queue behaves without transducers:
queue が transducer がない状態でどう動作するかを見てみましょう。
@<embed>{|latex|\vspace{-0.3\Cvs\}}
//emlist{
(def q (unbounded-queue))
;; => #<[object Object]>
(put! q 1)
;; => 1
(put! q 2)
;; => 2
//}
@<embed>{|latex|\vspace{-0.3\Cvs\}}
#@# Page118
#@# @<embed>{|latex|\vspace{-0.4\Cvs\}}
//embed[latex]{
\enlargethispage{10mm}
//}
//embed[latex]{
\clearpage
//}
//emlist{
(take! q)
;; => 1
(take! q)
;; => 2
(take! q)
;; => nil
//}
#@# Pretty much what we expected, let's now try with a stateless transducer:
期待していた通りですね。今度はステートレスな transducer で試してみましょう。
//emlist{
(def incq (unbounded-queue (map inc)))
;; => #<[object Object]>
(put! incq 1)
;; => 2
(put! incq 2)
;; => 3
(take! incq)
;; => 2
(take! incq)
;; => 3
(take! incq)
;; => nil
//}
#@# To check that we've implemented the transducible process, let's use a stateful transducer. We'll use a transducer that will accept values while they aren't equal to 4 and will partition inputs in chunks of 2 elements:
transducible なプロセスを実装したかどうかを確認するために、ステートフルな transducer を使いましょう。4 と等しくない間は値を受け入れ、入力を2つの要素のチャンクに分割する transducer を使用します。
//emlist{
(def xq (unbounded-queue (comp
(take-while #(not= % 4))
(partition-all 2))))
(put! xq 1)
(put! xq 2)
;; => [1 2]
(put! xq 3)
(put! xq 4) ;; shouldn't accept more values from here on
(put! xq 5)
;; => nil
(take! xq)
;; => [1 2]
;; seems like partition-all flushed correctly!
(take! xq)
;; => [3]
(take! xq)
;; => nil
//}
#@# Page119
#@# @<embed>{|latex|\vspace{-0.4\Cvs\}}
//embed[latex]{
\enlargethispage{10mm}
//}
//embed[latex]{
\clearpage
//}
#@# The example of the queue was heavily inspired by how core.async channels use transducers in their internal step. We'll discuss channels and their usage with transducers in a later section.
queue の例は、@<code>{core.async} チャンネルが内部ステップで transducer を使う方法にに大きく影響を受けています。チャンネルと transducer を使ったチャンネルの使い方については、後のセクションで説明します。
#@# Transducible processes must respect reduced as a way for signaling early termination. For example, building a collection stops when encountering a reduced and core.async channels with transducers are closed. The reduced value must be unwrapped with deref and passed to the completion step, which must be called exactly once.
Transducible なプロセスは、早期終了のシグナルの伝達手段として @<code>{reduced} を反映しなければいけません。たとえば、コレクションのビルドは、@<code>{reduced} に遭遇したときや、transducer をもつ @<code>{core.async} チャンネルが閉じられた場合に停止します。reduce された値は必ず @<code>{deref} で開封されて、完了のステップに渡されます。完了ステップは 1 度しか呼び出されてはいけません。
#@# Transducible processes shouldn't expose the reducing function they generate when calling the transducer with their own step function since it may be stateful and unsafe to use from elsewhere.
transducible なプロセスは、それ自身のステップ関数を用いて transducer を呼び出す際に生成する reducing 関数を expose すべきではありません。なぜなら、それは状態をもち、他の場所から使用することは安全でないからです。
== 一時性
#@# Transient
#@# Although ClojureScript's immutable and persistent data structures are reasonably performant there are situations in which we are transforming large data structures using multiple steps to only share the final result. For example, the core into function takes a collection and eagerly populates it with the contents of a sequence:
ClojureScriptの不変で永続的なデータ構造は、それなりのパフォーマンスを発揮しますが、最終的な結果を共有するだけのために、複数のステップを使って大きなデータ構造を変換している状況もあります。たとえば、core にある into 関数は、コレクションを取得して、シーケンスの内容を続々と取り込みます。
//emlist{
(into [] (range 100))
;; => [0 1 2 ... 98 99]
//}
#@# In the above example we are generating a vector of 100 elements conj -ing one at a time. Every intermediate vector that is not the final result won't be seen by anybody except the into function and the array copying required for persistence is an unnecesary overhead.
上の例では、100 個の要素からなるベクタを一度に一つずつ @<code>{conj} しながら生成しています。最終的な結果でない全ての中間ベクタは into 関数以外では見ることができず、永続化のために必要な配列のコピーは不要なオーバーヘッドです。
#@# For these situations ClojureScript provides a special version of some of its persistent data structures, which are called transients. Maps, vectors and sets have a transient counterpart. Transients are always derived from a persistent data structure using the transient function, which creates a transient version in constant time:
このような状況のために、ClojureScript は transient と呼ばれる永続的なデータ構造を提供しています。マップ、ベクタ、セットには、transient に相当するものがあります。Transient は、常に transient 関数を使用して、永続的なデータ構造から生成されます。これにより、一定の時間内に、transient のバージョンが作成されます。
@<embed>{|latex|\vspace{-0.4\Cvs\}}
//emlist{
(def tv (transient [1 2 3]))
;; => #<[object Object]>
//}
@<embed>{|latex|\vspace{-0.4\Cvs\}}
#@# Transients support the read API of their persistent counterparts:
Transient は、対応する永続的な読取り API をサポートします。
@<embed>{|latex|\vspace{-0.4\Cvs\}}
//emlist{
(def tv (transient [1 2 3]))
(nth tv 0)
;; => 1