-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.html
2728 lines (2605 loc) · 195 KB
/
index.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
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="generator" content="Asciidoctor 0.1.4">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Scala’s Types of Types</title>
<link rel="stylesheet" href="stylesheets/foundation.css">
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/font-awesome/3.2.1/css/font-awesome.min.css">
<link rel="stylesheet" href="stylesheets/asciidoctor-pygments.css">
</head>
<body class="article toc2 toc-right">
<div id="header">
<h1>Scala’s Types of Types</h1>
<div id="toc" class="toc2">
<div id="toctitle">Table of Contents</div>
<ul class="sectlevel1">
<li><a href="#the-different-types-of-types-in-scala">1. The different types of… Types in Scala</a></li>
<li><a href="#work-in-progress">2. WORK IN PROGRESS</a></li>
<li><a href="#type-ascription">3. Type Ascription</a></li>
<li><a href="#unified-type-system-any-anyref-anyval">4. Unified Type System - Any, AnyRef, AnyVal</a></li>
<li><a href="#the-bottom-types-nothing-and-null">5. The Bottom Types - Nothing and Null</a></li>
<li><a href="#type-of-an-code-object-code">6. Type of an <code>object</code></a></li>
<li><a href="#type-variance-in-scala">7. Type Variance in Scala</a></li>
<li>
<ul class="sectlevel2">
<li><a href="#traits-as-in-interfaces-with-implementation">7.1. Traits, as in "interfaces with implementation"</a></li>
<li><a href="#type-linearization-vs-the-diamond-problem">7.2. Type Linearization vs. The Diamond Problem</a></li>
</ul>
</li>
<li><a href="#refined-types-refinements">8. Refined Types (refinements)</a></li>
<li><a href="#package-object">9. Package Object</a></li>
<li><a href="#type-alias">10. Type Alias</a></li>
<li><a href="#abstract-type-member">11. Abstract Type Member</a></li>
<li><a href="#self-recursive-type">12. Self-Recursive Type</a></li>
<li>
<ul class="sectlevel2">
<li><a href="#f-bounded-type">12.1. F-Bounded Type</a></li>
</ul>
</li>
<li><a href="#type-constructor-span-style-color-red-span">13. Type Constructor <span style="color:red">✗</span></a></li>
<li><a href="#higher-order-kind-span-style-color-red-span">14. Higher-Order Kind <span style="color:red">✗</span></a></li>
<li><a href="#case-class">15. Case Class</a></li>
<li><a href="#enumeration">16. Enumeration</a></li>
<li>
<ul class="sectlevel2">
<li><a href="#enumeration-2">16.1. Enumeration</a></li>
<li><a href="#enum">16.2. @enum</a></li>
</ul>
</li>
<li><a href="#value-class">17. Value Class</a></li>
<li><a href="#type-class-span-style-color-red-span">18. Type Class <span style="color:red">✗</span></a></li>
<li><a href="#universal-trait-span-style-color-red-span">19. Universal Trait <span style="color:red">✗</span></a></li>
<li><a href="#self-type-annotation">20. Self Type Annotation</a></li>
<li><a href="#phantom-type">21. Phantom Type</a></li>
<li><a href="#structural-type">22. Structural Type</a></li>
<li><a href="#path-dependent-type">23. Path Dependent Type</a></li>
<li><a href="#type-projection">24. Type Projection</a></li>
<li><a href="#existential-types">25. Existential Types</a></li>
<li><a href="#specialized-types">26. Specialized Types</a></li>
<li>
<ul class="sectlevel2">
<li><a href="#specialized">26.1. @specialized</a></li>
<li><a href="#miniboxing-span-style-color-red-span">26.2. Miniboxing <span style="color:red">✗</span></a></li>
</ul>
</li>
<li><a href="#type-lambda-span-style-color-red-span">27. Type Lambda <span style="color:red">✗</span></a></li>
<li><a href="#union-type-span-style-color-red-span">28. Union Type <span style="color:red">✗</span></a></li>
<li><a href="#delayed-init">29. Delayed Init</a></li>
<li><a href="#dynamic-type">30. Dynamic Type</a></li>
<li>
<ul class="sectlevel2">
<li><a href="#applydynamic">30.1. applyDynamic</a></li>
<li><a href="#applydynamicnamed">30.2. applyDynamicNamed</a></li>
<li><a href="#selectdynamic">30.3. selectDynamic</a></li>
<li><a href="#updatedynamic">30.4. updateDynamic</a></li>
</ul>
</li>
<li><a href="#bibliography-and-kudos">31. Bibliography and Kudos</a></li>
<li>
<ul class="sectlevel2">
<li><a href="#reference-and-further-reading">31.1. Reference and further reading</a></li>
<li><a href="#thanks-and-kudos">31.2. Thanks and kudos</a></li>
<li><a href="#give-back-some-kudos">31.3. Give back some kudos!</a></li>
</ul>
</li>
</ul>
</div>
</div>
<div id="content">
<div class="sect1">
<h2 id="the-different-types-of-types-in-scala"><a class="anchor" href="#the-different-types-of-types-in-scala"></a>1. The different types of… Types in Scala</h2>
<div class="sectionbody">
<div class="paragraph">
<p>这篇博客是我在 2013 年参加了数个 JavaOne 会议,多次与人们讨论 Scala 中的类型之后写下的。经过这些讨论,我发现不同的人学习 Scala 时都会重复询问许多类似的问题。
我认为这是因为我们没有一个完整记录 Scala 类型技巧的列表,所以我决定写这样的一个列表 - 用现实生活的例子解释为什么我们需要这些类型。</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="work-in-progress"><a class="anchor" href="#work-in-progress"></a>2. WORK IN PROGRESS</h2>
<div class="sectionbody">
<div class="paragraph">
<p>虽然我在这篇博文已经花了不少时间,但是其中还有很多未完成。
比如 Higher Kinds 部分需要重写,Self Type 部分添加更多的细节等等。请查看 <a href="https://github.com/ktoso/scala-types-of-types/blob/gh-pages/TODO">TODO</a>。</p>
</div>
<div class="paragraph">
<p>如果你想帮忙的话,请不要犹豫!我欢迎任何形式的 pull request 或者建议(呃,我更喜欢 pull request;-)</p>
</div>
<div class="paragraph">
<p>另外,如果你看到某个小节标记着 "<span style="color:red">✗</span>" ,这意味着这部分需要重写或者还没有完成。</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="type-ascription"><a class="anchor" href="#type-ascription"></a>3. Type Ascription</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Scala 有类型推断,这意味我们不需要每次都在源代码中声明变量的类型,可以直接使用 <code>val</code> 或者 <code>def</code> 。
这种显式声明变量类型的方式称为 Type Ascription(有时也称为 "Type Annotation",
但是这样的名字很容易引起误解,所以没有在 Scala 规范中使用)。</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="pygments highlight"><code class="scala language-scala"><span class="tok-k">trait</span> <span class="tok-nc">Thing</span>
<span class="tok-k">def</span> <span class="tok-n">getThing</span> <span class="tok-k">=</span> <span class="tok-k">new</span> <span class="tok-nc">Thing</span> <span class="tok-o">{</span> <span class="tok-o">}</span>
<span class="tok-c1">// 没有 Type Ascription,变量类型被推导为 `Thing`</span>
<span class="tok-k">val</span> <span class="tok-n">inferred</span> <span class="tok-k">=</span> <span class="tok-n">getThing</span>
<span class="tok-c1">// 有 Type Ascription</span>
<span class="tok-k">val</span> <span class="tok-n">thing</span><span class="tok-k">:</span> <span class="tok-kt">Thing</span> <span class="tok-o">=</span> <span class="tok-n">getThing</span>
</code></pre>
</div>
</div>
<div class="paragraph">
<p>在上述情况下,我们可以省略 Type Ascription。不过你可能会决定总是描述公有方法(public methods)的返回类型(<strong>这是一个好主意!</strong>),使得代码更加自我文档化。</p>
</div>
<div class="paragraph">
<p>当你犹豫的时候,可以参考下面的提示问题来决定是否使用 Type Ascription:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>是一个参数吗?如果是的话,那么你必须加上。</p>
</li>
<li>
<p>是公有方法的返回值吗?如果是的话,那么需要加上以便于代码自我文档化和控制返回类型。</p>
</li>
<li>
<p>是递归或者重载方法的返回值吗?如果是的话,那么你必须加上。</p>
</li>
<li>
<p>你是否需要返回一个比推导器推导的更通用的类型?如果是的话,那么需要 Type Ascription,不然你就会向客户端暴露代码实现细节</p>
</li>
<li>
<p>否则的话…不要加上 Type Ascription</p>
</li>
<li>
<p>相关提示:加上 Type Ascription 可以加快编译速度,并且能看到方法的返回类型也是很好的</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>所以我们在变量名字后加上 Type Ascription。说了这么多,让我们进入下一个话题,其中这些类型会变得越来越有趣。</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="unified-type-system-any-anyref-anyval"><a class="anchor" href="#unified-type-system-any-anyref-anyval"></a>4. Unified Type System - Any, AnyRef, AnyVal</h2>
<div class="sectionbody">
<div class="paragraph">
<p>我们之所以称 Scala 的类型系统是"统一"的,是因为它有一个最顶层类型 Any。
<strong>这不同于 Java</strong>,Java 存在原始类型的"特殊情况"(<code>int</code>, <code>long</code>, <code>float</code>, <code>double</code>, <code>byte</code>, <code>char</code>, <code>short</code>, <code>boolean</code>),这些类型不继承 Java 的"近似顶层类型" - <code>java.lang.Object</code>。</p>
</div>
<div class="imageblock" style="text-align: center">
<div class="content">
<img src="assets/img/scala-types.png" alt="Scala's Unified Type System">
</div>
</div>
<div class="paragraph">
<p>Scala 通过引入 <code>Any</code> 使得所有类型都有一个通用顶层类型。<code>Any</code> 是 <code>AnyRef</code> 和 <code>AnyVal</code> 的父类。</p>
</div>
<div class="paragraph">
<p><code>AnyRef</code> 是 Java(以及 JVM)的"对象世界(object world)",对应 <code>java.lang.Object</code>,是所有对象的父类。
另一方面,<code>AnyVal</code> 对应 Java 的"值世界(value world)",比如 <code>int</code> 和其它 JVM 原始类型。</p>
</div>
<div class="paragraph">
<p>得益于这样的层次结构,我们可以定义接受 <code>Any</code> 的方法 - 兼容 <code>scala.Int</code> 和 <code>java.lang.String</code>:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="pygments highlight"><code class="scala language-scala"><span class="tok-k">class</span> <span class="tok-nc">Person</span>
<span class="tok-k">val</span> <span class="tok-n">allThings</span> <span class="tok-k">=</span> <span class="tok-nc">ArrayBuffer</span><span class="tok-o">[</span><span class="tok-kt">Any</span><span class="tok-o">]()</span>
<span class="tok-k">val</span> <span class="tok-n">myInt</span> <span class="tok-k">=</span> <span class="tok-mi">42</span> <span class="tok-c1">// Int, 运行时保持 JVM 原始类型 `int`</span>
<span class="tok-n">allThings</span> <span class="tok-o">+=</span> <span class="tok-n">myInt</span> <span class="tok-c1">// Int(继承 AnyVal)</span>
<span class="tok-c1">// 需要装箱(!) -> 在集合中变成 java.lang.Integer(!)</span>
<span class="tok-n">allThings</span> <span class="tok-o">+=</span> <span class="tok-k">new</span> <span class="tok-nc">Person</span><span class="tok-o">()</span> <span class="tok-c1">// Person(继承 AnyRef),没有特别的地方</span>
</code></pre>
</div>
</div>
<div class="paragraph">
<p>类型系统能透明地处理值和对象的集成或者共同存在,但一旦我们在 JVM 级别进入 <code>ArrayBuffer[Any]</code>,我们的 Int 实例会被打包成对象。
让我们使用 Scala REPL 和它的 <code>:javap</code> 命令(该命令可以展示编译器生成的字节码)来研究上面的例子:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="pygments highlight"><code class="asciidoc language-asciidoc">35: invokevirtual #47 // Method myInt:()I
38: invokestatic #53 // Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer;
41: invokevirtual #57 // Method scala/collection/mutable/ArrayBuffer.$plus$eq:(Ljava/lang/Object;)Lscala/collection/mutable/ArrayBuffer;</code></pre>
</div>
</div>
<div class="paragraph">
<p>你能注意到 <code>myInt</code> 仍是一个 <code>int primitive</code> 类型的值(从 <code>myInt:() I</code> <strong>invokevirtual</strong> 调用后的 I 可以看出)。
然后,在将其加入 ArrayBuffer 之前,scalac 插入了一个 <code>BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer</code> 的调用(给没有经常阅读字节码读者的一个小提示,scalac 实际调用的是 <code>public Integer boxToInteger(i: int)</code>)。
通过一个聪明的编译器,将所有变量都作为这个公共类型结构中的一个对象,这么做,至少在 Scala 源代码层面,我们可以远离"但是原始类型是不同的"窘况 - 编译器会帮我们处理这种情况。
在 JVM 层面,当然区别还是存在的,scalac 会尽可能的使用原始类型,因为原始类型的操作更快,并且占用更少的内存(对象显然大于原始类型)。</p>
</div>
<div class="paragraph">
<p>另一方面,我们可以限制一个方法只能接受"轻量级"值类型:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="pygments highlight"><code class="scala language-scala"><span class="tok-k">def</span> <span class="tok-n">check</span><span class="tok-o">(</span><span class="tok-n">in</span><span class="tok-k">:</span> <span class="tok-kt">AnyVal</span><span class="tok-o">)</span> <span class="tok-k">=</span> <span class="tok-o">()</span>
<span class="tok-n">check</span><span class="tok-o">(</span><span class="tok-mi">42</span><span class="tok-o">)</span> <span class="tok-c1">// Int -> AnyVal</span>
<span class="tok-n">check</span><span class="tok-o">(</span><span class="tok-mf">13.37</span><span class="tok-o">)</span> <span class="tok-c1">// Double -> AnyVal</span>
<span class="tok-n">check</span><span class="tok-o">(</span><span class="tok-k">new</span> <span class="tok-nc">Object</span><span class="tok-o">)</span> <span class="tok-c1">// -> AnyRef = 编译失败</span>
</code></pre>
</div>
</div>
<div class="paragraph">
<p>在上面的例子中,我们使用了一个 TypeClass <code>Checker[T]</code> 和一个类型绑定,这将在下面讨论。
总体思路是,这个方法只接受 <a href="#value-class">Value Classes</a>,可以是 Int 或者自定义的 Value 类型。
虽然这样的用法不常见,但是它很好地展示了类型系统是如何拥抱 Java 原始类型,并将它们引入到"真实"的类型系统,
而不是像 Java 那样区分出引用类型和值类型。</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="the-bottom-types-nothing-and-null"><a class="anchor" href="#the-bottom-types-nothing-and-null"></a>5. The Bottom Types - Nothing and Null</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Scala 中,每个变量都有"某种"类型…但是你是否想过在某些"奇怪"的情况下,类型推导器(type inferencer)是如何能继续工作的,
比如抛出了异常。让我们来看看下面这个 "if/else throw" 例子:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="pygments highlight"><code class="scala language-scala"><span class="tok-k">val</span> <span class="tok-n">thing</span><span class="tok-k">:</span> <span class="tok-kt">Int</span> <span class="tok-o">=</span>
<span class="tok-k">if</span> <span class="tok-o">(</span><span class="tok-n">test</span><span class="tok-o">)</span>
<span class="tok-mi">42</span> <span class="tok-c1">// : Int</span>
<span class="tok-k">else</span>
<span class="tok-k">throw</span> <span class="tok-k">new</span> <span class="tok-nc">Exception</span><span class="tok-o">(</span><span class="tok-s">"Whoops!"</span><span class="tok-o">)</span> <span class="tok-c1">// : Nothing</span>
</code></pre>
</div>
</div>
<div class="paragraph">
<p>正如你在注释中看到,if 语句块的类型是 Int(容易推导),else 语句块的类型是 Nothing(有趣)。
推导器推导出 thing 的类型只能是 Int,这是因为 Nothing 的 <strong>Bottom Type</strong> 属性。</p>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="icon-note" title="Note"></i>
</td>
<td class="content">
一个关于 bottom types 如何工作的直观理解是:<em>"<code>Nothing</code> extends everything."</em>
</td>
</tr>
</table>
</div>
<div class="paragraph">
<p>类型推导器总会寻找 if/else 语句两个分支的"公共类型",所以如果一个分支有一个继承了所有类型的类型,
那么另一个分支的类型就会自动成为 if/else 表达式的类型。</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="pygments highlight"><code class="scala language-scala"><span class="tok-n">类型可视化</span><span class="tok-err">:</span>
<span class="tok-o">[</span><span class="tok-kt">Int</span><span class="tok-o">]</span> <span class="tok-o">-></span> <span class="tok-o">...</span> <span class="tok-o">-></span> <span class="tok-nc">AnyVal</span> <span class="tok-o">-></span> <span class="tok-nc">Any</span>
<span class="tok-nc">Nothing</span> <span class="tok-o">-></span> <span class="tok-o">[</span><span class="tok-kt">Int</span><span class="tok-o">]</span> <span class="tok-o">-></span> <span class="tok-o">...</span> <span class="tok-o">-></span> <span class="tok-nc">AnyVal</span> <span class="tok-o">-></span> <span class="tok-nc">Any</span>
</code></pre>
</div>
</div>
<div class="paragraph">
<p>这样的推导方式也适用于 Scala 的第二个 Bottom Type - <code>Null</code>。</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="pygments highlight"><code class="scala language-scala"><span class="tok-k">val</span> <span class="tok-n">thing</span><span class="tok-k">:</span> <span class="tok-kt">String</span> <span class="tok-o">=</span>
<span class="tok-k">if</span> <span class="tok-o">(</span><span class="tok-n">test</span><span class="tok-o">)</span>
<span class="tok-s">"Yay!"</span> <span class="tok-c1">// : String</span>
<span class="tok-k">else</span>
<span class="tok-kc">null</span> <span class="tok-c1">// : Null</span>
</code></pre>
</div>
</div>
<div class="paragraph">
<p>正如预料中那样 <code>thing</code> 的类型是, String。<code>Null</code> 遵循与 Nothing 几乎一样的规则。
我会用这个例子来谈谈类型推导,以及 AnyVals 和 AnyRefs 之间的区别。</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="pygments highlight"><code class="asciidoc language-asciidoc">类型可视化:
[String] -> AnyRef -> Any
Null -> [String] -> AnyRef -> Any
推导类型:String</code></pre>
</div>
</div>
<div class="paragraph">
<p>让我们思考下 <code>Int</code> 和其他不能包含 null 的原始类型。为了研究这种情况,让我们进入 REPL 并使用 <code>:type</code> 命令(这条命令可以得到表达式的类型):</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="pygments highlight"><code class="scala-repl language-scala-repl">scala> :type if (false) 23 else null
Any</code></pre>
</div>
</div>
<div class="paragraph">
<p>这与上面例子中一个分支返回 String 对象的情况不同。
让我们来看看这里的详细类型,相比 <code>Nothing</code> 继承 everything,<code>Null</code> 继承的类型会少一点。
让我们再次使用 :type 看看 Int 的继承关系:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="pygments highlight"><code class="scala-repl language-scala-repl">scala> :type -v 12
// 类型签名
Int
// 内部类型结构
TypeRef(TypeSymbol(final abstract class Int extends AnyVal))</code></pre>
</div>
</div>
<div class="paragraph">
<p>在这里,可见选项(-v)输出了更多的信息,现在我们知道 <code>Int</code> 是一个 <code>AnyVal</code> - 是一个代表值类的特殊类 - 不能接受 <code>Null</code>。
如果我们查看 <a href="https://github.com/scala/scala/blob/v2.10.3/src/library/scala/AnyVal.scala">AnyVal 实现</a>,我们会发现:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="pygments highlight"><code class="scala language-scala"><span class="tok-k">abstract</span> <span class="tok-k">class</span> <span class="tok-nc">AnyVal</span> <span class="tok-k">extends</span> <span class="tok-nc">Any</span> <span class="tok-k">with</span> <span class="tok-nc">NotNull</span>
</code></pre>
</div>
</div>
<div class="paragraph">
<p>(注意,上面的代码是 Scala 2.10.x 及之前的 <code>AnyVal</code> 实现;从 2.11.x 之后,NotNull 就被移除了)</p>
</div>
<div class="paragraph">
<p>我之所以在这里提到这个,是因为 AnyVal 的核心功能被很好的用类型表达出来。<strong>注意 NotNull 特质</strong>!</p>
</div>
<div class="paragraph">
<p>回到刚才的主题,为什么我们的 if 语句,其中一个分支的类型是 <code>AnyVal</code> 然后另一个分支的类型是 <code>Null</code> 的公共类型是 Any 而不是其他类型。
一句简洁的解释就是: <code>Null 继承所有 AnyRefs</code> 而 <code>Nothing 继承 anything</code>。
因为 AnyVals(比如 numbers)与 AnyRefs 不在同一颗继承树上,那么一个 number 和一个 <code>null</code> 值的公共类型只能是 Any - 这就是上述情况的解释。</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="pygments highlight"><code class="scala language-scala"><span class="tok-n">类型可视化</span><span class="tok-err">:</span>
<span class="tok-nc">Int</span> <span class="tok-o">-></span> <span class="tok-nc">NotNull</span> <span class="tok-o">-></span> <span class="tok-nc">AnyVal</span> <span class="tok-o">-></span> <span class="tok-o">[</span><span class="tok-kt">Any</span><span class="tok-o">]</span>
<span class="tok-nc">Null</span> <span class="tok-o">-></span> <span class="tok-nc">AnyRef</span> <span class="tok-o">-></span> <span class="tok-o">[</span><span class="tok-kt">Any</span><span class="tok-o">]</span>
<span class="tok-n">推导类型</span><span class="tok-err">:</span><span class="tok-nc">Any</span>
</code></pre>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="type-of-an-code-object-code"><a class="anchor" href="#type-of-an-code-object-code"></a>6. Type of an <code>object</code></h2>
<div class="sectionbody">
<div class="paragraph">
<p>Scala 的 <code>object</code> 是基于类实现的(这是很显然的 - 因为类是 JVM 的基本构建模块),
但是你会注意到我们无法像获得类的类型那样获得一个 object 的类型。</p>
</div>
<div class="paragraph">
<p>令人惊讶的是,我经常被问到如何传递一个 object 给一个方法的问题。
如果仅使用 <code>obj: ExampleObj</code> 是不能通过编译的,这是因为 ExampleObject 已经指向 object 实例了,在这种情况下应该使用一个叫做 <code>type</code> 的方法。
下面的例子展示了如何使用 <code>type</code> 方法:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="pygments highlight"><code class="scala language-scala"><span class="tok-k">object</span> <span class="tok-nc">ExampleObj</span>
<span class="tok-k">def</span> <span class="tok-n">takeAnObject</span><span class="tok-o">(</span><span class="tok-n">obj</span><span class="tok-k">:</span> <span class="tok-kt">ExampleObj.</span><span class="tok-k">type</span><span class="tok-o">)</span> <span class="tok-k">=</span> <span class="tok-o">{}</span>
<span class="tok-n">takeAnObject</span><span class="tok-o">(</span><span class="tok-nc">ExampleObj</span><span class="tok-o">)</span>
</code></pre>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="type-variance-in-scala"><a class="anchor" href="#type-variance-in-scala"></a>7. Type Variance in Scala</h2>
<div class="sectionbody">
<div class="paragraph">
<p>一般来说,variance 可被解释为类型之间的"类型兼容性",形成一个 <code>extends</code> 关系。
你需要处理这种问题的最常见情况是使用容器或者函数(你会惊讶的发现这种情况是非常频繁的!)。</p>
</div>
<div class="paragraph">
<p>Scala 与 Java 的一个主要区别是,容器类型<strong>默认并不是协变的(not-variant by default)</strong>。
这意味着如果你定义了一个容器 <code>Box[A]</code>,然后使用 <code>Fruit</code> 代替类型参数 A,那么你不能插入一个 <code>Apple</code> 到容器内(Apple 确实是水果,<em>IS-A</em> 关系)。</p>
</div>
<div class="paragraph">
<p>在 Scala 中,Variance 是通过在类型参数前加上 <code>+</code> 或者 <code>-</code> 符号定义的</p>
</div>
<table class="tableblock frame-all grid-all" style="width:100%; ">
<colgroup>
<col style="width:33%;">
<col style="width:33%;">
<col style="width:33%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">名字</th>
<th class="tableblock halign-left valign-top">描述</th>
<th class="tableblock halign-left valign-top">Scala 语法</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">Invariant</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">C[T'] and C[T] <strong>不</strong>相关</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">C[T]</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">Covariant</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">C[T'] 是 C[T] 的子类</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">C[+T]</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">Contravariant</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">C[T] 是 C[T'] 的子类</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">C[-T]</p></td>
</tr>
</tbody>
</table>
<div class="paragraph">
<p>上面的表格以抽象的形式展现了我们需要关心的所有 variance。
你可能想知道在哪里你需要关心这些呢。事实上,每次你使用集合时你都面临这样的问题:"它是协变的吗?"。</p>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="icon-note" title="Note"></i>
</td>
<td class="content">
大多数<em>不可变(immutable)</em> 都是<em>协变的(covariant)</em>,大多数<em>可变(mutable)</em>集合都是<em>不变的(invariant)</em>.
</td>
</tr>
</table>
</div>
<div class="paragraph">
<p>在 Scala 中至少有两个关于 variance 很好并且非常直观的例子。第一个例子是"任何集合",我们使用 List[+A] 作为集合的代表;
第二个例子出现在函数的使用中,我们将不会在这里介绍。
当讨论 Scala 的 <code>List</code> 时,我们通常指的是 <code>scala.collection.immutable.List[+A]</code>,既是不可变又是协变的。
让我们看看 variance 与构建一个包含不同类型的列表有何关联。</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="pygments highlight"><code class="scala language-scala"><span class="tok-k">class</span> <span class="tok-nc">Fruit</span>
<span class="tok-k">case</span> <span class="tok-k">class</span> <span class="tok-nc">Apple</span><span class="tok-o">()</span> <span class="tok-k">extends</span> <span class="tok-nc">Fruit</span>
<span class="tok-k">case</span> <span class="tok-k">class</span> <span class="tok-nc">Orange</span><span class="tok-o">()</span> <span class="tok-k">extends</span> <span class="tok-nc">Fruit</span>
<span class="tok-k">val</span> <span class="tok-n">l1</span><span class="tok-k">:</span> <span class="tok-kt">List</span><span class="tok-o">[</span><span class="tok-kt">Apple</span><span class="tok-o">]</span> <span class="tok-k">=</span> <span class="tok-nc">Apple</span><span class="tok-o">()</span> <span class="tok-o">::</span> <span class="tok-nc">Nil</span>
<span class="tok-k">val</span> <span class="tok-n">l2</span><span class="tok-k">:</span> <span class="tok-kt">List</span><span class="tok-o">[</span><span class="tok-kt">Fruit</span><span class="tok-o">]</span> <span class="tok-k">=</span> <span class="tok-nc">Orange</span><span class="tok-o">()</span> <span class="tok-o">::</span> <span class="tok-n">l1</span>
<span class="tok-c1">// 并且我们可以很安全的向前添加任何元素</span>
<span class="tok-c1">// 这是因为我们在构建一个新的 list - 而不是修改原来的实例</span>
<span class="tok-k">val</span> <span class="tok-n">l3</span><span class="tok-k">:</span> <span class="tok-kt">List</span><span class="tok-o">[</span><span class="tok-kt">AnyRef</span><span class="tok-o">]</span> <span class="tok-k">=</span> <span class="tok-s">""</span> <span class="tok-o">::</span> <span class="tok-n">l2</span>
</code></pre>
</div>
</div>
<div class="paragraph">
<p>值得一提的是,尽管<strong>让不可变集合具有协变特性是<em>安全的</em></strong>,但是对于可变集合就不成立。
一个经典的例子是不可变的 <code>Array[T]</code>。让我们来看看这里不变性对我们来说意味着什么,以及它是如何从错误中拯救我们的:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="pygments highlight"><code class="scala language-scala"><span class="tok-c1">// 不能通过编译</span>
<span class="tok-k">val</span> <span class="tok-n">a</span><span class="tok-k">:</span> <span class="tok-kt">Array</span><span class="tok-o">[</span><span class="tok-kt">Any</span><span class="tok-o">]</span> <span class="tok-k">=</span> <span class="tok-nc">Array</span><span class="tok-o">[</span><span class="tok-kt">Int</span><span class="tok-o">](</span><span class="tok-mi">1</span><span class="tok-o">,</span> <span class="tok-mi">2</span><span class="tok-o">,</span> <span class="tok-mi">3</span><span class="tok-o">)</span>
</code></pre>
</div>
</div>
<div class="paragraph">
<p>因为数组的不可变性,上述赋值是不能通过编译的。
假设这样的赋值是合法的,那么我们就可以这样写出这样的代码:<code>a(0) = "" // ArrayStoreException!</code>,会导致令人畏惧的 ArrayStoreException。</p>
</div>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<i class="icon-tip" title="Tip"></i>
</td>
<td class="content">
我说 Scala 中"大部分"不可变集合都是协变的。如果你好奇的话,一个反例是 <code>Set[A]</code>,虽然它是不可变集合,但是它是不变的。
</td>
</tr>
</table>
</div>
<div class="sect2">
<h3 id="traits-as-in-interfaces-with-implementation"><a class="anchor" href="#traits-as-in-interfaces-with-implementation"></a>7.1. Traits, as in "interfaces with implementation"</h3>
<div class="paragraph">
<p>首先,让我们看看我们用特质(Trait)能做到的最简单的事情:
我们是如何处理一个混入了多个特质的类型,就好像它实现了这些"带有实现的接口" - 如果你来自 Java 世界,你有可能会试图这样称呼特质。</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="pygments highlight"><code class="scala language-scala"><span class="tok-k">class</span> <span class="tok-nc">Base</span> <span class="tok-o">{</span> <span class="tok-k">def</span> <span class="tok-n">b</span> <span class="tok-k">=</span> <span class="tok-s">""</span> <span class="tok-o">}</span>
<span class="tok-k">trait</span> <span class="tok-nc">Cool</span> <span class="tok-o">{</span> <span class="tok-k">def</span> <span class="tok-n">c</span> <span class="tok-k">=</span> <span class="tok-s">""</span> <span class="tok-o">}</span>
<span class="tok-k">trait</span> <span class="tok-nc">Awesome</span> <span class="tok-o">{</span> <span class="tok-k">def</span> <span class="tok-n">a</span> <span class="tok-o">=</span><span class="tok-s">""</span> <span class="tok-o">}</span>
<span class="tok-k">class</span> <span class="tok-nc">BA</span> <span class="tok-k">extends</span> <span class="tok-nc">Base</span> <span class="tok-k">with</span> <span class="tok-nc">Awesome</span>
<span class="tok-k">class</span> <span class="tok-nc">BC</span> <span class="tok-k">extends</span> <span class="tok-nc">Base</span> <span class="tok-k">with</span> <span class="tok-nc">Cool</span>
<span class="tok-c1">// 正如你所预料的,你可以将这些实例转换成它们混入的任意特质类型</span>
<span class="tok-k">val</span> <span class="tok-n">ba</span><span class="tok-k">:</span> <span class="tok-kt">BA</span> <span class="tok-o">=</span> <span class="tok-k">new</span> <span class="tok-nc">BA</span>
<span class="tok-k">val</span> <span class="tok-n">bc</span><span class="tok-k">:</span> <span class="tok-kt">Base</span> <span class="tok-kt">with</span> <span class="tok-kt">Cool</span> <span class="tok-o">=</span> <span class="tok-k">new</span> <span class="tok-nc">BC</span>
<span class="tok-k">val</span> <span class="tok-n">b1</span><span class="tok-k">:</span> <span class="tok-kt">Base</span> <span class="tok-o">=</span> <span class="tok-n">ba</span>
<span class="tok-k">val</span> <span class="tok-n">b2</span><span class="tok-k">:</span> <span class="tok-kt">Base</span> <span class="tok-o">=</span> <span class="tok-n">bc</span>
<span class="tok-n">ba</span><span class="tok-o">.</span><span class="tok-n">a</span>
<span class="tok-n">bc</span><span class="tok-o">.</span><span class="tok-n">c</span>
<span class="tok-n">b1</span><span class="tok-o">.</span><span class="tok-n">b</span>
</code></pre>
</div>
</div>
<div class="paragraph">
<p>到目前为止,这对你来说应该是相当直接的。现在让我们深入"菱形问题"的世界,C++ 开发人员应该熟悉这一点。
基本上,"菱形问题"就是存在多重继承的情况下,我们无法确定哪个是直接父类。
如果我们将特质组合看成是多重继承的用法,那么下面的图片就演示了这个问题:</p>
</div>
</div>
<div class="sect2">
<h3 id="type-linearization-vs-the-diamond-problem"><a class="anchor" href="#type-linearization-vs-the-diamond-problem"></a>7.2. Type Linearization vs. The Diamond Problem</h3>
<div class="imageblock" style="text-align: center">
<div class="content">
<img src="assets/img/220px-Diamond_inheritance.svg.png" alt="Diamond Inheritance">
</div>
</div>
<div class="paragraph">
<p>为了产生"菱形问题",我们只要在 <code>B</code> 或者/并且 <code>C</code> 中有一个覆盖实现。
这样的话,当调用 D 的方法时,我们就引入了歧义性。在 D 中,我们调用的是继承来自 C 还是来自 B 的方法?
在 Scala 中,只有一个覆盖方法时是非常简单的 - 覆盖方法胜利。但是,让我们考虑更复杂的案例:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>类 <code>A</code> 定义了一个方法 <code>common</code> 返回 <code>a</code>,</p>
</li>
<li>
<p>特质 <code>B</code> 覆盖了 <code>common</code> 返回 <code>b</code>,</p>
</li>
<li>
<p>特质 <code>C</code> 覆盖了 <code>common</code> 返回 <code>c</code>,</p>
</li>
<li>
<p>类 <code>D</code> 继承了 <code>B</code> and <code>C</code>,</p>
</li>
<li>
<p>类 <code>D</code> 继承了哪个版本的 <code>common</code> 方法呢?是来自 <code>C</code> 的覆盖实现,还是来自 <code>B</code> 的呢?</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>这种模糊性是每个类似多继承机制的痛点。Scala 通过所谓的 <strong>Type Linearization</strong> 解决这个问题。
换句话说,给定一个菱形的类结构,我们<strong>总是</strong>(<strong>确定性地</strong>)可以决定在 D 的内部调用 <code>common</code> 时哪个覆盖方法会被调用。
让我们用代码实现,然后再讨论线性化:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="pygments highlight"><code class="scala language-scala"><span class="tok-k">trait</span> <span class="tok-nc">A</span> <span class="tok-o">{</span> <span class="tok-k">def</span> <span class="tok-n">common</span> <span class="tok-k">=</span> <span class="tok-s">"A"</span> <span class="tok-o">}</span>
<span class="tok-k">trait</span> <span class="tok-nc">B</span> <span class="tok-k">extends</span> <span class="tok-n">A</span> <span class="tok-o">{</span> <span class="tok-k">override</span> <span class="tok-k">def</span> <span class="tok-n">common</span> <span class="tok-k">=</span> <span class="tok-s">"B"</span> <span class="tok-o">}</span>
<span class="tok-k">trait</span> <span class="tok-nc">C</span> <span class="tok-k">extends</span> <span class="tok-n">A</span> <span class="tok-o">{</span> <span class="tok-k">override</span> <span class="tok-k">def</span> <span class="tok-n">common</span> <span class="tok-k">=</span> <span class="tok-s">"C"</span> <span class="tok-o">}</span>
<span class="tok-k">class</span> <span class="tok-nc">D1</span> <span class="tok-k">extends</span> <span class="tok-n">B</span> <span class="tok-k">with</span> <span class="tok-n">C</span>
<span class="tok-k">class</span> <span class="tok-nc">D2</span> <span class="tok-k">extends</span> <span class="tok-n">C</span> <span class="tok-k">with</span> <span class="tok-n">B</span>
</code></pre>
</div>
</div>
<div class="paragraph">
<p>检查上述类型,我们获得下面的运行时行为:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="pygments highlight"><code class="scala language-scala"><span class="tok-o">(</span><span class="tok-k">new</span> <span class="tok-n">D1</span><span class="tok-o">).</span><span class="tok-n">common</span> <span class="tok-o">==</span> <span class="tok-s">"C"</span>
<span class="tok-o">(</span><span class="tok-k">new</span> <span class="tok-n">D2</span><span class="tok-o">).</span><span class="tok-n">common</span> <span class="tok-o">==</span> <span class="tok-s">"B"</span>
</code></pre>
</div>
</div>
<div class="paragraph">
<p>之所以会出现这样的结果,是因为在这里 Scala 为我们应用了 type linearization。算法流程如下:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>从头构建一个类型的列表,列表的第一个元素是我们正在线性化的类型</p>
</li>
<li>
<p>递归地扩展每个父类,并把这些类型都放到这个列表中(列表应该是平坦而不是嵌套的)</p>
</li>
<li>
<p>从结果列表中删除重复项,从列表左边开始扫描,去除已经"看到过"的类型</p>
</li>
<li>
<p>完成</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>让我们对上面的菱形例子应用这个算法,验证为什么 <code>D1 extends B with C</code>(和 <code>D2 extends C with B</code>)会返回这样的结果:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="pygments highlight"><code class="scala language-scala"><span class="tok-c1">// 从 D1 开始</span>
<span class="tok-n">B</span> <span class="tok-k">with</span> <span class="tok-n">C</span> <span class="tok-k">with</span> <span class="tok-o"><</span><span class="tok-n">D1</span><span class="tok-o">></span>
<span class="tok-c1">// 对每一个类型,扩展它直到到达 Any</span>
<span class="tok-o">(</span><span class="tok-nc">Any</span> <span class="tok-k">with</span> <span class="tok-nc">AnyRef</span> <span class="tok-k">with</span> <span class="tok-n">A</span> <span class="tok-k">with</span> <span class="tok-n">B</span><span class="tok-o">)</span> <span class="tok-k">with</span> <span class="tok-o">(</span><span class="tok-nc">Any</span> <span class="tok-k">with</span> <span class="tok-nc">AnyRef</span> <span class="tok-k">with</span> <span class="tok-n">A</span> <span class="tok-k">with</span> <span class="tok-n">C</span><span class="tok-o">)</span> <span class="tok-k">with</span> <span class="tok-o"><</span><span class="tok-n">D1</span><span class="tok-o">></span>
<span class="tok-c1">// 从左到右,通过删除"已经看到"的类型,去除冗余</span>
<span class="tok-o">(</span><span class="tok-nc">Any</span> <span class="tok-k">with</span> <span class="tok-nc">AnyRef</span> <span class="tok-k">with</span> <span class="tok-n">A</span> <span class="tok-k">with</span> <span class="tok-n">B</span><span class="tok-o">)</span> <span class="tok-k">with</span> <span class="tok-o">(</span> <span class="tok-n">C</span><span class="tok-o">)</span> <span class="tok-k">with</span> <span class="tok-o"><</span><span class="tok-n">D1</span><span class="tok-o">></span>
<span class="tok-c1">// 书写最后的结果类型</span>
<span class="tok-nc">Any</span> <span class="tok-k">with</span> <span class="tok-nc">AnyRef</span> <span class="tok-k">with</span> <span class="tok-n">A</span> <span class="tok-k">with</span> <span class="tok-n">B</span> <span class="tok-k">with</span> <span class="tok-n">C</span> <span class="tok-k">with</span> <span class="tok-o"><</span><span class="tok-n">D1</span><span class="tok-o">></span>
</code></pre>
</div>
</div>
<div class="paragraph">
<p>现在调用 <code>common</code> 方法时,我们可以很简单的决定调用的是哪个版本:
我们只需要查看线性化类型,并尝试从右向左解析方法调用。
在 <code>D1</code> 的例子中,处于"最右边"并且能提供的 <code>common</code> 实现的是特质 C,
所以它覆盖了 <code>B</code> 提供的 <code>common</code> 实现。那么在 <code>D1</code> 内部调用 <code>common</code> 的结果就是 <code>"c"</code>。</p>
</div>
<div class="paragraph">
<p>你可以通过对类 <code>D2</code> 运行这个算法来加深理解 - <code>B</code> 应该线性化在 <code>C</code> 的右边,所以当你运行代码时会返回一个 <code>"b"</code>。
对于这样简单的线性化例子,我们可以仅考虑"最右边的赢",这样的想法非常简单易于理解,但是没有给出线性化算法的全貌。</p>
</div>
<div class="paragraph">
<p>值得一提的是,使用这个技巧,我们现在还可以回答<strong>"谁是我的 <code>super</code>?</strong>。对于任意的类,如果你想检查谁是你的父类,只需要检查线性化类型的左边。比如在我们的例子(<code>D1</code>)中,<code>C</code> 的父类是 <code>B</code>。</p>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="refined-types-refinements"><a class="anchor" href="#refined-types-refinements"></a>8. Refined Types (refinements)</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Refinements 可以很容易的解释为"继承而无需命名子类"("subclassing without naming the subclass")。所以在代码中,refinements 看起来像:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="pygments highlight"><code class="scala language-scala"><span class="tok-k">class</span> <span class="tok-nc">Entity</span>
<span class="tok-k">trait</span> <span class="tok-nc">Persister</span> <span class="tok-o">{</span>
<span class="tok-k">def</span> <span class="tok-n">doPersist</span><span class="tok-o">(</span><span class="tok-n">e</span><span class="tok-k">:</span> <span class="tok-kt">Entity</span><span class="tok-o">)</span> <span class="tok-k">=</span> <span class="tok-o">{</span>
<span class="tok-n">e</span><span class="tok-o">.</span><span class="tok-n">persistForReal</span><span class="tok-o">()</span>
<span class="tok-o">}</span>
<span class="tok-o">}</span>
<span class="tok-c1">// 我们的 refined 实例(和类型):</span>
<span class="tok-k">val</span> <span class="tok-n">refinedMockPersister</span> <span class="tok-k">=</span> <span class="tok-k">new</span> <span class="tok-nc">Persister</span> <span class="tok-o">{</span>
<span class="tok-k">override</span> <span class="tok-k">def</span> <span class="tok-n">doPersist</span><span class="tok-o">(</span><span class="tok-n">e</span><span class="tok-k">:</span> <span class="tok-kt">Entity</span><span class="tok-o">)</span> <span class="tok-k">=</span> <span class="tok-o">()</span>
<span class="tok-o">}</span>
</code></pre>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="package-object"><a class="anchor" href="#package-object"></a>9. Package Object</h2>
<div class="sectionbody">
<div class="paragraph">
<p><a href="http://www.scala-lang.org/docu/files/packageobjects/packageobjects.html">Package object</a> 在 Scala 2.8 中加入,尽管它没有很好的扩展类型系统,
但是提供了一个非常有用的模式,允许我们"一次性导入一堆东西(importing a bunch of stuff together)",同时还是编译器寻找隐式转换的地方。
这里,我们的讨论将局限于它的第一个用法,即将数据聚合在一起:</p>
</div>
<div class="paragraph">
<p>声明一个 package object 很简单,只需要使用关键字 <code>package</code> 和 <code>object</code>,例如:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="pygments highlight"><code class="scala language-scala"><span class="tok-c1">// src/main/scala/com/garden/apples/package.scala</span>
<span class="tok-k">package</span> <span class="tok-nn">com.garden</span>
<span class="tok-k">package</span> <span class="tok-nn">object</span> <span class="tok-n">apples</span> <span class="tok-k">extends</span> <span class="tok-nc">RedApples</span> <span class="tok-k">with</span> <span class="tok-nc">GreenApples</span> <span class="tok-o">{</span>
<span class="tok-k">val</span> <span class="tok-n">redApples</span> <span class="tok-k">=</span> <span class="tok-nc">List</span><span class="tok-o">(</span><span class="tok-n">red1</span><span class="tok-o">,</span> <span class="tok-n">red2</span><span class="tok-o">)</span>
<span class="tok-k">val</span> <span class="tok-n">greenApples</span> <span class="tok-k">=</span> <span class="tok-nc">List</span><span class="tok-o">(</span><span class="tok-n">green1</span><span class="tok-o">,</span> <span class="tok-n">green2</span><span class="tok-o">)</span>
<span class="tok-o">}</span>
<span class="tok-k">trait</span> <span class="tok-nc">RedApples</span> <span class="tok-o">{</span>
<span class="tok-k">val</span> <span class="tok-n">red1</span><span class="tok-o">,</span> <span class="tok-n">red2</span> <span class="tok-k">=</span> <span class="tok-s">"red"</span>
<span class="tok-o">}</span>
<span class="tok-k">trait</span> <span class="tok-nc">GreenApples</span> <span class="tok-o">{</span>
<span class="tok-k">val</span> <span class="tok-n">green1</span><span class="tok-o">,</span> <span class="tok-n">green2</span> <span class="tok-k">=</span> <span class="tok-s">"green"</span>
<span class="tok-o">}</span>
</code></pre>
</div>
</div>
<div class="paragraph">
<p>通常人们会将 package object 放在一个名为 <code>package.scala</code> 的文件中,
再将该文件又放在这些对象所属的包下,例如上面的源文件路径和 package。</p>
</div>
<div class="paragraph">
<p>使用时,你会获得极大的好处,
这是因为当你导入这个 "package" 时,
你会导入定义在该 package 中的任何状态:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="pygments highlight"><code class="scala language-scala"><span class="tok-k">import</span> <span class="tok-nn">com.garden.apples._</span>
<span class="tok-n">redApples</span> <span class="tok-n">foreach</span> <span class="tok-n">println</span>
</code></pre>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="type-alias"><a class="anchor" href="#type-alias"></a>10. Type Alias</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Type alias 实际上并不是一种类型,而是一个能用来提高我们代码可读性的技巧:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="pygments highlight"><code class="scala language-scala"><span class="tok-k">type</span> <span class="tok-kt">User</span> <span class="tok-o">=</span> <span class="tok-nc">String</span>
<span class="tok-k">type</span> <span class="tok-kt">Age</span> <span class="tok-o">=</span> <span class="tok-nc">Int</span>
<span class="tok-k">val</span> <span class="tok-n">data</span><span class="tok-k">:</span> <span class="tok-kt">Map</span><span class="tok-o">[</span><span class="tok-kt">User</span>, <span class="tok-kt">Age</span><span class="tok-o">]</span> <span class="tok-k">=</span> <span class="tok-nc">Map</span><span class="tok-o">.</span><span class="tok-n">empty</span>
</code></pre>
</div>
</div>
<div class="paragraph">
<p>使用这个技巧,现在 Map 的定义一下变得"合理"了。
如果我们只是使用了一个 <code>String => Int</code> 类型的 map,那么我们会降低代码的可读性。
在这里,我们可以继续使用原始类型(也许我们需要原始类型的性能等),但是使用 Type Alias <strong>命名</strong>它们,便于以后的读者理解这个类。</p>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="icon-note" title="Note"></i>
</td>
<td class="content">
注意,当你为一个类创建别名时,不会将伴生对象关联在一起。例如,
假设你定义了一个 <code>case class Person(name: String)</code> 和一个别名类型 <code>User = Person</code>。
调用 <code>User("John")</code> 会导致<em>错误</em>,因为 <code>Person("John")</code> 会隐式调用 <code>Person</code> 伴生对象的 <code>apply</code> 方法,
但是伴生对象在这种情况下没有被定义别名,所以 <code>User("John")</code> 就报错。
</td>
</tr>
</table>
</div>
</div>
</div>
<div class="sect1">
<h2 id="abstract-type-member"><a class="anchor" href="#abstract-type-member"></a>11. Abstract Type Member</h2>
<div class="sectionbody">
<div class="paragraph">
<p>让我们进一步深入使用 Type Aliases,这样的用法称为 Abstract Type Members。</p>
</div>
<div class="paragraph">
<p>有了 Abstract Type Members,我们可以做到"定义一个抽象类型,并希望其他人告诉我具体类型 - 我们可以用 MyType 来引用这个类型"。
Abstract Type Members 最基础的功能是允许我们无需使用 <code>class Clazz[A, B]</code> 构建泛型类(模板),
我们可以在类中命名 abstract type member(s),例如:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="pygments highlight"><code class="scala language-scala"><span class="tok-k">trait</span> <span class="tok-nc">SimplestContainer</span> <span class="tok-o">{</span>
<span class="tok-k">type</span> <span class="tok-kt">A</span> <span class="tok-c1">// Abstract Type Member</span>
<span class="tok-k">def</span> <span class="tok-n">value</span><span class="tok-k">:</span> <span class="tok-kt">A</span>
<span class="tok-o">}</span>
</code></pre>
</div>
</div>
<div class="paragraph">
<p>对于熟悉 Java 的人来说,上面的语法看起来与 <code>Container<A></code> 类似,
但我们会在 <a href="#path-dependent-type">Path Dependent Types</a> 和下面的例子中看到 Abstract Type Members 功能更强大。</p>
</div>
<div class="paragraph">
<p>需要注意的是尽管 <code>type A</code> 的注释中包含 "abstract",但是它与抽象字段不同 - 所以即使我们不"实现"类型成员 A,也可以创建一个 SimplestContainer 实例:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="pygments highlight"><code class="scala language-scala"><span class="tok-k">new</span> <span class="tok-nc">SimplestContainer</span> <span class="tok-c1">// 合法,但是 A 是 "anything"</span>
</code></pre>
</div>
</div>
<div class="paragraph">
<p>你可能想知道 <code>A</code> 是什么类型,考虑到我们没有提供任何关于它的信息。事实上,<code>type A</code> 是 <code>type A >: Nothing <: Any</code> 的简写,意味着 A 可以是"任意类型(anything)"。</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="pygments highlight"><code class="scala language-scala"><span class="tok-k">object</span> <span class="tok-nc">IntContainer</span> <span class="tok-k">extends</span> <span class="tok-nc">SimplestContainer</span> <span class="tok-o">{</span>
<span class="tok-k">type</span> <span class="tok-kt">A</span> <span class="tok-o">=</span> <span class="tok-nc">Int</span>
<span class="tok-k">def</span> <span class="tok-n">value</span> <span class="tok-k">=</span> <span class="tok-mi">42</span>
<span class="tok-o">}</span>
</code></pre>
</div>
</div>
<div class="paragraph">
<p>因为我们使用 <a href="#type-alias">Type Alias</a> 提供了一个抽象类型,现在我们可以实现一个返回 <code>Int</code> 的方法。</p>
</div>
<div class="paragraph">
<p>当我们对 Abstract Type Members 应用类型约束时,事情变得更有趣了。
比如,想象你想要定义一个只能存储任意 <code>Number</code> 实例的容器。
我们可以在定义 type members 加上这样的约束:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="pygments highlight"><code class="scala language-scala"><span class="tok-k">trait</span> <span class="tok-nc">OnlyNumbersContainer</span> <span class="tok-o">{</span>
<span class="tok-k">type</span> <span class="tok-kt">A</span> <span class="tok-k"><:</span> <span class="tok-kt">Number</span>
<span class="tok-k">def</span> <span class="tok-n">value</span><span class="tok-k">:</span> <span class="tok-kt">A</span>
<span class="tok-o">}</span>
</code></pre>
</div>
</div>
<div class="paragraph">
<p>或者我们可以稍后在类层次结构中添加约束,例如通过混入声明 "only Numbers" 的特质:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="pygments highlight"><code class="scala language-scala"><span class="tok-k">trait</span> <span class="tok-nc">SimpleContainer</span> <span class="tok-o">{</span>
<span class="tok-k">type</span> <span class="tok-kt">A</span>
<span class="tok-k">def</span> <span class="tok-n">value</span><span class="tok-k">:</span> <span class="tok-kt">A</span>
<span class="tok-o">}</span>
<span class="tok-k">trait</span> <span class="tok-nc">OnlyNumbers</span> <span class="tok-o">{</span>
<span class="tok-k">type</span> <span class="tok-kt">A</span> <span class="tok-k"><:</span> <span class="tok-kt">Number</span>
<span class="tok-o">}</span>
<span class="tok-k">val</span> <span class="tok-n">ints</span> <span class="tok-k">=</span> <span class="tok-k">new</span> <span class="tok-nc">SimpleContainer</span> <span class="tok-k">with</span> <span class="tok-nc">OnlyNumbers</span> <span class="tok-o">{</span>
<span class="tok-k">type</span> <span class="tok-kt">A</span> <span class="tok-o">=</span> <span class="tok-nc">Integer</span>
<span class="tok-k">def</span> <span class="tok-n">value</span> <span class="tok-k">=</span> <span class="tok-mi">12</span>
<span class="tok-o">}</span>
<span class="tok-c1">// 下面的定义不能通过编译</span>
<span class="tok-k">val</span> <span class="tok-k">_</span> <span class="tok-k">=</span> <span class="tok-k">new</span> <span class="tok-nc">SimpleContainer</span> <span class="tok-k">with</span> <span class="tok-nc">OnlyNumbers</span> <span class="tok-o">{</span>
<span class="tok-k">def</span> <span class="tok-n">value</span> <span class="tok-k">=</span> <span class="tok-s">""</span> <span class="tok-c1">// error: type mismatch; found: String(""); required: this.A</span>
<span class="tok-o">}</span>
</code></pre>
</div>
</div>
<div class="paragraph">
<p>正如你所看到的,我们可以像使用 Type Parameters 一样使用 Abstract Type Members,
但是我们无需忍受显式四处传递类型的痛苦 - 因为类型是个字段,我们需要传递。
我们需要付出的代价就是按名称绑定这些类型。</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="self-recursive-type"><a class="anchor" href="#self-recursive-type"></a>12. Self-Recursive Type</h2>
<div class="sectionbody">
<div class="paragraph">
<p>在多数文献中,Self-recursive Types 被称为 <strong>F-Bounded Types</strong>,
所以你会发现许多文章或者博客都提到 "F-bounded"。
实际上,F-bounded 是 "self-resurive" 的另一个称呼,表示<em>子类型约束自身</em>被出现在类型参数左侧的一个 binders 参数化情况。
由于 self-recursive 含义更直观,所以我们会本小节中继续使用(而小节标题旨在帮助那些试图 google 什么是 "F-bounded" 的人)</p>
</div>
<div class="sect2">
<h3 id="f-bounded-type"><a class="anchor" href="#f-bounded-type"></a>12.1. F-Bounded Type</h3>
<div class="paragraph">
<p>虽然 self-resurive type 并不是 Scala 的特定类型,但是有时还是会引起一些注意。
对于许多人来说,一个熟悉(也有可能不知道)的 self-recursive type 例子是 Java 的 <code>Enum<E></code>,如果你对此感兴趣,
可以查看 <a href="http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/lang/Enum.java">Enum 源码</a>。
不过现在让我们回到 Scala ,首先看看我们实际上在讨论什么。</p>
</div>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<i class="icon-tip" title="Tip"></i>
</td>
<td class="content">
在本小节,我们不会深入讨论这一类型。
如果你对在 Scala 中深入使用 self-recursive type 感兴趣,可以看看 Kris Nuttycombe 的 <a href="http://logji.blogspot.se/2012/11/f-bounded-type-polymorphism-give-up-now.html">F-Bounded Type Polymorphism Considered Tricky</a>。
</td>
</tr>
</table>
</div>
<div class="paragraph">
<p>想象你有一个 <code>Fruit</code> 特质,<code>Apple</code> 和 <code>Orange</code> 类继承了这个特质。Fruit 特质还有一个 "compareTo" 方法,
现在问题来了:想象一下你想实现 "我不能用 oranges 与 apples 比较,因为它们是完全不同的东西!"。
首先让我们看看不考虑编译安全的最简单实现:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="pygments highlight"><code class="scala language-scala"><span class="tok-c1">// 最简单的实现,Fruit 没有被自递归参数化</span>
<span class="tok-k">trait</span> <span class="tok-nc">Fruit</span> <span class="tok-o">{</span>
<span class="tok-k">final</span> <span class="tok-k">def</span> <span class="tok-n">compareTo</span><span class="tok-o">(</span><span class="tok-n">other</span><span class="tok-k">:</span> <span class="tok-kt">Fruit</span><span class="tok-o">)</span><span class="tok-k">:</span> <span class="tok-kt">Boolean</span> <span class="tok-o">=</span> <span class="tok-kc">true</span> <span class="tok-c1">// 在我们的例子中实现不重要,我们只关心编译时</span>
<span class="tok-o">}</span>
<span class="tok-k">class</span> <span class="tok-nc">Apple</span> <span class="tok-k">extends</span> <span class="tok-nc">Fruit</span>
<span class="tok-k">class</span> <span class="tok-nc">Orange</span> <span class="tok-k">extends</span> <span class="tok-nc">Fruit</span>
<span class="tok-k">val</span> <span class="tok-n">apple</span> <span class="tok-k">=</span> <span class="tok-k">new</span> <span class="tok-nc">Apple</span><span class="tok-o">()</span>
<span class="tok-k">val</span> <span class="tok-n">orange</span> <span class="tok-k">=</span> <span class="tok-k">new</span> <span class="tok-nc">Orange</span><span class="tok-o">()</span>
<span class="tok-n">apple</span> <span class="tok-n">compareTo</span> <span class="tok-n">orange</span> <span class="tok-c1">// 编译成功,但我们希望这句话不能通过编译</span>
</code></pre>
</div>
</div>
<div class="paragraph">
<p>在上面朴素的实现中,因为 <code>Fruit</code> 特质不知道任何继承它的类的线索,所以不能限制 compareTo 的签名使得其只能接受"<em>与 <code>this</code> 相同的子类</em>(<em>the same subclass as</em> <code>this</code>)"的参数。
让我们使用 <strong>Self Recursive Type Parameter</strong> 重写这个例子:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="pygments highlight"><code class="scala language-scala"><span class="tok-k">trait</span> <span class="tok-nc">Fruit</span><span class="tok-o">[</span><span class="tok-kt">T</span> <span class="tok-k"><:</span> <span class="tok-kt">Fruit</span><span class="tok-o">[</span><span class="tok-kt">T</span><span class="tok-o">]]</span> <span class="tok-o">{</span>
<span class="tok-k">final</span> <span class="tok-k">def</span> <span class="tok-n">compareTo</span><span class="tok-o">(</span><span class="tok-n">other</span><span class="tok-k">:</span> <span class="tok-kt">Fruit</span><span class="tok-o">[</span><span class="tok-kt">T</span><span class="tok-o">])</span><span class="tok-k">:</span> <span class="tok-kt">Boolean</span> <span class="tok-o">=</span> <span class="tok-kc">true</span> <span class="tok-c1">// 在我们的例子中实现并不重要</span>
<span class="tok-o">}</span>
<span class="tok-k">class</span> <span class="tok-nc">Apple</span> <span class="tok-k">extends</span> <span class="tok-nc">Fruit</span><span class="tok-o">[</span><span class="tok-kt">Apple</span><span class="tok-o">]</span>
<span class="tok-k">class</span> <span class="tok-nc">Orange</span> <span class="tok-k">extends</span> <span class="tok-nc">Fruit</span><span class="tok-o">[</span><span class="tok-kt">Orange</span><span class="tok-o">]</span>
<span class="tok-k">val</span> <span class="tok-n">apple</span> <span class="tok-k">=</span> <span class="tok-k">new</span> <span class="tok-nc">Apple</span>
<span class="tok-k">val</span> <span class="tok-n">orange</span> <span class="tok-k">=</span> <span class="tok-k">new</span> <span class="tok-nc">Orange</span>
</code></pre>
</div>
</div>
<div class="paragraph">
<p>注意到 <code>Fruit</code> 签名中的类型参数。你可以读作"我接受一个类型参数 <code>T</code>,并且 <code>T</code> 必须是一个 <code>Fruit[T]</code>",
要满足这样要求的唯一方法是像类 <code>Apple</code> 和 <code>Orange</code> 那样继承这个特质。
现在,如果我们尝试将 <code>apple</code> 与 <code>orange</code> 比较,我们会得到一个编译错误:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="pygments highlight"><code class="repl language-repl">scala> orange compareTo apple
<console>:13: error: type mismatch;
found : Apple
required: Fruit[Orange]
orange compareTo apple
scala> orange compareTo orange
res1: Boolean = true</code></pre>
</div>
</div>
<div class="paragraph">
<p>现在我们能确定我们只能将 apples 与 apples 比较,其他水果与同类型的 Fruit(<em>子类</em>)比较。
当然这里还有更多需要讨论的 - 我们能将 <code>Apple</code> 和 <code>Orange</code> 的子类分别与 <code>Apple</code> 和 <code>Orange</code> 比较吗?
因为在类型层次中实现 <code>Apple</code> 和 Orange 时,我们填入的类型参数分别是 <code>Apple</code> 和 <code>Orange</code>,
我们的意思是 <code>Apple</code> 只能与 <code>Apple</code> 比较,这也意味着 <code>Apple</code> 的子类可以互相比较 -
这依然满足 Fruit 签名的 <code>compareTo</code> 限制,因为现在我们的调用右边是比 <code>Fruit[Apple]</code> 更具体的类型。
比如,让我们尝试将一个日本 apple(ja. "りんご", "ringo")和一个波兰 apple(pl. "Jabłuszko")比较:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="pygments highlight"><code class="scala language-scala"><span class="tok-k">object</span> <span class="tok-nc">`りんご`</span> <span class="tok-k">extends</span> <span class="tok-nc">Apple</span>
<span class="tok-k">object</span> <span class="tok-nc">Jabłuszko</span> <span class="tok-k">extends</span> <span class="tok-nc">Apple</span>
<span class="tok-n">`りんご`</span> <span class="tok-n">compareTo</span> <span class="tok-nc">Jabłuszko</span>
<span class="tok-c1">// true</span>
</code></pre>
</div>
</div>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<i class="icon-tip" title="Tip"></i>
</td>
<td class="content">
你可以使用更花哨的技巧实现同样的类型安全,比如 path dependent types 或者 implicit parameters 和 type classes。但是这里最简单的办法是使用 self-recursively type。
</td>
</tr>
</table>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="type-constructor-span-style-color-red-span"><a class="anchor" href="#type-constructor-span-style-color-red-span"></a>13. Type Constructor <span style="color:red">✗</span></h2>
<div class="sectionbody">
<div class="paragraph">
<p>Type Constructor 的作用非常类似于函数,但是作用在类型级别。也就是说,在普通编程中,你可以有一个函数,接受一个值 <code>a</code> 并基于这个值返回另一个值 <code>b</code>;
那么在类级别编程(type-level programming)中,你可以将 List[+A] 看成一个有类似功能的 type constructor:</p>
</div>
<div class="ulist">
<ul>
<li>
<p><code>List[+A]</code> 接受一个类型参数(<code>A</code>),</p>