-
-
Notifications
You must be signed in to change notification settings - Fork 5
/
grokking-monad-zh.tex
1973 lines (1532 loc) · 74.8 KB
/
grokking-monad-zh.tex
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
% Created 2021-02-15 Mon 18:18
% Intended LaTeX compiler: xelatex
\documentclass[letterspacing]{tufte-book}
\usepackage{graphicx}
\usepackage{longtable}
\usepackage{wrapfig}
\usepackage{rotating}
\usepackage[normalem]{ulem}
\usepackage{amsmath}
\usepackage{textcomp}
\usepackage{amssymb}
\usepackage{capt-of}
\usepackage{hyperref}
\usepackage{ucs}
\usepackage{xeCJK}
\setCJKmainfont{STXihei}
\setkeys{Gin}{width=\linewidth,totalheight=\textheight,keepaspectratio}
\usepackage{booktabs} % book-quality tables
\usepackage{units} % non-stacked fractions and better unit spacing
\usepackage{multicol} % multiple column layout facilities
\usepackage{fancyvrb} % extended verbatim environments
\fvset{fontsize=\normalsize}% default font size for fancy-verbatim environments
\usepackage{listings}
\lstset{basicstyle=\ttfamily\normalsize,breaklines=false,frame=l}
\setcounter{secnumdepth}{3}
\author{欧阳继超}
\date{\textit{<2017-02-10 Fri>}}
\title{Grokking Monad\\\medskip
\large 函数式装逼手册}
\hypersetup{
pdfauthor={欧阳继超},
pdftitle={Grokking Monad},
pdfkeywords={},
pdfsubject={},
pdfcreator={Emacs 27.1 (Org mode 9.3)},
pdflang={English}}
\begin{document}
\maketitle
\tableofcontents
\newpage
\begin{fullwidth}
~\vfill
\thispagestyle{empty}
\setlength{\parindent}{0pt}
\setlength{\parskip}{\baselineskip}
Copyright \copyright\ 2015-\the\year\ Jichao Ouyang
Printable is generated from free softwares: GNU Emacs, orgmode, Tex, Graphivz DOT...
\par\url{github.com/jcouyang/grokking-monad}
\par\textit{First printing, 2020}
\end{fullwidth}
\chapter*{前言}
本书的主要目的是为了解释 \textbf{为什么需要*, *如何理解} 以及 \textbf{如何使用} Monad,
分为三大个部分,猫论/Catergory Theory,食用猫呢/Practical Monads和搞基猫呢/Advanced Monads。\footnote{什么?你不喜欢谐音梗?我也不喜欢,可是,这也不是讲脱口秀的书啊。 \sout{再说你也就花了六美元,十分适合这种廉价梗}}
\begin{quote}
\textbf{猫论/Catergory Theory} 是理论基础,解释单子由何而来,若是 \sout{不想装逼装得有理有据} 觉得太无聊其实可跳过,比较适合好奇心大的猫。
\textbf{食用猫呢/Practical Monads} 提供很多日常会遇到的单子供大家食用。
\textbf{搞基猫呢/Advanced Monads} 适合谁你自然懂得。
\end{quote}
其中所有例子都有双语 \sout{中文和英语} \emph{Haskell} 和 \emph{Scala} 解释,双语例子都成对出现,先 Haskell 后 Scala。
至于为什么选择这两种语言?其实代表两大派系,一个ML系一个Java/C++系,若是看不懂 Haskell,Scala 可能
会更好懂些。
当掌握了这种思维方式,不局限于 Haskell 或者 Scala,其实可以扩展到任何语言的编程中,即使是没有类型系统的语言,JS,说你呢,不要看人家PHP。
\begin{quote}
那么,为啥要花钱买一本不是正规出版社的书?
\end{quote}
许多年前,当我还是年少 \sout{有为} 无知的时候,有个很正规的出版社叫我写过一本书\footnote{\url{https://book.douban.com/subject/26883736/}}。
当我听说写书是按字数给钱的时候,我的程序员世界观崩塌了那么一会。
什么 DRY\footnote{不要重复(Don't Repeat Yourself) \url{https://en.wikipedia.org/wiki/Don\%27t\_repeat\_yourself}},什么 YAGNI\footnote{你可能不会需要的(You Aren't Gonna Need It) \url{https://en.wikipedia.org/wiki/You\_aren\%27t\_gonna\_need\_it}},我统统都需要,而且能重复说的概念绝对不能一次说清楚了。
那段时光里 Emacs 那熟练的快捷键 \texttt{Ctrl y} 简直就是我的人民币印钞机。
于是我东拼西凑,从我的博客抄了好多字。结果豆瓣才6.6分,居然只有几个人说我是抄的, \sout{真是的,读书人,怎么能说抄呢?} 显然读者都不怎么看我的博客。
再说了, \sout{我抄你家书了吗?} 其实书上可比我的博客精致多了,看,我还加了好多萌萌的插画呢。字不算钱,画也多少算点吧。而且,老子写博客的时间不要钱吗?
呵呵,确实不要。
显然第一版还滞销,出版社也再没联系我第二版的版税💰的事情。就这么投入一年的时间加工精美博客以获得利润的完美赚钱计划,结果还不如我打工一天赚的钱多。
不过不叫确实好怪我,目标读者是前端,内容显然过于 \sout{超前} 乏味了些。但是卖不掉我还真是严重怀疑是因为出版社设计的书皮太难看。
你看看那橘色的封面,跟 Emmet\footnote{Emmet Brickowski:乐高大电影里的一名普通建筑工人。咦,普通工人怎么有wiki?我都没有 \url{https://thelegomovie.fandom.com/wiki/Emmet\_Brickowski}} 的工服一个颜色,你很难想象这不是一本建筑工程师的书。
我要是在图书馆看见这本书,我都怀疑自己走错了到了建筑装潢区,对啊我本来是来借啥书来着?我在哪?我是谁?
你看,这种水平的前言,出版社肯定不乐意要让我改,但是我乐意啊,啊哈哈哈哈。
哦,对,还没说这本书是啥情况呢。其实这本书,它…也是从我的同名博客\footnote{\url{https://blog.oyanglul.us/grokking-monad/part1}}抄的…
但其\ldots{}实我还是有加入更多的内容\footnote{比如,你看,博客里就没有这篇前言。},更重要的是这个超有设计感的封面。
你想想,六美元\footnote{啊,那为啥是以美元为单位?那是因为 Gumroad 是按美元给我结算,这样我可以挑选合适的的时候换算成澳元或者人民币花,啊哈哈哈。}可以买两瓶可乐,六美元能买一本儿童涂色书,六美元能买二分之一顿饭。
你少喝两瓶可乐,让你娃少涂一本涂色书,你少吃半顿饭,买了这本书,把里面的词汇都背下来,在同事领导面前时不时冒出一两个来装逼,升值加薪不是梦。
或者,打印出来但千万不要装订,抱在怀里从女神男神面前走过时不小心撞一下顺势往天上这么一撒,女神男神在慌乱中一起帮你捡地上散落的书时,肯定会惊叹:哇,这人好厉害居然知道Monad是什么?
再加上刚才撞一下引起的心跳加速,对你的好感油然而生。
只是少喝两瓶可乐,少涂一本涂色书,少吃半顿饭省下来的六美元\footnote{你看我这写上本书落下的 \texttt{Ctrl y} 毛病。},就能提前助你走上人生巅峰的感觉,难道不六吗?
\part{猫论/Catergory Theory}
\label{sec:orga332599}
\begin{center}
\includegraphics[width=.9\linewidth]{./images/Cheshire_Cat.png}
\end{center} \footnote{\url{https://en.wikipedia.org/wiki/Cheshire\_Cat}}
\begin{quote}
`But I don’t want to go among mad people,’ Alice remarked.
`Oh, you can’t help that,’ said the Cat: `we’re all mad here. I’m mad. You’re mad.’
`How do you know I’m mad?’ said Alice.
`You must be,’ said the Cat, `or you wouldn’t have come here.’
Alice didn’t think that proved it at all; however, she went on `And how do you know that you’re mad?’
-- Alice's Adventures in Wonderland
\end{quote}
\begin{quote}
单子/Monad是什么? 你也不懂, 我也不懂, 我们都不懂.
话说, 我又怎么知道你不懂呢?
当然不懂, 不然, 你怎么会来到这里?
我又是怎么知道自己不懂呢?
因为,我知道懂的人是什么样子. 显然, 我不是.
因为,懂的人一定知道猫论/Category Theory.
\end{quote}
这一部分主要是纯理论,这里面有很多很装的单词,比如 \emph{单子/Monad/,它们都是 /斜体}
,就算一遍没看懂\footnote{可以继续看第二部分,看完概念是如何在现实中实现的,再回来看一遍,会感觉好很多。},把这些词背下来也足够装好长一阵子逼了。
这里还有很多代码, 它们都成对出现, 通常第一段是 Haskell, 第二段是 Scala 3.\footnote{为什么用两种语言呢?第一: \sout{这样代码量会翻倍,可以凑篇幅字数。} 这样大家会熟悉多种语言对同一概念的诠释,从而举一反三。
第二:读者受众会大一点,因为毕竟Haskell的表述比较简洁,有可能很容易理解,但是跟主流语言的表达方式大为不同,也有可能很难适应,加上表达方式更为具体的 Scala,便于加深理解。}
\chapter{范畴/Category}
\label{sec:orge141111}
\index{Catergory}
\index{范畴}
对于计算机科学的学生,范畴并不是一个新的概念,在本科大纲里,大家都应该学过 \emph{离散数学(Discrete Mathematics)} ,其中会讲很多 \emph{集合论(Set Theory)} 图论,抽象代数的东西。
现在回头看看,其实也就是集合论和抽象代数的内容。
所以下面的概念都点到为止,只为解释写成代码会长什么样。
一个 \emph{范畴/Category} 包含两个玩意:
\begin{itemize}
\item 东西 \texttt{O} (Object)
\item 两个东西的关系,箭头 \texttt{\textasciitilde{}>} ( \emph{态射/Morphism} )
\end{itemize}
还必须带上一些属性:
\begin{itemize}
\item 一定有一个叫 id 的箭头,也叫做 1
\item 箭头可以 \emph{组合} compose/
\end{itemize}
恩, 就是这么简单!
\begin{figure}[htbp]
\centering
\includegraphics[width=.9\linewidth]{images/category.png}
\caption{有东西 a, b, c 和箭头 f, g 的 Category,其中 f . g 表示 compose f 和 g}
\end{figure}
\begin{quote}
注意到为什么我会箭头从右往左,接着看代码, 你会发现这个方向跟 compose 的方向刚好一致!
\end{quote}
这些玩意对应到 Haskell 的 Typeclass 大致就是这样:
\lstset{language=haskell,label= ,caption={Category definition in Haskell},captionpos=b,numbers=none}
\begin{lstlisting}
class Category (c :: * -> * -> *) where
id :: c a a
(.) :: c y z -> c x y -> c x z
\end{lstlisting}
如果这是你第一次见到 Haskell 代码,没有关系,语法真的很简单:
\begin{itemize}
\item \texttt{class} 定义了一个 TypeClass, \texttt{Category} 是这个 TypeClass 的名字
\item Type class 类似于定义类型的规范,规范为 \texttt{where} 后面那一坨
\item 类型规范的对象是参数 \texttt{(c:: * -> * -> *)} , \texttt{::} 后面是c的类型
\item c 是 \emph{higher kind} \texttt{* -> *} ,跟higher order function的定义差不多,它是接收类型,构造新类型的类型。这里的 c 接收一个类型,再接收一个类型,就可以返回个类型。
\end{itemize}
\index{Kind}
\begin{itemize}
\item \texttt{id:: c a a} 表示 c 范畴上的 a 到 a 的箭头
\item \texttt{.} 的意思 c 范畴上,如果喂一个 y 到 z 的箭头,再喂一个 x 到 y 的箭头,那么就返回 x 到 z 的箭头。
\end{itemize}
而 Scala 可以用 trait 来表示这个 typeclass:
\lstset{language=scala,label= ,caption={Category definition in Scala},captionpos=b,numbers=none}
\begin{lstlisting}
trait Category[C[_, _]] {
def id[A]: C[A, A]
def <<<(a: C[Y, Z], b: C[X, Y]): C[X, Z]
}
\end{lstlisting}
如果这是你第一次见到 Scala 代码,没关系,从Haskell可以飞快的切换过来:
\begin{itemize}
\item \texttt{class} -> \texttt{trait}
\item[{=c}] * -> * -> *= -> \texttt{C[\_, \_]}
\item \texttt{::} -> \texttt{:}
\item 函数名前加 \texttt{def}
\end{itemize}
另外 compose 在 haskell 中直接是句号 \texttt{.}
scala 中用习惯用 \texttt{<<<} 或者 \texttt{compose}
总之,我们来用文字再读一遍上面这些代码就了然了.
范畴 C 其实就包含:
\begin{enumerate}
\item 返回 A 对象到 A 对象的 id 箭头
\item 可以组合 Y 对象到 Z 对象 和 X 对象到 Y 对象的箭头 compose
\end{enumerate}
简单吧?还没有高数抽象呢。
\section{\emph{Hask}}
\label{sec:org2bda925}
Haskell 类型系统范畴叫做 Hask。
\index{Hask}
在 Hask 范畴上:
\begin{itemize}
\item 东西就是类型
\item 箭头是类型的变换,即 \texttt{->}
\item id 就是 id 函数的类型 \texttt{a -> a}
\item compose 当然就是函数组合的类型
\end{itemize}
\lstset{language=haskell,label= ,caption= ,captionpos=b,numbers=none}
\begin{lstlisting}
type Hask = (->)
instance Category (Hask:: * -> * -> *) where
id a = a
(f . g) x = f (g x)
\end{lstlisting}
我们看见新的关键字 \texttt{instance} ,这表示 Hask 是 Type class Category 的实例类型,也就是说对任意Hask类型, 那么就能找到它的 id 和 compose
\lstset{language=scala,label= ,caption= ,captionpos=b,numbers=none}
\begin{lstlisting}
given Category[=>[_, _]] {
def id[A]: A => A = identity[A]
def <<<[X, Y, Z](a: Y => Z, b: X => Y) = a compose b
}
\end{lstlisting}
Scala 中, 只需要 new 这个 trait 就可以实现这个 typeclass
其中: identity \texttt{Hask a a} 就是
\lstset{language=haskell,label= ,caption= ,captionpos=b,numbers=none}
\begin{lstlisting}
(->) a a -- or
a -> a -- 因为 -> 是中缀构造器
\end{lstlisting}
\lstset{language=scala,label= ,caption= ,captionpos=b,numbers=none}
\begin{lstlisting}
A => A
\end{lstlisting}
\section{\emph{Duel}}
\label{sec:org5481e33}
\index{Duel}
每个 Category 还有一个镜像,什么都一样,除了箭头是反的。
\chapter{函子 / Functor}
\label{sec:orge4cfc5a}
\index{Functor}
\index{函子}
两个范畴中间可以用叫 Functor 的东西来连接起来,比如一个从范畴 C 到范畴 D 的函子 T,我们可以标
作 \texttt{Functor C D T} 。
\#+Functor Category
\begin{center}
\includegraphics[width=.9\linewidth]{images/functor.png}
\end{center}
所以大部分把函子或者单子比喻成盒子其实在定义上是错的,虽然这样比喻比较容易理解,在使用上问题也不大。但是,函子只是从一个范畴到另一个范畴的箭头而已。
\begin{itemize}
\item 范畴间东西的函子标记为 \texttt{T(O)}
\item 范畴间箭头的函子标记为 \texttt{T(\textasciitilde{}>)}
\item 任何范畴 C 上存在一个 T 把所有的 O 和 \textasciitilde{}> 都映射到自己,标记为函子 1\textsubscript{C}
\begin{itemize}
\item 1\textsubscript{C}(O) = O
\item 1\textsubscript{C}(\textasciitilde{}>) = \textasciitilde{}>
\end{itemize}
\end{itemize}
\lstset{language=haskell,label= ,caption={函子的 Haskell 定义},captionpos=b,numbers=none}
\begin{lstlisting}
class (Category c, Category d) => Functor c d t where
fmap :: c a b -> d (t a) (t b)
\end{lstlisting}
\lstset{language=scala,label= ,caption={函子的 Scala 定义},captionpos=b,numbers=none}
\begin{lstlisting}
trait Functor[C[_, _], D[_, _], T[_]]:
def fmap[A, B](c: C[A, B]): D[T[A], T[B]]
\end{lstlisting}
\texttt{Functor c d t} 这表示从范畴 c 到范畴 d 的一个 Functor t
如果把范畴 c 和 d 都限制到 Hask 范畴:
\lstset{language=haskell,label= ,caption= ,captionpos=b,numbers=none}
\begin{lstlisting}
class Functor (->) (->) t where
fmap :: (->) a b -> (->) (t a) (t b)
\end{lstlisting}
\lstset{language=scala,label= ,caption= ,captionpos=b,numbers=none}
\begin{lstlisting}
trait Functor[=>[_, _], =>[_, _], T[_]]:
def fmap[A, B](c: =>[A, B]): =>[T[A], T[B]]
\end{lstlisting}
\texttt{->} 或者 \texttt{=>} 可以写在中间的:
这样就会变成我们熟悉的函子定义:\footnote{这里可以把 Functor 的第一第二个参数消掉, 因为已经知道是在 Hask 范畴了}
\lstset{language=haskell,label= ,caption= ,captionpos=b,numbers=none}
\begin{lstlisting}
class Functor t where
fmap :: (a -> b) -> (t a -> t b)
\end{lstlisting}
\lstset{language=scala,label= ,caption= ,captionpos=b,numbers=none}
\begin{lstlisting}
trait Functor[T[_]]:
def fmap[A, B](c: A => B): T[A] => T[B]
\end{lstlisting}
而 \emph{自函子/endofunctor} 就是这种连接相同范畴的 Functor,因为它从范畴 Hask 到达同样的范畴 Hask。
\index{endofunctor}
\index{自函子}
这回看代码就很容易对应上图和概念了, 这里的自函子只是映射范畴 \texttt{->} 到 \texttt{->}, 箭头函数那个箭头, 类型却变成了 \texttt{t a} 。
这里的 fmap 就是 T(\textasciitilde{}>),在 Hask 范畴上,所以是 T(->), 这个箭头是函数,所以也能表示成 T(f) 如果 \texttt{f:: a -> b}
\chapter{Cat/猫}
\label{sec:orgb23b488}
递归的, 当我们可以把一个范畴看成一个对象,函子看成箭头的话,那么我们又得到了一个新的范畴,这种对象是范畴箭头是函子的范畴我们叫它 -- \emph{Cat/猫} 。
已经没/meow的办法用语言描述这么高维度的事情了,请回忆\label{org0420545}并把 C 和 D 想象成点。
\chapter{自然变换 / Natural Transformations \label{org45b15a0}}
\label{sec:org7bc7d92}
函子是范畴间的映射,所以如果我们现在又把 Cat 范畴看成是对象, 那 Cat 范畴之间的箭头,其实就是函子的函子,
又升维度了,我们有个特殊的名字给它,叫 \sout{喵的变换} \emph{自然变换/Natural Transformations} 。
\index{Natural Transformations}
\index{自然变换}
\begin{figure}[htbp]
\centering
\includegraphics[width=.9\linewidth]{images/natrual-transformation.png}
\caption[Functor G \(\eta\)]{Functor F 和 G 以及 F 到 G 的自然变化}
\end{figure}
范畴 c 上的函子 f 到 g 的自然变化就可以表示成:
\lstset{language=haskell,label= ,caption= ,captionpos=b,numbers=none}
\begin{lstlisting}
type Nat c f g = c (f a) (g a)
\end{lstlisting}
Scala 3 的 rank n types\footnote{\url{https://blog.oyanglul.us/scala/dotty/en/rank-n-type} 别急, 后面马上讲到} 也很简洁:
\lstset{language=scala,label= ,caption= ,captionpos=b,numbers=none}
\begin{lstlisting}
type Nat[C[_,_],F[_],G[_]] = [A] => C[F[A], G[A]]
\end{lstlisting}
如果换到 Hask 范畴上的自然变化就变成了:
\lstset{language=haskell,label= ,caption= ,captionpos=b,numbers=none}
\begin{lstlisting}
type NatHask f g = f a -> g a
\end{lstlisting}
\lstset{language=scala,label= ,caption= ,captionpos=b,numbers=none}
\begin{lstlisting}
type Nat[F[_],G[_]] = [A] => F[A] => G[A]
\end{lstlisting}
这就是 Scala 中常见的 FunctionK\footnote{\url{https://blog.oyanglul.us/scala/dotty/en/functionk}}。
恭喜你到达 Functor 范畴.
当然, 要成为范畴,还有两个属性:
\begin{itemize}
\item id 为 f a 到 f a 的自然变换
\item 自然变换的组合
\end{itemize}
\begin{center}
\includegraphics[width=.9\linewidth]{images/functor-category.png}
\end{center}
别着急, 我们来梳理一下,如果已经不知道升了几个维度了,我们假设类型所在范畴是第一维度
\begin{itemize}
\item 一维: Hask, 东西是类型,箭头是 ->
\item 二维: Cat, 东西是 Hask, 箭头是 Functor
\item 三维: Functor范畴, 东西是Functor, 箭头是自然变换
\end{itemize}
感觉到达三维已经是极限了,尼玛还有完没完了,每升一个维度还要起这么多装逼的名字,再升维度老子就画不出来了。
所以,是时候引入真正的技术了 -- String Diagram。
\chapter{String Diagram}
\label{sec:orga9ad355}
String Diagram\footnote{\url{https://www.youtube.com/watch?v=kiXjcqxVogE\&list=PL50ABC4792BD0A086\&index=5}} 的概念很简单,就是点变线线变点。
还记得当有了自然变换之后,三个维度已经没法表示了,那原来的点和线都升一维度,变成线和面,这样,就腾出一个点来表示自然变换了。
\begin{figure}[htbp]
\centering
\includegraphics[width=.9\linewidth]{images/p1-string-diagram.png}
\caption{String Diagram:自然变换是点,函子是线,范畴是面,自然变换是点}
\end{figure}
组合(compose)的方向是从右往左,从下到上。
阅读起来,你会发现左右图给出的信息是完全等价的:
\begin{enumerate}
\item 范畴 E 通过 函子 D 到范畴 D,范畴 D 通过函子 F 到范畴 C
\item 范畴 E 通过 函子 E 到范畴 C
\item F . G 通过自然变换 \(\alpha\) 到 H
\end{enumerate}
\chapter{Adjunction Functor 伴随函子}
\label{sec:orgda3fd5f}
\index{Adjunction Functor}
伴随函子是范畴 C 和 D 之间有来有回的函子,为什么要介绍这个,因为它直接可以推出单子。
让我们来看看什么叫有来回。
\begin{center}
\includegraphics[width=.9\linewidth]{images/p1-adjunction-functor.png}
\end{center}
其中:
\begin{itemize}
\item 图右:一个范畴 C 可以通过函子 G 到范畴 D,再通过函子 F 回到 C,那么 F 和 G 就是伴随函子。
\item 图中:范畴 C 通过函子组合 F . G 回到范畴 C,函子 G . F 通过自然变换 \(\eta\) 到函子 1\textsubscript{D}
\item 图左:范畴 D 通过函子组合 G . F 回到范畴 D,函子 1\textsubscript{C} 通过自然变化 \(\epsilon\) 到函子 F . G
\end{itemize}
同时根据同构的定义,G 与 F 是 \emph{同构} 的。
\index{isomorphic}
\index{同构}
同构指的是若是有
\lstset{language=haskell,label= ,caption= ,captionpos=b,numbers=none}
\begin{lstlisting}
f :: a -> b
f':: b -> a
\end{lstlisting}
那么 f 与 f' 同构,因为 \texttt{f . f' = id = f' . f}
伴随函子的 F . G 组合是 C 范畴的 id 函子 \texttt{F . G = 1\_c}
\begin{figure}[htbp]
\centering
\includegraphics[width=.9\linewidth]{images/p1-ajunction-functor-compose.png}
\caption{伴随函子的两个Functor组合, 左侧记为 F eta, 右侧记为 epsilon F}
\end{figure}
注意看坐标,该图横着组合表示函子组合,竖着是自然变换维度,因此是自然变换的组合。
\begin{figure}[htbp]
\centering
\includegraphics[width=.9\linewidth]{images/p1-ajunction-functor-compose-nat.png}
\caption{eta . epsilon = F -> F}
\end{figure}
当组合两个自然变换 \(\eta\) . \(\epsilon\) 得到一个弯弯曲曲的 F 到 F 的线时,我们可以拽着 F 的两端一拉,就得到了直的 F 线。
String Diagram 神奇的地方是所有线都可以拉上下两端,因为线不管是弯的还是直的,包含的信息并不会发生变化。
这个技巧非常有用,在之后的单子推导还需要用到。
\chapter{从伴随函子到 单子/Monad}
\label{sec:orga815b6c}
有了伴随函子,很容易推出单子,让我们先来看看什么是单子:
\begin{itemize}
\item 首先,它是一个自函子(endofunctor) T
\item 有一个从 i\textsubscript{c} 到 T 的自然变化 \(\eta\) (eta)
\item 有一个从 T\textsuperscript{2} 到 T 的自然变化 \(\mu\) (mu)
\end{itemize}
\begin{center}
\includegraphics[width=.9\linewidth]{images/p1-monad-properties.png}
\end{center}
\lstset{language=haskell,label= ,caption= ,captionpos=b,numbers=none}
\begin{lstlisting}
class Endofunctor c t => Monad c t where
eta :: c a (t a)
mu :: c (t (t a)) (t a)
\end{lstlisting}
\lstset{language=scala,label= ,caption= ,captionpos=b,numbers=none}
\begin{lstlisting}
trait Monad[C[_, _], T[_]]] extends Endofunctor[C, T]:
def eta[A]: C[A, T[A]]
def mu[A]: C[T[T[A]], T[A]]
\end{lstlisting}
同样,把 c = Hask 替换进去,就得到更类似我们 Haskell 中 Monad 的定义
\lstset{language=haskell,label= ,caption= ,captionpos=b,numbers=none}
\begin{lstlisting}
class Endofunctor m => Monad m where
eta :: a -> (m a)
mu :: m m a -> m a
\end{lstlisting}
\lstset{language=scala,label= ,caption= ,captionpos=b,numbers=none}
\begin{lstlisting}
trait Monad[M[_]] extends Endofunctor[M]:
def eta[A]: A => M[A]
def mu[A]: M[M[A]] => M[A]
\end{lstlisting}
要推出单子的 \(\eta\) 变换,只需要让 FG = T。可以脑补一下,因为是自函子,因此可以抹掉 D,
想象一下,当 D 这一块面被拿掉之后,线 F 和线 G 是不是就贴在一起了呢?两根贴着的线,不就是一根线吗?
\begin{figure}[htbp]
\centering
\includegraphics[width=.9\linewidth]{images/p1-ajunction-functor-to-monad-eta.png}
\caption{伴随函子的 epsilon 就是单子的 eta}
\end{figure}
同样的,当 FG = T, 也就是把 D 这陀给抹掉,F 和 G 就变成了 T。
\begin{figure}[htbp]
\centering
\includegraphics[width=.9\linewidth]{images/p1-ajunction-functor-to-monad-mu.png}
\caption{伴随函子的 F eta G 是函子的 mu}
\end{figure}
\section{三角等式}
\label{sec:org2d6499d}
三角等式是指 \(\mu\) . T \(\eta\) = T = \(\mu\) . \(\eta\) T
要推出三角等式只需要组合 F \(\eta\) G 和 \(\epsilon\) F G
\begin{figure}[htbp]
\centering
\includegraphics[width=.9\linewidth]{images/p1-adjunction-functor-triangle.png}
\caption{F eta G . epsilon F G = F G}
\end{figure}
\begin{figure}[htbp]
\centering
\includegraphics[width=.9\linewidth]{images/p1-monad-triangle.png}
\caption{F eta G . epsilon F G= F G 对应到Monad就是 mu . eta T = T}
\end{figure}
换到代码上来说
\lstset{language=haskell,label= ,caption= ,captionpos=b,numbers=none}
\begin{lstlisting}
(mu . eta) m = m
\end{lstlisting}
同样的,左右翻转也成立
\begin{figure}[htbp]
\centering
\includegraphics[width=.9\linewidth]{images/p1-adjunction-functor-triangle-reverse.png}
\caption{F eta G . F G epsilon = F G}
\end{figure}
\begin{figure}[htbp]
\centering
\includegraphics[width=.9\linewidth]{images/p1-monad-triangle-reverse.png}
\caption{F eta G . F G epsilon = F G 对应到 Monad是 mu . T eta = T}
\end{figure}
T \(\eta\) 就是 fmap eta
\lstset{language=haskell,label= ,caption= ,captionpos=b,numbers=none}
\begin{lstlisting}
(mu . fmap eta) m = m
\end{lstlisting}
如果把 \texttt{mu . fmap} 写成 \texttt{>>=} , 就有了
\lstset{language=haskell,label= ,caption= ,captionpos=b,numbers=none}
\begin{lstlisting}
m >>= eta = m
\end{lstlisting}
\section{结合律}
\label{sec:org3ad2f67}
单子另一大定律是结合律,让我们从伴随函子推起
假设我们现在有函子 F \(\eta\) G 和 函子 F \(\eta\) G F G, compose 起来会变成 F \(\eta\) G . F \(\eta\) G F G
\begin{center}
\includegraphics[width=.9\linewidth]{images/p1-ajunction-functor-monad-laws-1.png}
\end{center}
用 F G = T , F \(\eta\) G = \(\mu\) 代换那么就得到了单子的 \(\mu\) . \(\mu\) T
\begin{center}
\includegraphics[width=.9\linewidth]{images/p1-ajunction-functor-monad-laws-2.png}
\end{center}
当组合 F \(\eta\) G 和 F G F \(\mu\) G 后,会得到一个镜像的图
\begin{center}
\includegraphics[width=.9\linewidth]{images/p1-ajunction-functor-monad-laws-3.png}
\end{center}
对应到单子的 \(\mu\) . T \(\mu\)
结合律是说 \(\mu\) . \(\mu\) T = \(\mu\) . T \(\mu\) , 即图左右翻转结果是相等的,为什么呢?看单子的String Diagram 不太好看出来,我们来看伴随函子
如果把左图的左边的 \(\mu\) 往上挪一点,右边的 \(\mu\) 往下挪一点,是不是跟右图就一样了
\begin{center}
\includegraphics[width=.9\linewidth]{images/p1-ajunction-functor-monad-laws-4.png}
\end{center}
结合律反映到代码中就是
\lstset{language=haskell,label= ,caption= ,captionpos=b,numbers=none}
\begin{lstlisting}
mu . fmap mu = mu . mu
\end{lstlisting}
代码很难看出结合在哪里,因为正常的结合律应该是这样的 (1+2)+3 = 1+(2+3),但是不想加法的维度不一样,这里说的是自然变换维度的结合,可以通过String Diagram 很清楚的看见结合的过程,即 \(\mu\) 左边的两个T和先 \(\mu\) 右边两个 T 是相等的。
\chapter{Yoneda lemma / \sout{米田共} 米田引理}
\label{sec:orga68f251}
\index{米田引理}
\index{Yoneda Lemma}
米田引理是说所有的函子 \texttt{f a} 一定存在两个变换 \texttt{embed} 和 \texttt{unembed=,使得 =f a} 和 \texttt{(a -> b) -> F b} 同构。
要再 Haskell 中做到这一波操作需要先打开 \texttt{RankNTypes} 的编译器开关:
\lstset{language=haskell,label= ,caption= ,captionpos=b,numbers=none}
\begin{lstlisting}
{-# LANGUAGE RankNTypes #-}
embed :: Functor f => f a -> (forall b . (a -> b) -> f b)
embed x f = fmap f x
unembed :: Functor f => (forall b . (a -> b) -> f b) -> f a
unembed f = f id
\end{lstlisting}
Scala 3 不需要插件或者开关\footnote{\url{https://blog.oyanglul.us/scala/dotty/rank-n-type}},如果是 Scala 2 可以用 \texttt{apply} 来模拟. 比如 Cats 中 \href{https://typelevel.org/cats/datatypes/functionk.html}{FunctionK(\textasciitilde{}>)}。
\lstset{language=scala,label= ,caption= ,captionpos=b,numbers=none}
\begin{lstlisting}
type ~>[F[_],G[_]] = [A] => F[A] => G[A]
def embed[F[_], A](fa: F[A])(using F: Functor[F]) =
[B] => (fn: A=>B) => f.fmap(fn)(fa)
def unembed[F[_]](fn: [B] => (A => B) => F[B]): F[A] =
fn(identity)
\end{lstlisting}
\texttt{embed} 可以把 \texttt{f a} 变成 \texttt{(a -> b) -> f b}
\texttt{unembed} 是反过来, \texttt{(a -> b) -> f b} 变成 \texttt{f a}
上个图可能就明白了:
\begin{figure}[htbp]
\centering
\includegraphics[width=.9\linewidth]{images/yoneda-lemma.png}
\caption{也就是说,图中无论知道a->b 再加上任意一个 F x,都能推出另外一个 F}
\end{figure}
这个引理看似很巧妙,特别是用 id 的这个部分,但是有什么用呢?
如果着急可以跳到 Free Monad/自由单子 部分,你会发现他是自由单子的基础。而且如果再往后会介绍的宇宙本原左看和右看,更会发现其中得精妙相似之处。
\section{Rank N Type}
\label{sec:orgbc21743}
\index{Arbitrary-rank polymorphism}
\index{Rank N Type}
前面说好的要解释 Rank N Type,这里赶快补充一下,不然等会我就忘了。
Haskell 中可以不用声明类型, 但是其实是省略掉 universally quantified \texttt{forall}, 如果把 forall 全部加回来,
就明了很多:
\begin{itemize}
\item Monomorphic Rank 0 / 0级单态\footnote{也就不是不变态}: t
\item Polymorphic Rank 1 / 1级 \sout{变态} 多态: forall a b. a -> b
\item Polymorphic Rank 2 / 2级多态: forall c. (forall a b. a -> b) -> c
\item Polymorphic Rank 3 / 3级多态: forall d . (forall c . (forall a b . a -> b) -> c) -> d
\end{itemize}
看 rank 几只要数左边 forall 的个数就好了.
一级多态只锁定一次类型 a 和 b
二级多态可以分两次确定类型, 第一次确定 c, 第二次确定 a b
三级多台分三次: 第一次 d, 第二次 c, 第三次 a b
比如:
\lstset{language=haskell,label= ,caption= ,captionpos=b,numbers=none}
\begin{lstlisting}
rank2 :: forall b c . b -> c -> (forall a. a -> a) -> (b, c)
rank2 b c f = (f b, f c)
rank2 True 'a' id
-- (True, 'a')
\end{lstlisting}
\begin{itemize}
\item \texttt{f} 在 \texttt{f True} 时类型 \texttt{Boolean -> Boolean} 是符合 \texttt{forall a. a->a} 的
\item 与此同时 \texttt{f 'a'} 时类型确实是 \texttt{Char -> Char} 但也符合 \texttt{forall a. a->a}
\end{itemize}
看 Scala 的更简单,因为 Scala 不能省去 universally quantified,只需要数方括号即可。
最左边 \texttt{[B, C]} 是 rank1, \texttt{fn} 的类型里的 \texttt{[A]} 是 rank2。
\lstset{language=scala,label= ,caption= ,captionpos=b,numbers=none}
\begin{lstlisting}
def rank2[B, C](b: B, c: C)(fn: [A] => A => A): (B, C) =
(fn(b), fn(c))
rank2(true, 'a')([A] => (a: A) => A)
\end{lstlisting}
如果不用rank2 而是只有 rank1 类型系统就懵逼了:
\lstset{language=haskell,label= ,caption= ,captionpos=b,numbers=none}
\begin{lstlisting}
rank1 :: forall a b c . b -> c -> (a -> a) -> (b, c)
rank1 b c f = (f b, f c)
\end{lstlisting}
\lstset{language=scala,label= ,caption= ,captionpos=b,numbers=none}
\begin{lstlisting}
def rank1[A, B, C](b: B, c: C)(fn: A => A): (B, C) =
(fn(b), fn(c))
\end{lstlisting}
f 在 \texttt{f True} 是确定 a 是 Boolean,在rank1多态是时就确定了 \texttt{a -> a} 的类型一定是 \texttt{Boolean -> Boolean} ,
然后当看到 \texttt{f 'a'} 时类型就挂了,因为 \texttt{'a'} 不是 \texttt{Boolean} 。
\chapter{\emph{Kleisli Catergory}}
\label{sec:org97818f4}
\index{Kleisi Catergory}
函子/Functor 的范畴叫做 函子范畴/Functor Catergory, 自然变换是其箭头。那单子/Monad也可以定义一个范畴吗?\footnote{当然, 单子是自函子,所以也可以是自函子范畴}
是的, 这个范畴名字叫做 \sout{单子范畴}\footnote{怎么说也是函数式编程的核心,怎么可以叫的这么low这么直接} 可莱斯利范畴/Kleisli Catergory\footnote{这个是我瞎翻译的, 但是读出来就是这么个意思, 真的, 不骗你, 照这么读绝对装的一手好逼, 不会被嘲笑的},那么 Kleisli 的箭头是什么?
\begin{figure}[htbp]
\centering
\includegraphics[width=.9\linewidth]{images/kleisli.png}
\caption[\sout{弯}]{注意观察大火箭 <=< 的轨迹, 不知道dot为什么会把这根线搞这么又弯又骚的, 和 >>= 。所以 Kleisli 其实就是斜着走的一个范畴,但是 >>= 把它硬生生掰 \sout{弯} 直了。}
\end{figure}
我们看定义,Kleisli Category:
\begin{enumerate}
\item 箭头是 Kleisli 箭头 \texttt{a -> T b}
\item 东西就是c范畴中的东西. 因为 a 和 b 都是 c 范畴上的, 由于T是自函子,所以 T b 也是 c 范畴的
\end{enumerate}
看到图上的 T f/ fmap f 和 \(\mu\) 了没?\footnote{(敲黑板) 就是紫色那根嘛!}
\lstset{language=haskell,label= ,caption= ,captionpos=b,numbers=none}
\begin{lstlisting}
f :: b -> T c
fmap f :: T b -> T T c
mu :: T T c -> T c
\end{lstlisting}
\lstset{language=scala,label= ,caption= ,captionpos=b,numbers=none}
\begin{lstlisting}
def f[T[_], B, C](b: B): T[C]
def fmap[T[_], B, C](f: B => C)(tb: T[B]): T[T[C]]
def mu[T[_], C](ttc: T[T[C]]): T[C]
\end{lstlisting}
紫色的箭头 \texttt{T f} \footnote{即 fmap f} 和紫色的虚线箭头 \(\mu\) 连起来就是 \texttt{T f'}, 那么最出名的 bind \texttt{>>=} 符号终于出来了:
\lstset{language=haskell,label= ,caption= ,captionpos=b,numbers=none}
\begin{lstlisting}
tb >>= f = (mu . fmap f) tb
\end{lstlisting}
Scala 中通常叫作 \texttt{flatMap} ,但如果你用 Cats 也是可以用 \texttt{>>=} 的。
\lstset{language=scala,label= ,caption= ,captionpos=b,numbers=none}
\begin{lstlisting}
def flatMap[T[_], B, C](f: B => T[C])(tb: T[B]): T[C] = (mu compose fmap(f))(tb)
\end{lstlisting}
下面这个大火箭 \texttt{<=<} 可以把蓝色箭头组合起来.
\lstset{language=haskell,label= ,caption= ,captionpos=b,numbers=none}
\begin{lstlisting}
(f <=< g) = mu . T f . g = mu . fmap f . g
\end{lstlisting}
\lstset{language=scala,label= ,caption= ,captionpos=b,numbers=none}
\begin{lstlisting}
def <=<[T[_], A, B, C](f: B => T[C])(g: A => T[B]): A => T[C] =
mu compose fmap(f) compose g
\end{lstlisting}
因此大火箭就是 Kleisli 范畴的 \texttt{compose}
\lstset{language=haskell,label= ,caption= ,captionpos=b,numbers=none}
\begin{lstlisting}
(<=<) :: Monad T => (b -> T c) -> (a -> T b) -> (a -> T c)
\end{lstlisting}
\chapter{Summary}
\label{sec:orgd5dd7d5}
第一部分理论部分都讲完了, 如果你读到这里还没有被这些吊炸天/乱七八糟的概念劝退,
那么你这份如此强大得信念感,其实到后面两部分也不会有什么用。
因为,接下来的例子会很简单,我们要通过编程中常遇到的场景看看理论到底该如何得到实践?
\part{食用猫呢/Practical Monads}
\label{sec:orgee41947}
\begin{center}
\includegraphics[width=.9\linewidth]{./images/Alice_through_the_looking_glass.jpg}
\end{center}
\chapter{Functor 食用函子定义}
\label{sec:org5fdf827}
\lstset{language=haskell,label= ,caption= ,captionpos=b,numbers=none}
\begin{lstlisting}
class Functor f where
fmap :: (a -> b) -> (f a -> f b)
(<$) :: a -> f b -> f a
(<$) = fmap . const
\end{lstlisting}
\lstset{language=scala,label= ,caption= ,captionpos=b,numbers=none}
\begin{lstlisting}
trait Functor[F[_]]:
def fmap[A, B](fn: A => B): F[A] => F[B]
extension [B](fb: F[B])
def <$(a: A)(fb: F[B]): F[A] = fmap(const(a))
\end{lstlisting}
\chapter{Applicative}
\label{sec:orgd390c88}
\lstset{language=haskell,label= ,caption= ,captionpos=b,numbers=none}
\begin{lstlisting}
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f B
liftA2 :: (a -> b -> c) -> f a -> f b -> f c
liftA2 f x = (<*>) (fmap f x)
(*>) :: f a -> f b -> f b
a1 *> a2 = (id <$ a1) <*> a2
(<*) :: f a -> f b -> f a
(<*) = liftA2 const
\end{lstlisting}
\lstset{language=scala,label= ,caption= ,captionpos=b,numbers=none}
\begin{lstlisting}
trait Applicative[F[_]] extends Functor[F]:
def pure[A](a: A): F[A]
def ap[A, B](fab: F[A=>B]): F[A] => F[B]
def liftA2[A, B, C](f: A => B => C): F[A] => F[B] => F[C] = (x: F[A]) =>
ap(fmap(f)(x))
extension [A](fa: F[A])
def *>(fb: F[B]) = (identity <$ fa) <*> fb
def <*(fb: F[B]) = liftA2(const)
extension [A, B](fab: F[A => B])
def <*>(fa: F[B]) = ap(fab)(fa)
\end{lstlisting}
\chapter{Monad}
\label{sec:org3cf4122}
\lstset{language=haskell,label= ,caption= ,captionpos=b,numbers=none}
\begin{lstlisting}
class Applicative m => Monad m where
(>>=) :: forall a b. m a -> (a -> m b) -> m b
(>>) :: forall a b. m a -> m b -> m b
m >> k = m >>= \_ -> k
return :: a -> m a
return = pure
\end{lstlisting}
\lstset{language=scala,label= ,caption= ,captionpos=b,numbers=none}
\begin{lstlisting}
trait Monad[M[_]] extends Applicative[M] {
def flatMap[A, B](ma: M[A])(f: A => M[B]): M[B]
extension [A, B](ma: M[A])
def >>(mb: M[B]): MB = flatMap(ma)((_: A) => mb)
}
\end{lstlisting}
\chapter{Identity 本身就有}
\label{sec:org4276978}
本身就有单子/ Identity Monad\footnote{从来没见过有人给这些数据类型按过中文名字, 不然我来, 这样也更好的体会这些数据类型的意图.} 可能是最简单的单子了。本身不包含任何计算, 且只有一个构造器:
\lstset{language=haskell,label= ,caption= ,captionpos=b,numbers=none}
\begin{lstlisting}
newtype Identity a = Identity { runIdentity :: a }
\end{lstlisting}
\lstset{language=scala,label= ,caption= ,captionpos=b,numbers=none}
\begin{lstlisting}
case class Identity[A](run: A)
\end{lstlisting}
\begin{itemize}
\item 这里取名 \texttt{Identity} 叫 \textbf{本身就有} ,所以 \texttt{Identity a} 就是 \textbf{本身就有 a}
\item 这里使用 \texttt{newtype} 而不是 \texttt{data} 是因为 \texttt{Identity} 与 \texttt{runIdentity} 是 \emph{同构} 的\footnote{见 \href{part1.org}{第一部分 伴随函子}}.
\end{itemize}
\lstset{language=haskell,label= ,caption= ,captionpos=b,numbers=none}
\begin{lstlisting}
Identity :: a -> Identity a
runIdentity :: Identity a -> a
\end{lstlisting}
你看 \texttt{runIdentity . Identity = id} ,所以他们是同构的。
左边的 \texttt{Identity} 是 \emph{类型构造器}\footnote{也就是 Kind * -> *, 因为它非常的 nice, 一定要等到 a 才出类型}, 接收类型 \texttt{a} 返回 \texttt{Identity a} 类型。
如果 \texttt{a} 是 \texttt{Int}, 那么就得到一个 \texttt{Identity Int} 类型。
右边的 \texttt{Identity} 是数据构造器,也就是构造值,比如 \texttt{Identity 1} 会构造出一个值,其类型为 \texttt{Identity Int} 。
大括号比较诡异,可以想象成给 \texttt{a} 自动生成了一个 \texttt{Identity a -> a} 的函数, 比如:
\lstset{language=haskell,label= ,caption= ,captionpos=b,numbers=none}
\begin{lstlisting}
runIdentity (Identity 1)
\end{lstlisting}
\lstset{language=scala,label= ,caption= ,captionpos=b,numbers=none}
\begin{lstlisting}
Identity(1).run
\end{lstlisting}
会返回 1
\textbf{本身就有} 可以实现 Functor 和 Monad,就得到 Identity 函子 和 Identity 单子。
\lstset{language=haskell,label= ,caption= ,captionpos=b,numbers=none}
\begin{lstlisting}
instance Functor Identity where
fmap f (Identity a) = Identity (f a)
instance Monad Identity where
return a = Identity a
Identity a >>= f = f a
\end{lstlisting}
而 Scala 则用 \texttt{given} 来实现 typeclass:
\lstset{language=scala,label= ,caption= ,captionpos=b,numbers=none}
\begin{lstlisting}
given Functor[Identity]:
def fmap[A, B](f: A => B): Identity[A] => Identity[B] =
case Identity(a) => Identity(f(a))
given Monad[Identity]:
def pure[A](a: A): Id[A] = Identity(a)
def flatMap[A, B](f: A => Identity[B]): Identity[A] => Identity[B] =
case Identity(a) => f(a)
\end{lstlisting}
可以看到 \texttt{Identity} 即是构造器/constructor,也是解构器/destructure,利用模式匹配是可以解构出值的。
上面函子实现中的 \texttt{fmap f (Identity a)}, 假如 \texttt{fmap} 的是 \texttt{Identity 1},
那么这个模式匹配到 \texttt{(Identity a)} 时会通过解构器把 \texttt{1} 放到 \texttt{a} 的位置。
\textbf{本来就有} 看起来什么也没有干,就跟 \texttt{identity} 函数一样,但是实际上, 它也跟 identity 相对于函数一样,在某些场景底下非常有用,比如后一部分搞基猫呢会
提的高达猫。
\chapter{Maybe 可能会有}
\label{sec:org038ba9f}
可能会有单子/Maybe Monad是一个超级简单的但比本身就有稍稍复杂的单子.