-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathatom.xml
More file actions
1000 lines (897 loc) · 214 KB
/
atom.xml
File metadata and controls
1000 lines (897 loc) · 214 KB
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
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Forec's Notes</title>
<link href="/atom.xml" rel="self"/>
<link href="http://forec.github.io/"/>
<updated>2018-09-26T06:08:29.097Z</updated>
<id>http://forec.github.io/</id>
<author>
<name>Forec</name>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>HDFS 组织及工作</title>
<link href="http://forec.github.io/2017/08/22/hadoop_knowledge/"/>
<id>http://forec.github.io/2017/08/22/hadoop_knowledge/</id>
<published>2017-08-22T08:16:16.000Z</published>
<updated>2018-09-26T06:08:29.097Z</updated>
<content type="html"><blockquote>
<p>Apache Hadoop 是一款支持数据密集型分布式应用程序并以 Apache 2.0 许可协议发布的开源软件框架。它支持在商品硬件构建的大型集群上运行的应用程序。Hadoop 是根据谷歌公司发表的 MapReduce 和 Google 文件系统的论文自行实现而成。所有的 Hadoop 模块都有一个基本假设,即硬件故障是常见情况,应该由框架自动处理。</p>
</blockquote>
<a id="more"></a>
<h2 id="子项目"><a href="#子项目" class="headerlink" title="子项目"></a>子项目</h2><ul>
<li><strong>Hadoop Common</strong>:在 0.20 及以前的版本中,包含 HDFS、MapReduce 和其他项目公共内容,从 0.21 开始 HDFS 和 MapReduce 被分离为独立的子项目,其余内容为 Hadoop Common </li>
<li><strong>HDFS</strong>:Hadoop Distributed File System </li>
<li><strong>MapReduce</strong>:并行计算框架,0.20 前使用 <code>org.apache.hadoop.mapred</code> 旧接口,0.20 版本开始引入 <code>org.apache.hadoop.mapreduce</code> 的新 API </li>
</ul>
<h2 id="HDFS-结构"><a href="#HDFS-结构" class="headerlink" title="HDFS 结构"></a>HDFS 结构</h2><ul>
<li>HDFS 是一个高度容错性的系统,适合部署在廉价的机器上。</li>
<li>硬件错误是常态而不是异常,错误检测和快速、自动的恢复是HDFS最核心的架构目标。</li>
<li>运行在 HDFS 上的应用需要 <strong>流式访问</strong> 数据集,HDFS 的设计更多考虑了数据批处理,而不是用户交互处理。比之数据访问的低延迟问题,更关键的在于 <strong>数据访问的高吞吐量</strong>。</li>
<li><strong>移动计算比移动数据更划算</strong>:一个应用请求的计算,离它操作的数据越近就越高效,HDFS 为应用提供了将它们自己移动到数据附近的接口。</li>
<li>HDFS 采用 <strong>master/slave 结构</strong>,一个 HDFS 集群由一个 Namenode 和一定数目的 Datanodes 组成。Namenode 是一个 <strong>中心服务器</strong>,负责管理文件系统的命名空间以及客户端对文件的访问;集群中的 Datanode 一般是一个节点一个,负责管理它所在节点上的存储。HDFS 暴露了文件系统的命名空间,用户能够以文件的形式在上面存储数据。从内部看,一个文件其实被分成一个或多个数据块,这些块存储在一组 Datanode 上。Namenode 执行文件系统的命名空间操作,如打开、关闭、重命名文件或目录,它也<strong>负责确定数据块到具体 Datanode 节点的映射</strong>。Datanode 负责处理文件系统客户端的读写请求,在 Namenode 的统一调度下进行数据块的创建、删除和复制。Namenode 是所有 HDFS 元数据的仲裁者和管理者,<strong>用户数据永远不会流过 Namenode</strong>。</li>
</ul>
<h3 id="数据复制和副本"><a href="#数据复制和副本" class="headerlink" title="数据复制和副本"></a>数据复制和副本</h3><ul>
<li>HDFS 将<strong>每个文件存储成一系列的数据块</strong>,除了最后一个,所有的数据块都是同样大小的。一个典型的数据块大小是 64MB,HDFS 中的文件总是按照 64MB 被切分成不同的块,每个块尽可能地存储于不同的 Datanode 中。为了容错,文件的所有数据块都会有副本,每个文件的数据块大小和副本系数都是可配置的,应用程序可以指定某个文件的副本数目。副本系数可以在文件创建的时候指定,也可以在之后改变。HDFS中的文件都是一次性写入的,并且严格要求在任何时候只能有一个写入者。</li>
<li>Namenode 全权管理数据块的复制,它<strong>周期性地从集群中的每个 Datanode 接收心跳信号和块状态报告</strong>。接收到心跳信号意味着该 Datanode 节点工作正常,块状态报告包含了一个该 Datanode 上所有数据块的列表。</li>
<li>大型 HDFS 实例一般运行在跨越多个机架的计算机组成的集群上,不同机架上的两台机器之间的通讯需要经过交换机。在大多数情况下,同一个机架内的两台机器间的带宽会比不同机架的两台机器间的带宽大。通过 <strong>机架感知</strong>(使用 API resolve 将 slave 的 DNS 名称(或IP地址)转换成机架id)Namenode可以确定每个 Datanode 所属的机架 id。一种简单但没有优化的策略是将副本存放在不同的机架上,这样可以有效防止当整个机架失效时数据的丢失,且允许读数据的时候充分利用多个机架的带宽。但这种策略的一个写操作需要传输数据块到多个机架,增加了写的代价。<br>大多数情况下,副本系数是3,HDFS 的存放策略是将 <strong>一个副本存放在本地机架的节点上,一个副本放在同一机架的另一个节点上,最后一个副本放在不同机架的节点上</strong>。</li>
<li>HDFS 会<strong>尽量让读取程序读取离它最近的副本</strong>。如果在读取程序的同一个机架上有一个副本,那么就读取该副本。如果一个 HDFS 集群跨越多个数据中心,那么客户端也将首先读本地数据中心的副本。</li>
<li>Namenode 启动后会进入一个称为 <strong>安全模式</strong> 的特殊状态。处于安全模式的 Namenode 不会进行数据块的复制,并从所有的 Datanode 接收心跳信号和块状态报告。块状态报告包括了某个 Datanode 所有的数据块列表,每个数据块都有一个指定的最小副本数。当 Namenode 确认某个数据块的副本数目达到这个最小值,那么该数据块就会被认为是副本安全的;在一定百分比(这个参数可配置)的数据块被 Namenode 检测确认是安全之后(加上一个额外的30秒等待时间), Namenode 将退出安全模式状态。接下来它会确定还有哪些数据块的副本没有达到指定数目,并将这些数据块复制到其他 Datanode 上。</li>
</ul>
<h3 id="元数据持久化"><a href="#元数据持久化" class="headerlink" title="元数据持久化"></a>元数据持久化</h3><ul>
<li>Namenode 上保存着 HDFS 的命名空间,对于任何对文件系统元数据产生修改的操作,Namenode 都会使用 <code>EditLog</code> 事务日志记录下来。Namenode 在本地操作系统的文件系统中存储这个 <code>Editlog</code>。整个文件系统的命名空间,包括数据块到文件的映射、文件的属性等,都存储在一个称为 <code>FsImage</code> 的文件中,这个文件也放在 Namenode 所在的本地文件系统上。</li>
<li><strong>Namenode 在内存中保存着整个文件系统的命名空间和文件数据块映射的映像</strong>。这个关键的元数据结构设计得很紧凑,因此 4G 内存的 Namenode 足够支撑大量的文件和目录。当 Namenode <strong>启动时</strong>,它从硬盘中读取 <code>Editlog</code> 和 <code>FsImage</code>,将所有 <code>Editlog</code> 中的事务作用在内存中的 <code>FsImage</code> 上,并将这个新版本的 <code>FsImage</code> 从内存中保存到本地磁盘上,然后删除旧的 <code>Editlog</code>,这个过程称为一个<strong>检查点</strong>。</li>
<li>Datanode <strong>将 HDFS 数据以文件的形式存储在本地的文件系统中</strong>,它并不知道有关 HDFS 文件的信息。它把<strong>每个HDFS数据块存储在本地文件系统的一个单独的文件中</strong>。Datanode 并不在同一个目录创建所有的文件,而是用试探的方法来确定每个目录的最佳文件数目,并且在适当的时候创建子目录。在同一个目录中创建所有的本地文件并不是最优的选择,这是因为本地文件系统可能无法高效地在单个目录中支持大量的文件。当 Datanode 启动时,它会扫描本地文件系统,产生一个这些本地文件对应的所有 HDFS 数据块的列表,然后作为报告发送到 Namenode ,这个报告就是块状态报告。</li>
</ul>
<h3 id="集群通讯和健壮性"><a href="#集群通讯和健壮性" class="headerlink" title="集群通讯和健壮性"></a>集群通讯和健壮性</h3><ul>
<li>客户端通过一个可配置的 <strong>TCP</strong> 端口连接到 Namenode,通过 ClientProtocol 协议与 Namenode 交互。而 Datanode 使用 DatanodeProtocol 协议与 Namenode 交互。一个远程过程调用模型被抽象出来封装 ClientProtocol 和 Datanodeprotocol 协议。Namenode 不会主动发起 RPC,而是响应来自客户端或 Datanode 的 RPC 请求。</li>
<li>三种出错情况是:<strong>Namenode 出错、Datanode 出错、网络割裂</strong>。Datanode 出错和网络割裂可能导致一部分 Datanode 跟 Namenode 失去联系。Namenode 通过心跳信号的缺失来将近期不再发送心跳信号 Datanode 标记为宕机,不会再将新的 IO 请求发给它们,且任何存储在宕机 Datanode 上的数据将不再有效。这可能会引起一些数据块的副本系数低于指定值,Namenode 不断地检测这些需要复制的数据块,一旦发现就启动复制操作。</li>
<li>HDFS 支持<strong>数据均衡策略</strong>。如果某个 Datanode 节点上剩余空闲空间低于临界点,系统会自动地将数据从这个 Datanode 移动到其他空闲 Datanode。当对某个文件的请求突然增加,也可能启动一个计划创建该文件新的副本,并且同时重新平衡集群中的其他数据。</li>
<li>当 HDFS 客户端创建一个新的 HDFS 文件时,会计算这个文件每个数据块的校验和,并<strong>将校验和作为一个单独的隐藏文件保存在同一个 HDFS 名字空间下</strong>。当客户端获取文件内容后,它会检验从 Datanode 获取的数据跟相应的校验和文件中的校验和是否匹配,如果不匹配,客户端可以选择从其他 Datanode 获取该数据块的副本。</li>
<li><code>FsImage</code> 和 <code>Editlog</code> 是 HDFS 的核心数据结构。如果这些文件损坏了,整个 HDFS 实例都将失效,因此 Namenode 可以配置成支持维护多个 <code>FsImage</code> 和 <code>Editlog</code> 的副本。Namenode 是 HDFS 集群中的 <strong>单点故障</strong> 所在。如果 Namenode 机器故障,需要手工干预。也可以通过指定配置,在 Namenode 宕机时切换到 Secondary Namenode。</li>
</ul>
<h3 id="数据组织"><a href="#数据组织" class="headerlink" title="数据组织"></a>数据组织</h3><ul>
<li><strong>客户端缓存</strong>:客户端创建文件的请求并没有立即发送给 Namenode。HDFS 客户端会先将文件数据缓存到本地的一个临时文件,应用程序的写操作被透明地重定向到这个临时文件。当这个临时文件累积的数据量超过一个数据块的大小,客户端才会联系 Namenode。Namenode 将文件名插入文件系统的层次结构中,并且分配一个数据块给它,然后返回 Datanode 的标识符和目标数据块给客户端。接着客户端将这块数据从本地临时文件上传到指定的 Datanode 上。当文件关闭时,在临时文件中剩余的没有上传的数据也会传输到指定的 Datanode 上,然后客户端告诉 Namenode 文件已关闭,此时 Namenode 才将文件创建操作提交到日志里进行存储。<strong>如果 Namenode 在文件关闭前宕机,该文件将丢失</strong>。</li>
<li><strong>流水线复制</strong>:假设文件的副本系数设置为3,且客户端开始向第一个 Datanode 传输数据,第一个 Datanode 逐小块(4 KB)地接收数据,将每一部分写入本地仓库,并同时传输该部分到列表中第二个 Datanode 节点。第二个 Datanode 也以此类推同时传给第三个 Datanode。即,Datanode 流水线式地从前一个节点接收数据,并在同时转发给下一个节点。</li>
</ul>
<h2 id="HDFS-使用"><a href="#HDFS-使用" class="headerlink" title="HDFS 使用"></a>HDFS 使用</h2><h3 id="存储空间回收"><a href="#存储空间回收" class="headerlink" title="存储空间回收"></a>存储空间回收</h3><ul>
<li>当用户或应用程序删除某个文件时,HDFS 会将这个文件重命名转移到 <code>.Trash</code> 目录。文件在 <code>.Trash</code> 中保存的时间是可配置的(设置属性 <code>fs.trash.interval</code>),超时后 Namenode 会将该文件从命名空间中删除,删除文件会使得该文件相关的数据块被释放,因此从用户删除文件到 HDFS 空闲空间的增加之间会有一定时间的延迟。</li>
<li>当一个文件的副本系数被减小后,Namenode 会选择过剩的副本删除,并在下次心跳检测时将该信息传递给 Datanode。</li>
</ul>
<h3 id="Secondary-NameNode"><a href="#Secondary-NameNode" class="headerlink" title="Secondary NameNode"></a>Secondary NameNode</h3><ul>
<li>因为 NameNode 只有在启动阶段才合并 <code>fsImage</code> 和 <code>EditLog</code>,所以日志文件可能会变得非常庞大,且下一次 NameNode 启动会花很长时间。</li>
<li>Secondary NameNode 定期合并 <code>fsImage</code> 和 <code>EditLog</code>,将日志文件大小控制在一个限度下。因为内存需求和 NameNode 在一个数量级上,所以通常 Secondary NameNode 和 NameNode 运行在不同的机器上。Secondary NameNode 通过 <code>bin/start-dfs.sh</code> 在`conf/masters 中指定的节点上启动。</li>
<li>Secondary NameNode 的检查点进程启动,由两个配置参数控制:<ul>
<li><code>fs.checkpoint.period</code>:指定连续两次检查点的最大时间间隔,默认为 1 小时;</li>
<li><code>fs.checkpoint.size</code>:定义日志文件的最大值,一旦超过这个值会强制执行检查点,默认值是64MB。</li>
</ul>
</li>
<li>Secondary NameNode 保存最新检查点的目录与 NameNode 的目录结构相同,NameNode 可以在需要的时候读取 Secondary NameNode 上的检查点镜像。如果 NameNode 上除了最新的检查点以外,所有的其他的历史镜像和日志文件都丢失了,则可以引入这个最新的检查点:<ul>
<li>在配置参数 <code>dfs.name.dir</code> 指定的位置创建空目录;</li>
<li>把检查点目录的位置赋值给配置参数 <code>fs.checkpoint.dir</code>;</li>
<li>启动 NameNode,并加上 <code>-importCheckpoint</code>。</li>
</ul>
</li>
<li>按上述步骤,NameNode 会从 <code>fs.checkpoint.dir</code> 目录读取检查点,并把它保存在 <code>dfs.name.dir</code> 目录下。如果 <code>dfs.name.dir</code> 目录下有合法的镜像文件,NameNode 会启动失败。NameNode 会检查 <code>fs.checkpoint.dir</code> 目录下镜像文件的一致性,但是不会改动它。</li>
</ul>
<h2 id="Map-Reduce"><a href="#Map-Reduce" class="headerlink" title="Map/Reduce"></a>Map/Reduce</h2><ul>
<li>一个 Map/Reduce 作业通常会把输入的数据集切分为若干独立的数据块,由 map 任务以完全并行的方式处理它们。框架会对 map 的输出先进行排序,然后把结果输入给 reduce 任务。通常作业的输入和输出都会被存储在文件系统中。整个框架负责任务的调度和监控,以及重新执行已经失败的任务。</li>
<li>通常,<strong>Map/Reduce 框架和分布式文件系统是运行在一组相同的节点上的</strong>,即计算节点和存储节点通常在一起。Map/Reduce 框架由一个单独的 master JobTracker 和每个集群节点一个 slave TaskTracker 组成。master 负责调度构成一个作业的所有任务,这些任务分布在不同的 slave 上,master 监控它们的执行,重新执行已经失败的任务,slave 仅负责执行由 master 指派的任务。</li>
<li>应用程序通过提供 map 和 reduce 来实现 Mapper 和 Reducer 接口,它们组成作业的核心。Mapper 将输入键值对映射到一组中间格式的键值对集合,这种转换的中间格式记录集不需要与输入记录集的类型一致,一个给定的输入键值对可以映射成 0 个或多个输出键值对;Reducer 将与一个 key 关联的一组中间数值集归约为一个更小的数值集。<strong>Map 的数目通常是由输入数据的大小决定的</strong>,一般就是所有输入文件的总块数。</li>
<li>Reducer有3个主要阶段:shuffle、sort 和 reduce。<ul>
<li>Shuffle:Reducer 的输入就是 Mapper 已经排好序的输出。在这个阶段,框架通过 HTTP 为每个 Reducer 获得所有 Mapper 输出中与之相关的分块。</li>
<li>Sort:框架按照 key 的值对 Reducer 的输入进行分组(不同 mapper 的输出中可能会有相同的key)。<strong>Shuffle 和 Sort 两个阶段是同时进行的</strong>,map 的输出也是一边被取回一边被合并的。</li>
<li>Reduce:框架为已分组的输入数据中的每个 <code>&lt;key, (list of values)&gt;</code> 对调用一次 <code>reduce(WritableComparable, Iterator, OutputCollector, Reporter)</code> 方法。Reduce任务的输出通常是通过调用 <code>OutputCollector.collect(WritableComparable, Writable)</code> 写入文件系统的。Reducer的输出是没有排序的。Reduce的数目建议是 0.95 或 1.75 乘以 <code>(&lt;no. of nodes&gt; * mapred.tasktracker.reduce.tasks.maximum)</code>。增加 Reduce 的数目会增加整个框架的开销,但可以改善负载均衡,降低由于执行失败带来的负面影响。</li>
</ul>
</li>
</ul>
<hr>
<p>参考资料:<a href="http://hadoop.apache.org/docs/r1.0.4/" target="_blank" rel="external">Hadoop 0.18 中文文档</a> </p>
<p>原创作品,允许转载,转载时无需告知,但请务必以超链接形式标明文章<a href="http://blog.forec.cn/2017/08/22/hadoop_knowledge/" target="_blank" rel="external">原始出处</a>(<a href="http://blog.forec.cn/2017/08/22/hadoop_knowledge/" target="_blank" rel="external">http://blog.forec.cn/2017/08/22/hadoop_knowledge/</a>) 、作者信息(<a href="http://forec.cn/" target="_blank" rel="external">Forec</a>)和本声明。</p>
</content>
<summary type="html">
<blockquote>
<p>Apache Hadoop 是一款支持数据密集型分布式应用程序并以 Apache 2.0 许可协议发布的开源软件框架。它支持在商品硬件构建的大型集群上运行的应用程序。Hadoop 是根据谷歌公司发表的 MapReduce 和 Google 文件系统的论文自行实现而成。所有的 Hadoop 模块都有一个基本假设,即硬件故障是常见情况,应该由框架自动处理。</p>
</blockquote>
</summary>
<category term="大数据/分布式系统" scheme="http://forec.github.io/categories/%E5%A4%A7%E6%95%B0%E6%8D%AE-%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F/"/>
<category term="Hadoop" scheme="http://forec.github.io/tags/Hadoop/"/>
<category term="分布式系统" scheme="http://forec.github.io/tags/%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F/"/>
</entry>
<entry>
<title>从鳄鱼蛋看 λ 演算</title>
<link href="http://forec.github.io/2017/03/21/AlligatorEggs/"/>
<id>http://forec.github.io/2017/03/21/AlligatorEggs/</id>
<published>2017-03-21T15:42:42.000Z</published>
<updated>2018-09-26T06:08:29.201Z</updated>
<content type="html"><blockquote>
<p>这篇文章是 <a href="http://worrydream.com/" target="_blank" rel="external">Bret Victor</a> 所著 <a href="http://worrydream.com/#!/AlligatorEggs" target="_blank" rel="external">《Alligator Eggs!》</a> 的中文译文,已联系原作者取得授权。Bret Victor 在 2007 到 2011 年期间在苹果负责人机界面开发,参与设计了最初的 iPad,他的个人网站非常精美,文章也及其友好。更多关于他的信息可以参考维基百科上 <a href="https://en.wikipedia.org/wiki/Bret_Victor" target="_blank" rel="external">Bret Victor 的词条</a>。</p>
</blockquote>
<a id="more"></a>
<blockquote>
<p>This Article is the Chinese translation for <a href="http://worrydream.com/#!/AlligatorEggs" target="_blank" rel="external"><em>Alligator Eggs!</em></a> (Written by <a href="http://worrydream.com/" target="_blank" rel="external">Bret Victor</a>).</p>
</blockquote>
<h2 id="准备材料"><a href="#准备材料" class="headerlink" title="准备材料"></a>准备材料</h2><ul>
<li><strong>第一步</strong>:将这个 <a href="http://qiniu.forec.cn/alligator/alligators_colored.pdf" target="_blank" rel="external">PDF 文件</a> 打印到六种<span style="color: #f00;">不同</span><span style="color: #0a0;">颜色</span><span style="color: #00f;">的</span><span style="color: #ea0;">纸</span><span style="color: #f0f;">板</span>上。<br><img src="http://qiniu.forec.cn/alligator/materials_1.png"></li>
<li><strong>第二步</strong>:将这个 <a href="http://qiniu.forec.cn/alligator/alligators_colored.pdf" target="_blank" rel="external">PDF 文件</a> 打印到几张白纸上。<br><img src="http://qiniu.forec.cn/alligator/materials_2.png"></li>
<li><strong>第三步</strong>:把这些鳄鱼和鳄鱼蛋剪下来!<br><img src="http://qiniu.forec.cn/alligator/materials_3.png"></li>
</ul>
<h2 id="零件"><a href="#零件" class="headerlink" title="零件"></a>零件</h2><p>下面这些是 <strong>饥饿的鳄鱼</strong>(彩色的):<br><img src="http://qiniu.forec.cn/alligator/pieces_1.png"><br>饥饿的鳄鱼 <strong>需要进食</strong>。它们会吃掉任何在它们面前的东西!但是它们同时也是 <strong>负责任的</strong> 鳄鱼,所以它们还需要守护自己的家庭。</p>
<p>这些是 <strong>老鳄鱼</strong>:<br><img src="http://qiniu.forec.cn/alligator/pieces_2.png"><br>老鳄鱼们并不需要进食,它们已经吃饱了,而它们生活的唯一目标就是保卫自己的家庭。</p>
<p>这些是 <strong>蛋</strong>:<br><img src="http://qiniu.forec.cn/alligator/pieces_3.png"><br>蛋会孵化出新的鳄鱼家庭!</p>
<h2 id="家庭"><a href="#家庭" class="headerlink" title="家庭"></a>家庭</h2><p>这是一个小家庭:<br><img src="http://qiniu.forec.cn/alligator/families_1.png"><br>一条绿色的鳄鱼守护着她绿色的蛋。</p>
<p>这是一个稍大的家庭:<br><img src="http://qiniu.forec.cn/alligator/families_2.png"><br>一条绿鳄鱼和一条红鳄鱼在一起保护着一个绿色的鳄鱼蛋和一个红色的鳄鱼蛋。或者,你也可以这么理解:一条绿色的鳄鱼正在保护着一条红色的鳄鱼,而这条红色的鳄鱼正在看护着这些蛋。</p>
<p>这是一个大家庭!<br><img src="http://qiniu.forec.cn/alligator/families_3.png"><br>我们可以看到,各有一条黄色、绿色和红色的鳄鱼在守卫着这个家庭。它们守护着三个东西:一个绿色的鳄鱼蛋,一条老鳄鱼,以及一个红色的鳄鱼蛋。这条老鳄鱼还保护着一个黄色的鳄鱼蛋和一个绿色的鳄鱼蛋。<br>注意!鳄鱼蛋必须和它们保护者的颜色一致。换句话说,如果有一个蓝色的鳄鱼蛋,那就必须有一条蓝色的鳄鱼守护着它。</p>
<h2 id="进食"><a href="#进食" class="headerlink" title="进食"></a>进食</h2><p>现在事情开始变得复杂起来了。这是紧挨着的两个家庭:<br><img src="http://qiniu.forec.cn/alligator/eating_1.png"></p>
<p>毫无疑问,绿色的鳄鱼正处于饥饿状态。无独有偶,有一个黄色的鳄鱼家庭就在她面前。看起来似乎很好吃!<br><img src="http://qiniu.forec.cn/alligator/eating_2.png"></p>
<p>我想你已经知道马上会发生什么事。<br><img src="http://qiniu.forec.cn/alligator/eating_3.png"></p>
<p>不幸的是,绿色的鳄鱼有些高估自己的能力了。她吃得太多了!<br><img src="http://qiniu.forec.cn/alligator/eating_4.png"></p>
<p>这条绿色的鳄鱼去了鳄鱼天堂。但是,故事仍在继续。绿色鳄鱼死了之后,她所看护的绿色鳄鱼蛋开始孵化……<br><img src="http://qiniu.forec.cn/alligator/eating_5.png"></p>
<p>太神奇了,这个绿色鳄鱼蛋竟然孵化成了绿色鳄鱼刚刚吃掉的东西。这是生命的奇迹!<br><img src="http://qiniu.forec.cn/alligator/eating_6.png"></p>
<p>现在,它们又变成了一个家庭。这个家庭中有一条红鳄鱼守卫着一条黄鳄鱼和一个红鳄鱼蛋,而这条黄鳄鱼则守护着她的黄色鳄鱼蛋。</p>
<p>这条黄色鳄鱼当然也需要进食,何况她的面前恰好有一个美味的红色鳄鱼蛋。让我们再重复一次……<br><img src="http://qiniu.forec.cn/alligator/eating_7.png"></p>
<p>可怜的鳄鱼。对她的食量来说,即便是一个鳄鱼蛋也难以消化!<br><img src="http://qiniu.forec.cn/alligator/eating_8.png"></p>
<p>这条黄色的鳄鱼死了…… 但是同时,黄色的鳄鱼蛋也开始孵化……<br><img src="http://qiniu.forec.cn/alligator/eating_9.png"></p>
<p>黄色的鳄鱼蛋孵化出了黄色鳄鱼吃掉的东西。<br><img src="http://qiniu.forec.cn/alligator/eating_10.png"></p>
<p>现在已经没有任何鳄鱼有机会进食了,所以这个故事到这里可以停止了。</p>
<h2 id="进食规则"><a href="#进食规则" class="headerlink" title="进食规则"></a>进食规则</h2><p>上面所述的故事是这个游戏的第一条规则:<strong>进食规则</strong>。</p>
<p>进食规则指的是,如果有一些紧挨着的家庭……<br><img src="http://qiniu.forec.cn/alligator/eatingrule_1.png"></p>
<p>那么最左上角的鳄鱼会吃掉她右侧的整个鳄鱼家庭。<br><img src="http://qiniu.forec.cn/alligator/eatingrule_2.png"></p>
<p>之后,进食的这条鳄鱼会死掉。但如果这条鳄鱼正在守卫着任何和她颜色相同的鳄鱼蛋,那么 <strong>每个</strong> 这样的鳄鱼蛋(与死去的鳄鱼颜色相同且被其守卫的)都会孵化出这条死去的鳄鱼刚刚吃过的东西。<br><img src="http://qiniu.forec.cn/alligator/eatingrule_3.png"></p>
<h2 id="颜色规则"><a href="#颜色规则" class="headerlink" title="颜色规则"></a>颜色规则</h2><p>继续上面的例子,橘黄色的鳄鱼吃掉了黄色的鳄鱼家庭,情况变成下面这样:<br><img src="http://qiniu.forec.cn/alligator/eatingrule_4.png"></p>
<p>现在,左上角的绿色鳄鱼想要吃掉她右侧的鳄鱼家庭。但是在此之前,我们得检查一下这是否符合 <strong>颜色规则</strong>。</p>
<p>颜色规则说的是,如果一条鳄鱼准备吃掉一个鳄鱼家庭,并且有一种颜色在 <strong>两个家庭</strong>(进食的鳄鱼所属的家庭和将要被吃掉的家庭) 中都出现了,我们就需要将其中一个家庭的这种颜色替换为其它颜色。</p>
<p>上面的图片中,绿色和红色同时出现在第一、第二个家庭中。因此,我们将第二个家庭中所有的绿色替换为浅蓝色,所有的红色替换为黄色。<br><img src="http://qiniu.forec.cn/alligator/colorrule_1.png"></p>
<p>现在各个家庭间没有相同的颜色了,鳄鱼们已经饥渴难耐!动嘴吧!<br><img src="http://qiniu.forec.cn/alligator/colorrule_2.png"></p>
<p>还可以吃!<br><img src="http://qiniu.forec.cn/alligator/colorrule_3.png"></p>
<p>继续!<br><img src="http://qiniu.forec.cn/alligator/colorrule_4.png"></p>
<p>直到没有东西可以被吃掉。<br><img src="http://qiniu.forec.cn/alligator/colorrule_5.png"></p>
<h2 id="老鳄鱼"><a href="#老鳄鱼" class="headerlink" title="老鳄鱼"></a>老鳄鱼</h2><p>这个游戏还有一条与 <strong>老鳄鱼</strong> 有关的额外规则:<br><img src="http://qiniu.forec.cn/alligator/old_1.png"></p>
<p>左上角的老鳄鱼并不会感到饥饿,她不需要吃任何东西,她的生命只会奉献给她的家庭。那么这个游戏该怎么继续下去呢?</p>
<p>当需要守护的东西只剩最后一样的时候,老鳄鱼会撒手鳄寰。现在,左上角的老鳄鱼同时看护着一个绿色的家庭和一个红色的家庭,这些家庭还需要她去照顾。但是现在,绿色的鳄鱼需要进食了,于是它吃掉了右侧的红色家庭……<br><img src="http://qiniu.forec.cn/alligator/old_2.png"></p>
<p>现在,老鳄鱼只看护着一个家庭。这个家庭可以照顾自己了,老鳄鱼失去了存在的意义,所以她很快离去。<br><img src="http://qiniu.forec.cn/alligator/old_3.png"></p>
<p>这就是 <strong>老龄规则</strong>。当一条老鳄鱼只看管着(直接看管)一样东西时,她就该离开这个世界了。<br><img src="http://qiniu.forec.cn/alligator/old_4.png"></p>
<p>最后,红色的鳄鱼吃掉了黄色的家庭。<br><img src="http://qiniu.forec.cn/alligator/old_5.png"></p>
<h2 id="游戏"><a href="#游戏" class="headerlink" title="游戏"></a>游戏</h2><p>游戏包含一系列谜题,目标是让玩家设计一个家庭,使这个家庭在进食 X 后能够产出 Y。举个例子:</p>
<p>这是两个家庭,我们将左侧的家庭命名为 “True”,将右侧的家庭命名为 “False”:<br><img src="http://qiniu.forec.cn/alligator/gameplay_1.png"></p>
<p>这是一个名为 “Not” 的家庭:<br><img src="http://qiniu.forec.cn/alligator/gameplay_2.png"></p>
<p>当 “Not” 吃掉 “True” 的时候会产生 “False”,同样的,当 “Not” 吃掉 “False” 的时候会产生 “True”。那么,“Not” 中最底部的两个鳄鱼蛋应该是什么颜色呢?</p>
<p>(我们需要完善一下颜色规则:如果两个家庭颜色不同,但具有相同的模式,即相同位置的鳄鱼/蛋在两个家庭中的颜色能够建立起一一映射,就认为这两个家庭是等价的。)</p>
<p>这些谜题可以嵌入一些故事中。玩家必须依次解决谜题才能看到故事的发展。或者,这些谜题可以做成棋盘游戏。每个玩家通过选择鳄鱼死亡来解决到达目的地等问题。</p>
<h2 id="理论"><a href="#理论" class="headerlink" title="理论"></a>理论</h2><p>这个游戏实际表示了 <strong>无类型的 λ 演算</strong> 。<strong>饥饿的鳄鱼</strong> 是 λ 表示的抽象函数,<strong>老鳄鱼</strong> 是括号,<strong>鳄鱼蛋</strong> 是变量。<strong>进食规则</strong> 对应着 β-规约,<strong>颜色规则</strong> 对应着 α-变换。<strong>老龄规则</strong> 指的是,如果一对括号仅仅包含着单个不可分割表达式,那这对括号就可以被去除。</p>
<h2 id="变换"><a href="#变换" class="headerlink" title="变换"></a>变换</h2><p><a href="http://osteele.com/" target="_blank" rel="external">奥利弗·斯蒂尔</a>(Oliver Steele)指出,家庭角色对孩子的影响非常重要,这个游戏中的物品可以进一步变化为祖父母鳄鱼、鳄鱼家长和婴儿(蛋)等等。目前,变量名称是通过颜色表示的。这两者都是任意的,所以需要重命名/染色的规则去避免冲突。可以采用类似布鲁恩指数的替代方案,从而能够反应出生顺序和家庭关系。</p>
<p>奥利弗还建议,孩子们也许不会喜欢家长去世这一说法,所以鳄鱼的死亡可以解释为离开。</p>
<p>通过改变游戏规则,让饥饿的鳄鱼变得 <strong>贪婪</strong>,并吃掉一切在它们右侧的东西,我们就可以表示 <strong>右结合</strong> 的 λ 演算。和丘奇数、Y 组合子等相比,这种表示方法似乎去掉了许多老鳄鱼(括号)。不幸的是,这意味着当鳄鱼吃掉不止一样东西时,必须有老鳄鱼出生,同时也会产生一些其他问题。我仍然想找到一种让丘奇数(重复施用)看起来不那么丑陋的表示方法。</p>
<h2 id="示意图"><a href="#示意图" class="headerlink" title="示意图"></a>示意图</h2><p>我发现 “鳄鱼演算” 的原理比手动计算 λ 表达式来的更容易。我们将 λ 画做一条带嘴的直线。括号是一条不带嘴的直线。这是恒等(identity)函数:<br><img src="http://qiniu.forec.cn/alligator/schematic_1.png"></p>
<p>这是一些丘奇数:<br><img src="http://qiniu.forec.cn/alligator/schematic_2.png"></p>
<p>这是布尔型操作 AND 和 OR:<br><img src="http://qiniu.forec.cn/alligator/schematic_3.png"></p>
<p>Y 组合子:<br><img src="http://qiniu.forec.cn/alligator/schematic_4.png"></p>
<p>我不知道它们 <strong>读</strong> 起来是不是比标准符号更容易些,但我发现在有纸笔的情况下它们确实更容易 <strong>处理</strong> 。想象一下,一个表达式吃掉另一个并将它在底部孵化。我不会再在一长串符号中迷失,忘掉该把哪个 λ 应用到哪个 λ 上。</p>
<hr>
<p>英文原文链接: <a href="http://worrydream.com/#!/AlligatorEggs" target="_blank" rel="external"><em>Alligator Eggs!</em></a> (Written by <a href="http://worrydream.com/" target="_blank" rel="external">Bret Victor</a>) </p>
<p>原创作品,允许转载,转载时无需告知,但请务必以超链接形式标明文章<a href="http://blog.forec.cn/2017/03/21/AlligatorEggs/" target="_blank" rel="external">原始出处</a>(<a href="http://blog.forec.cn/2017/03/21/AlligatorEggs/" target="_blank" rel="external">http://blog.forec.cn/2017/03/21/AlligatorEggs/</a>) 、作者信息(<a href="http://forec.cn/" target="_blank" rel="external">Forec</a>)和本声明。</p>
</content>
<summary type="html">
<blockquote>
<p>这篇文章是 <a href="http://worrydream.com/">Bret Victor</a> 所著 <a href="http://worrydream.com/#!/AlligatorEggs">《Alligator Eggs!》</a> 的中文译文,已联系原作者取得授权。Bret Victor 在 2007 到 2011 年期间在苹果负责人机界面开发,参与设计了最初的 iPad,他的个人网站非常精美,文章也及其友好。更多关于他的信息可以参考维基百科上 <a href="https://en.wikipedia.org/wiki/Bret_Victor">Bret Victor 的词条</a>。</p>
</blockquote>
</summary>
<category term="Code" scheme="http://forec.github.io/categories/Code/"/>
<category term="函数式编程" scheme="http://forec.github.io/tags/%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B/"/>
</entry>
<entry>
<title>右线性语言</title>
<link href="http://forec.github.io/2017/03/19/formal-languages-and-automata3/"/>
<id>http://forec.github.io/2017/03/19/formal-languages-and-automata3/</id>
<published>2017-03-19T15:09:53.000Z</published>
<updated>2018-09-26T06:08:29.070Z</updated>
<content type="html"><blockquote>
<p>正则集、正则式,右线性文法,正则表达式与有限自动机之间的图示关系,右线性语言与有限自动机之间的关系。</p>
</blockquote>
<a id="more"></a>
<h2 id="正则集和正则式"><a href="#正则集和正则式" class="headerlink" title="正则集和正则式"></a>正则集和正则式</h2><ul>
<li>正则集可用正则式表示。字母表 T 上的一个正则式和它表示的正则集可递归定义如下:<ul>
<li>ε 和 ∅ 都是正则式,分别表示的正则集是 {ε} 和空集 ∅;</li>
<li>任意 a ∈ T 是正则式,他表示的正则集是 {a}(以上两条为原子正则式);</li>
<li>如果 A 和 B 是正则式,分别表示的正则集是 L(A) 和 L(B),则 (A+B)、(A·B)、(A<em>) 也都是正则式,分别表示的正则集是 <code>L(A) ∪ L(B)</code>、<code>L(A)L(B)</code>、`L(A)</em><code>。操作的优先级为</code>*(闭包) &gt; ·(连接)&gt; +(联合)`。</li>
</ul>
</li>
<li>仅由 <strong>有限次</strong> 使用以上三步定义的表达式才是字母表 T 的正则式。如果两个正则式表示相同的正则集,则称两个正则式相等。</li>
<li>假设 α、β、γ 都是正则式,则:<ul>
<li><code>(α + β) + γ = α + (β + γ)</code></li>
<li><code>(α · β) · γ = α · (β · γ)</code></li>
<li><code>α + β = β + α</code></li>
<li><code>α + α = α</code></li>
<li><code>α · (β + γ) = (α · β) + (α · γ)</code></li>
<li><code>(β + γ) · α = (β · α) + (γ · α)</code></li>
<li><code>α + ∅= α</code>(零元)</li>
<li><strong><code>α · ∅ = ∅ · α = ∅</code>(零元)</strong></li>
<li><strong><code>α · ε = ε · a = a</code>(幺元)</strong></li>
<li><code>(α*)* = α*</code>(闭包)</li>
<li><code>α* = ε + α+</code></li>
<li><code>∅* = ε</code></li>
</ul>
</li>
<li>注意:<ul>
<li>正则集是 T* 的子集;</li>
<li>L+ 包含 ε 当且仅当 L 包含 ε;</li>
<li>每个正则集至少对应一个正则式。</li>
</ul>
</li>
</ul>
<h2 id="右线性文法与正则"><a href="#右线性文法与正则" class="headerlink" title="右线性文法与正则"></a>右线性文法与正则</h2><h3 id="右线性文法与正则式"><a href="#右线性文法与正则式" class="headerlink" title="右线性文法与正则式"></a>右线性文法与正则式</h3><ul>
<li>右线性文法(正则文法)与正则式具有等价性。</li>
<li>求解规则:设 <code>x = αx + β, α ∈ T*, β ∈ (N∪T)*, x ∈ N</code>,可解出 <code>x = α*β</code>。</li>
<li>例:<code>G = {N = {S, A, B}, T = {a, b}, P, S}</code>,其中 P 包括以下规则:<code>S → aA, S → bB, S → b, A → bA, A → ε, B → bS</code>。<ul>
<li>将 P 中的推导式联立,可得到 <code>S = aA + bB + b</code>,<code>A =bA + ε</code> 和 <code>B = bS</code>;</li>
<li>根据 <code>A = bA + ε</code>,应用求解规则得到 <code>A = b*</code>;</li>
<li>将 <code>A = b*</code>,<code>B = bS</code> 代入,得到 <code>S = ab* + bbS + b</code>,转换形式得到 <code>S = (bb)S + (ab* + b)</code>;</li>
<li>在此应用求解规则得到 <code>S = (bb)*(ab*+b)</code>。</li>
</ul>
</li>
</ul>
<h3 id="右线性文法与正则集"><a href="#右线性文法与正则集" class="headerlink" title="右线性文法与正则集"></a>右线性文法与正则集</h3><ul>
<li><strong>正则集是由右线性文法产生的语言,二者是等同的</strong> ,按照上面定义的正则集都是右线性文法产生的语言。</li>
<li>设字母表为 T。∅、{ε} 和 {a}(任意 a ∈ T)都是正则集,则 ∅、{ε} 和 {a} 都是右线性语言。它们分别对应的右线性文法是 <code>G∅ = ({S}, T, ∅, S)</code>、<code>G{ε} = ({S}, T, {S → ε}, S)</code> 以及 <code>G{a} = ({S}, {a}, {S → a}, S)</code>。</li>
<li>可证明,设字母表 T 上有右线性语言 L1 和 L2,则 L1∪L2、L1L2 和 L1* 都是右线性语言。<ul>
<li>证 L1 ∪ L2 为右线性语言:有右线性文法 <code>G = (N, T, P, S)</code>,其中 <code>N = N1 ∪ N2 ∪ {S}</code>,<code>P = P1 ∪ P2 ∪ {S → S1, S → S2}</code>,S ∉ N1 ∪ N2,是一个新的非终结符;</li>
<li>证 L1L2 为右线性语言:有右线性文法 <code>G = (N, T, P, S)</code>,其中 N = N1 ∪ N2,S = S1,生成式 P 为:若 A → αB ∈ P1 则 A → αB ∈ P,若 A → α ∈ P1,则 A → αS2 ∈ P,P2 ⊆ P。</li>
<li>证 L1* 是右线性语言:有右线性文法 <code>G = (N, T, P, S)</code>,其中 N = N1 ∪ {S},S ∉ N1,S 是一个新非终结符,生成式 P 定义为:若 A → αB ∈ P1 则 A → αB ∈ P,若 A → α ∈ P1 则 A → αS ∈ P 且 A → α ∈ P,且 P 中包括 {S → S1, S → ε}。</li>
</ul>
</li>
</ul>
<h2 id="正则表达式和有限自动机"><a href="#正则表达式和有限自动机" class="headerlink" title="正则表达式和有限自动机"></a>正则表达式和有限自动机</h2><blockquote>
<p>以下图片均来自王柏教授(北京邮电大学大数据科学与服务中心)的《形式语言与自动机》课件。</p>
</blockquote>
<ul>
<li>有限自动机、右(左)线性文法和正则表达式都定义了同一种语言(正则语言)。</li>
<li>由 DFA 构造等价的正则表达式(状态消去法):</li>
</ul>
<p><img src="http://qiniu.forec.cn/fla/fla-1.jpg" width="500px"></p>
<ul>
<li>从 DFA 构造等价正则表达式的具体步骤如下:<ul>
<li>对每一终态 q,依次消去除 q 和初态 q0 之外的其它状态;</li>
<li>若 q ≠ q0,最终可得到一般形式:<code>(R+ SU*T)*SU*</code>,如下图中左侧的自动机;</li>
<li>若 q = q0,最终可得到如图中右侧的自动机,对应正则式为 <code>R*</code>;</li>
<li>最终的正则表达式为 <strong>每一终态对应的正则表达式之和(联合)</strong> 。<br><img src="http://qiniu.forec.cn/fla/fla-2.jpg"></li>
</ul>
</li>
<li>从正则表达式构建等价的 ε-NFA,可根据如下几种基本组合子组合:<br><img src="http://qiniu.forec.cn/fla/fla-3.jpg" width="400px"></li>
<li>对于 <code>R+S</code>、<code>RS</code> 以及 <code>R*</code>,分别为下图中从上到下三种连接方式:<div><br><img src="http://qiniu.forec.cn/fla/fla-4.jpg" width="250px" style="display:inline"><br><img src="http://qiniu.forec.cn/fla/fla-5.jpg" width="250px" style="display:inline"><br><img src="http://qiniu.forec.cn/fla/fla-6.jpg" width="250px" style="display:inline"><br></div>
</li>
</ul>
<h2 id="右线性文法与有限自动机"><a href="#右线性文法与有限自动机" class="headerlink" title="右线性文法与有限自动机"></a>右线性文法与有限自动机</h2><h3 id="右线性文法-⇒-有限自动机"><a href="#右线性文法-⇒-有限自动机" class="headerlink" title="右线性文法 ⇒ 有限自动机"></a>右线性文法 ⇒ 有限自动机</h3><ul>
<li>设右线性文法 G = (N, T, P, S),产生语言为 L(G),则存在一个有限自动机 M 接受语言 L(M) = L(G)。</li>
<li>构造:构造一个与 G 等价的 NFA M = (Q, T, δ, q0, F),其中<ul>
<li>Q = N ∪ {H},H 为新增状态,H ∉ N;</li>
<li>q0 = S;</li>
<li>当 S → ε ∈ P 时,F = {H, S},否则 F = {H};</li>
<li>δ 的定义为:若 A → αB ∈ P 则 B ∈ δ(A, α),若 A → α ∈ P 则 H ∈ δ(A, α),且对任意输入有 δ(H, α) = ∅。</li>
</ul>
</li>
<li>例:G = ({S, B}, {α, β}, P, S),其中 P 为 S → αB, B → αB | βS | α。<ul>
<li>令 NFA M = (Q, T, δ, q0, F),Q = {S, B, H},T = {α, β},q0 = S,F = {H};</li>
<li>∵ S → αB,故 δ(S, α) 中包括 B;</li>
<li>∵ B → αB,故 δ(B, α) 中包括 B;</li>
<li>∵ B → βS,故 δ(B, β) 中包括 S;</li>
<li>∵ B → α,故 δ(B, α) 中包括 H。</li>
</ul>
</li>
</ul>
<h3 id="有限自动机-⇒-右线性文法"><a href="#有限自动机-⇒-右线性文法" class="headerlink" title="有限自动机 ⇒ 右线性文法"></a>有限自动机 ⇒ 右线性文法</h3><ul>
<li>设 DFA M 接受的语言为 L(M),则存在右线性文法 G,它产生的语言 L(G) = L(M)。</li>
<li>构造:设 M = (Q, T, δ, q0, F),则令 G = (N, T, P, S),其中 <ul>
<li>N = Q,S = q0;</li>
<li>若 δ(A, α) = B 且 B ∉ F 则 A → αB ∈ P;</li>
<li>若 δ(A, α) = B 且 B ∈ F 则 A → α ∈ P,A → αB ∈ P。</li>
</ul>
</li>
<li>对于 NFA,可以转换为等价的 DFA 再做相应构造。</li>
</ul>
<h2 id="右线性语言性质"><a href="#右线性语言性质" class="headerlink" title="右线性语言性质"></a>右线性语言性质</h2><p>TODO</p>
<hr>
<p>专栏目录:<a href="http://blog.forec.cn/columns/cs-basic.html" target="_blank" rel="external">计算机理论基础</a><br>此专栏的上一篇文章:<a href="http://blog.forec.cn/2017/03/18/formal-languages-and-automata2/" target="_blank" rel="external">有限自动机</a><br>此专栏的下一篇文章:暂无 </p>
<p>参考资料:《形式语言与自动机》,王柏、杨娟编著,北京邮电大学出版社 </p>
<p>原创作品,允许转载,转载时无需告知,但请务必以超链接形式标明文章<a href="http://blog.forec.cn/2017/03/19/formal-languages-and-automata3/" target="_blank" rel="external">原始出处</a>(<a href="http://blog.forec.cn/2017/03/19/formal-languages-and-automata3/" target="_blank" rel="external">http://blog.forec.cn/2017/03/19/formal-languages-and-automata3/</a>) 、作者信息(<a href="http://forec.cn/" target="_blank" rel="external">Forec</a>)和本声明。</p>
</content>
<summary type="html">
<blockquote>
<p>正则集、正则式,右线性文法,正则表达式与有限自动机之间的图示关系,右线性语言与有限自动机之间的关系。</p>
</blockquote>
</summary>
<category term="计算机理论基础" scheme="http://forec.github.io/categories/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80/"/>
<category term="自动机" scheme="http://forec.github.io/tags/%E8%87%AA%E5%8A%A8%E6%9C%BA/"/>
</entry>
<entry>
<title>有限自动机</title>
<link href="http://forec.github.io/2017/03/18/formal-languages-and-automata2/"/>
<id>http://forec.github.io/2017/03/18/formal-languages-and-automata2/</id>
<published>2017-03-18T13:41:32.000Z</published>
<updated>2018-09-26T06:08:29.171Z</updated>
<content type="html"><blockquote>
<p>确定的/非确定的有限自动机,带/不带 ε 转移的非确定有限自动机以及各自之间的等价性。</p>
</blockquote>
<a id="more"></a>
<ul>
<li><strong>有限自动机(Finite State Automation)</strong> :<strong>状态</strong> 是将事物区分开的一种标识,状态 + 输入 → 状态转移。<ul>
<li>具有 <strong>离散</strong> 输入(输出,不必须)系统的一种数学模型,特殊情况下也可无输入;</li>
<li>有限的状态;</li>
<li>根据每次转换的后继状态数量可区分为 <strong>确定的</strong> 有限自动机(DFA,后继状态唯一)和 <strong>不确定的</strong> 有限自动机(NFA,后继状态有多个)。</li>
</ul>
</li>
</ul>
<h2 id="确定的有限自动机"><a href="#确定的有限自动机" class="headerlink" title="确定的有限自动机"></a>确定的有限自动机</h2><ul>
<li>形式定义:DFA 是一个五元组,<code>M = (Q, T, δ, q0, F)</code>:<ul>
<li>Q:有限的状态集合</li>
<li>T:有限的输入字母表</li>
<li>δ:转换函数,映射 Q × T → Q</li>
<li>q0:初始状态,q0 ∈ Q</li>
<li>F:终止状态集,F ⊆ Q</li>
</ul>
</li>
<li>输入一个字符串时,对转换函数 δ 扩展:<code>δ&#39; = Q × T* → Q</code>,对任何 q ∈ Q,定义:<ul>
<li><code>q&#39;(q, ε) = q</code> :没有读到字符时,有限自动机状态不变</li>
<li>对 a ∈ T 和 ω ∈ T*,有 <code>δ&#39;(q, ωa) = δ(δ&#39;(q, ω), a)</code></li>
</ul>
</li>
<li>被 DFA 接收的字符串 ω 满足:<code>δ(q0, ω) = p, p ∈ F</code>,即 <strong>输入结束后能够使 DFA 状态到达终态</strong> 。</li>
<li><strong>格局</strong> :FSA 在某个时刻的工作状态可以用 <code>(q, ω)</code> 表明,其中 ω 为待输入字符串,q 为当前状态。<ul>
<li>初始格局:<code>(q0, ω)</code></li>
<li>终止格局:<code>(q, ε), q ∈ F</code></li>
<li>当 <code>δ(q, a)</code> 含有 q1,则用格局形式写为 <code>(q, aω) |--- (q1, ω)</code>,其中 ω ∈ T*,符号 <code>|---</code> 表示从一个格局变换为另一个格局。</li>
</ul>
</li>
</ul>
<h2 id="不确定的有限自动机"><a href="#不确定的有限自动机" class="headerlink" title="不确定的有限自动机"></a>不确定的有限自动机</h2><ul>
<li>形式定义:NFA 是一个五元组,<code>M = (Q, T, δ, q0, F)</code>,与 DFA 仅 δ 不同,NFA 的 δ 的映射范围为 <code>Q × T → 2^Q</code>,即 NFA 在某个状态下读入一个字符时,可转换的后继状态是 Q 的一个子集。</li>
<li>输入字符串时对 δ 扩展:<ul>
<li>对 ε ∈ T*,有 `δ’(q, ε) = {q}’;</li>
<li>对任意 a ∈ T, ω ∈ T*,有 `δ’(q, ωa) = {p | 对 δ’(q, ω) 中的某个状态 r,且 p 在 δ(r, a) 内};</li>
<li><code>δ(P, ω) = ∪ δ(q, ω), q ∈ P, P ⊆ Q</code>。</li>
</ul>
</li>
<li>NFA 接受的语言为 `L(M) = {ω | δ(q0, ω) 含 F 中的一个状态}。</li>
</ul>
<h3 id="DFA-和-NFA-的等价性"><a href="#DFA-和-NFA-的等价性" class="headerlink" title="DFA 和 NFA 的等价性"></a>DFA 和 NFA 的等价性</h3><ul>
<li>DFA 是 NFA 的特例,NFA 必然能够接受 DFA 能接受的语言。</li>
<li>从 NFA 构造等价的 DFA(<strong>子集构造法</strong>):<ul>
<li>设 L 为某个 NFA,<code>N = (QN, T, δN, q0, FN)</code>,定义等价的 DFA 为 <code>M = (QD, T, δD, {q0}, FD)</code>;</li>
<li>定义的 DFA 中,<code>QD = {S | S ⊆ QN} = 2^Q</code>,对 S ∈ QD 和 a ∈ T,有 <code>δD(S, a) = ∪ δN(q, a), q ∈ S</code>;</li>
<li><code>FD = {S | S ⊆ QN ∩ S ∩ FN ≠ ∅}</code> (只要有一个在 F 中就可视为终止状态);</li>
<li><strong>构造过程从 q0 开始,仅当某些状态已加入可达状态时,才加入 DFA 中</strong>。最坏情况下由 NFA 构造的 DFA 状态数目为 2^|QN|。</li>
</ul>
</li>
</ul>
<h3 id="带-ε-转移的不确定的有限自动机"><a href="#带-ε-转移的不确定的有限自动机" class="headerlink" title="带 ε 转移的不确定的有限自动机"></a>带 ε 转移的不确定的有限自动机</h3><ul>
<li>有 ε 转换的 NFA 与无 ε 转换的 NFA 区别仅在于转换函数 δ 的不同,有 ε 转移的 NFA 在输入空串 ε(无输入)时也会引起状态转移,即 δ 是从 Q × (T ∪ {ε}) → 2^Q。</li>
<li><strong>ε-闭包(closure)</strong>:<ul>
<li>状态 q 的 ε-闭包记为 ε-CLOSURE 或 ECLOSURE,定义为从 q 仅通过 ε 路径可以到达的状态(包括 q 自身);</li>
<li>状态子集 I 的 ε-闭包:<code>ε-CLOSURE(I) = ∪ ε-CLOSURE(q), q ∈ I</code>;</li>
<li>Ia:对状态子集 I ⊆ Q,任意 a ∈ T,<code>Ia = ε-CLOSURE(P), P = δ(I, a)</code>,即 P 是从 I 中状态经过一条标 a 的边可以到达的状态集合。</li>
</ul>
</li>
<li>扩展定义 δ’:Q × T* → 2^Q,对任何 q ∈ Q,有:<ul>
<li><code>δ&#39;(q, ε) = ε-CLOSURE(q)</code> </li>
<li><code>δ&#39;(q, ωa) = ε-CLOSURE(P), P = {p | 对某些 r ∈ δ&#39;(q, ω) 且 p ∈ δ(r, a)}</code></li>
</ul>
</li>
<li>δ(q, a) ≠ δ’(q, a),因为 δ(q, a) 仅表示由 q 出发,仅沿着 <strong>一条</strong> 标 a 的路径能到达的状态,而 δ’(q, a) 表示经过标 a 或 ε 的路径得到状态集合的 ε-闭包,即 <code>δ&#39;(q, ωa) = ε-CLOSURE(δ( δ&#39;(q, ω), a ))</code>。</li>
</ul>
<h3 id="有-ε-转换的-NFA-和无-ε-转换的-NFA-等价"><a href="#有-ε-转换的-NFA-和无-ε-转换的-NFA-等价" class="headerlink" title="有 ε 转换的 NFA 和无 ε 转换的 NFA 等价"></a>有 ε 转换的 NFA 和无 ε 转换的 NFA 等价</h3><ul>
<li>ε-NFA 是无 ε 转移的 NFA 的一般情况。设 <code>M = (Q, T, δ, q0, F)</code> 是一个 ε-NFA,可构造一个等价的无 ε 的 NFA:<code>M1 = (Q, T, S1, q0, F1)</code><ul>
<li>对任何 a ∈ T,<code>δ1(q, a) = δ&#39;(q, a)</code>;</li>
<li>若 ε-CLORSURE(q0) ∩ F ≠ ∅,则 F1 = F ∪ {q0},否则 F1 = F。</li>
</ul>
</li>
<li>构造方法:先确定 F,之后按照 <code>δ1(q, a) = δ&#39;(q, a)</code> 来确定生成式集合。</li>
</ul>
<hr>
<p>专栏目录:<a href="http://blog.forec.cn/columns/cs-basic.html" target="_blank" rel="external">计算机理论基础</a><br>此专栏的上一篇文章:<a href="http://blog.forec.cn/2017/02/25/formal-languages-and-automata1/" target="_blank" rel="external">形式语言</a><br>此专栏的下一篇文章:<a href="http://blog.forec.cn/2017/03/19/formal-languages-and-automata3/" target="_blank" rel="external">右线性语言</a> </p>
<p>参考资料:《形式语言与自动机》,王柏、杨娟编著,北京邮电大学出版社 </p>
<p>原创作品,允许转载,转载时无需告知,但请务必以超链接形式标明文章<a href="http://blog.forec.cn/2017/03/18/formal-languages-and-automata2/" target="_blank" rel="external">原始出处</a>(<a href="http://blog.forec.cn/2017/03/18/formal-languages-and-automata2/" target="_blank" rel="external">http://blog.forec.cn/2017/03/18/formal-languages-and-automata2/</a>) 、作者信息(<a href="http://forec.cn/" target="_blank" rel="external">Forec</a>)和本声明。</p>
</content>
<summary type="html">
<blockquote>
<p>确定的/非确定的有限自动机,带/不带 ε 转移的非确定有限自动机以及各自之间的等价性。</p>
</blockquote>
</summary>
<category term="计算机理论基础" scheme="http://forec.github.io/categories/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80/"/>
<category term="自动机" scheme="http://forec.github.io/tags/%E8%87%AA%E5%8A%A8%E6%9C%BA/"/>
</entry>
<entry>
<title>三种实用 Monad</title>
<link href="http://forec.github.io/2017/03/02/translation-adit-tum/"/>
<id>http://forec.github.io/2017/03/02/translation-adit-tum/</id>
<published>2017-03-02T10:24:20.000Z</published>
<updated>2018-09-26T06:08:29.384Z</updated>
<content type="html"><blockquote>
<p>这篇文章是 <a href="https://github.com/egonSchiele" target="_blank" rel="external">Aditya Bhargava</a> 所著 <a href="http://adit.io/posts/2013-06-10-three-useful-monads.html" target="_blank" rel="external">《Three Useful Monads》</a> 的中文译文,已联系原作者取得授权。</p>
</blockquote>
<a id="more"></a>
<blockquote>
<p>This Article is the Chinese translation for <a href="http://adit.io/posts/2013-06-10-three-useful-monads.html" target="_blank" rel="external"><em>Three Useful Monads</em></a> (Written by <a href="https://github.com/egonSchiele" target="_blank" rel="external">Aditya Bhargava</a>).</p>
</blockquote>
<ul>
<li>英文原文写于 2013 年 6 月 10 日。</li>
</ul>
<h2 id="引文"><a href="#引文" class="headerlink" title="引文"></a>引文</h2><blockquote>
<p>在阅读本文之前,你应当了解 <code>Monad</code> 的基本概念,否则请先阅读 <a href="http://blog.forec.cn/2017/03/02/translation-adit-faamip/" target="_blank" rel="external">《图解 Functor, Applicative 和 Monad》</a>。</p>
</blockquote>
<p>下图是函数 <code>half</code>:<br><img src="http://qiniu.forec.cn/adit-tum/half.png"><br>我们可以将其连续应用多次:<br><figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="title">half</span> . half $ <span class="number">8</span></span><br><span class="line">=&gt; <span class="number">2</span></span><br></pre></td></tr></table></figure></p>
<p>结果与预期一致。现在你决定记录这个函数的执行过程:<br><img src="http://qiniu.forec.cn/adit-tum/half_with_log.png"><br><figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="title">half</span> x = (x `div` <span class="number">2</span>, <span class="string">"I just halved "</span> ++ (show x) ++ <span class="string">"!"</span>)</span><br></pre></td></tr></table></figure></p>
<p>看起来不错。如果我们想将其连续应用多次,又该怎么书写呢?我们无法直接使用 <code>half . half $ 8</code>,因为应用一次 <code>half</code> 的返回值已经变成了元组,我们无法对元组继续应用 <code>half</code>。下图展示了我们实际期望的功能:<br><img src="http://qiniu.forec.cn/adit-tum/half_chain.png"><br>显然这个功能不会自己产生,我们必须自己实现:<br><figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="title">finalValue</span> = (val2, log1 ++ log2)</span><br><span class="line"> <span class="keyword">where</span> (val1, log1) = half <span class="number">8</span></span><br><span class="line"> (val2, log2) = half val1</span><br></pre></td></tr></table></figure></p>
<p>但是如果需要记录更多的函数呢?这里存在一个模式:我们希望将每个返回 <code>(Value, Log)</code> 的函数 “串” 到一起。这其实是一种副作用,而 <code>Monad</code> 刚好擅长处理这种副作用!</p>
<h2 id="Writer-Monad"><a href="#Writer-Monad" class="headerlink" title="Writer Monad"></a>Writer Monad</h2><p><img src="http://qiniu.forec.cn/adit-tum/writer_monad_on_a_horse.png"><br><code>Writer Monad</code> 非常酷炫。“老铁,让我来处理这波历史记录”,<code>Writer</code> 这么说,“我会帮助你的代码恢复整洁,我还能帮你上天!”(原著这里为 “启动齐柏林飞艇”)。每个 <code>Writer</code> 都包含一个历史记录并回传计算结果。<br><img src="http://qiniu.forec.cn/adit-tum/writer_monad.png"><br><figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="typedef"><span class="keyword">data</span> <span class="type">Writer</span> w a = <span class="type">Writer</span> <span class="container">&#123; <span class="title">runWriter</span> :: (<span class="title">a</span>, <span class="title">w</span>) &#125;</span></span></span><br></pre></td></tr></table></figure></p>
<p><code>Writer</code> 允许我们这么写代码:<br><figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="title">half</span> <span class="number">8</span> &gt;&gt;= half</span><br></pre></td></tr></table></figure></p>
<p>或者你可以用 <code>&lt;=&lt;</code>,它实现了 <code>Monad</code> 版本的函数复合:<br><figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="title">half</span> &lt;=&lt; half $ <span class="number">8</span></span><br></pre></td></tr></table></figure></p>
<p>非常接近 <code>half . half $ 8</code> 的写法!一颗赛艇!<br>我们使用 <code>tell</code> 来写入历史记录,用 <code>return</code> 将一个普通的值放入 <code>Writer</code> 的返回值。这是 <code>Writer</code> 版本的 <code>half</code> 函数:<br><figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="title">half</span> :: <span class="type">Int</span> -&gt; <span class="type">Writer</span> <span class="type">String</span> <span class="type">Int</span></span><br><span class="line"><span class="title">half</span> x = <span class="keyword">do</span></span><br><span class="line"> tell (<span class="string">"I just halved "</span> ++ (show x) ++ <span class="string">"!"</span>)</span><br><span class="line"> return (x `div` <span class="number">2</span>)</span><br></pre></td></tr></table></figure></p>
<p>新的 <code>half</code> 会回传一个 <code>Writer</code>:<br><img src="http://qiniu.forec.cn/adit-tum/half_writer.png"><br><code>runWriter</code> 能帮助我们取出 <code>Writer</code> 封装的元组。<br><img src="http://qiniu.forec.cn/adit-tum/run_writer.png"><br><figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="title">runWriter</span> $ half <span class="number">8</span></span><br><span class="line">=&gt; (<span class="number">4</span>, <span class="string">"I just halved 8!"</span>)</span><br></pre></td></tr></table></figure></p>
<p>然而,真正牛逼的地方在于,我们现在可以用 <code>&gt;&gt;=</code> 把 <code>half</code> 串起来了:<br><figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="title">runWriter</span> $ half <span class="number">8</span> &gt;&gt;= half</span><br><span class="line">=&gt; (<span class="number">2</span>, <span class="string">"I just halved 8!I just halved 4!"</span>)</span><br></pre></td></tr></table></figure></p>
<p>下图说明了上面这行代码的原理:<br><img src="http://qiniu.forec.cn/adit-tum/half_monad_chain.png"><br>我们不需要写任何繁杂的代码,因为 <code>&gt;&gt;=</code> 知道如何将两个 <code>Writer</code> 合并(做 <code>Monad</code> 最重要的是整整齐齐了)!下面是 <code>&gt;&gt;=</code> 针对 <code>Writer</code> 的完整定义:<br><img src="http://qiniu.forec.cn/adit-tum/writer_bind_definition.png"><br>其实这就是我们之前写过的样本代码,不过现在 <code>&gt;&gt;=</code> 帮助我们简化了。别忘了我们还有 <code>return</code>,它将一个值放入 <code>Monad</code> 中,对于 <code>Writer</code> 而言其作用如下图:<br><img src="http://qiniu.forec.cn/adit-tum/writer_return_definition.png"><br><figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="title">return</span> val = <span class="type">Writer</span> (val, <span class="string">""</span>)</span><br></pre></td></tr></table></figure></p>
<p>(<strong>注意</strong>:这些定义 <em>可视作</em> 正确的。实际的 <code>Writer Monad</code> 允许将任何 <code>Monoid</code> 类型作为 “历史记录”,而不仅限于字符串。这里我用字符串简化以帮助你理解。)<br>感谢 <code>Writer Monad</code> !</p>
<h2 id="Reader-Monad"><a href="#Reader-Monad" class="headerlink" title="Reader Monad"></a>Reader Monad</h2><p>假如你想将一些配置传递给许多函数,不妨试试 <code>Reader Monad</code>!<br><img src="http://qiniu.forec.cn/adit-tum/reader_monad.png"><br><code>Reader Monad</code> 允许你将一个值传递给所有幕后的函数。举个例子:<br><figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="title">greeter</span> :: <span class="type">Reader</span> <span class="type">String</span> <span class="type">String</span></span><br><span class="line"><span class="title">greeter</span> = <span class="keyword">do</span></span><br><span class="line"> name &lt;- ask</span><br><span class="line"> return (<span class="string">"hello, "</span> ++ name ++ <span class="string">"!"</span>)</span><br></pre></td></tr></table></figure></p>
<p><code>greeter</code> 回传一个 <code>Reader Monad</code>:<br><img src="http://qiniu.forec.cn/adit-tum/greeter_reader.png"><br>下面是 <code>Reader</code> 的定义:<br><figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="typedef"><span class="keyword">data</span> <span class="type">Reader</span> r a = <span class="type">Reader</span> <span class="container">&#123; <span class="title">runReader</span> :: <span class="title">r</span> -&gt; <span class="title">a</span> &#125;</span></span></span><br></pre></td></tr></table></figure></p>
<p><code>Reader</code> 的唯一字段是一个函数,<code>runReader</code> 可以取出这个函数:<br><img src="http://qiniu.forec.cn/adit-tum/run_reader.png"><br>现在你可以给这个函数一些输入,它们将会被 <code>greeter</code> 应用:<br><img src="http://qiniu.forec.cn/adit-tum/run_reader_expanded.png"><br><figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="title">runReader</span> greeter $ <span class="string">"adit"</span></span><br><span class="line">=&gt; <span class="string">"hello, adit!"</span></span><br></pre></td></tr></table></figure></p>
<p>每当你使用 <code>&gt;&gt;=</code> 都会得到一个 <code>Reader</code>,当你向该 <code>Reader</code> 传入一个状态时,这个状态会被传递给 monad 中的每个函数。<br><img src="http://qiniu.forec.cn/adit-tum/reader_bind_definition.png"><br><figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="title">m</span> &gt;&gt;= k = <span class="type">Reader</span> $ \r -&gt; runReader (k (runReader m r)) r</span><br></pre></td></tr></table></figure></p>
<p><code>Reader</code> 有些复杂,不过复杂的才是最吼的。<br><code>return</code> 将一个值放入 <code>Reader</code> :<br><img src="http://qiniu.forec.cn/adit-tum/reader_return_definition.png"><br><figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="title">return</span> a = <span class="type">Reader</span> $ \_ -&gt; a</span><br></pre></td></tr></table></figure></p>
<p><code>ask</code> 将传入的状态回传:<br><figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="title">ask</span> = <span class="type">Reader</span> $ \x -&gt; x</span><br></pre></td></tr></table></figure></p>
<p>想了解更多关于 <code>Reader</code> 的内容吗?你可以在 <a href="https://gist.github.com/egonSchiele/5752172" target="_blank" rel="external">这里</a> 看到一个更长的例子(需翻墙)。</p>
<h2 id="State-Monad"><a href="#State-Monad" class="headerlink" title="State Monad"></a>State Monad</h2><p><code>State Monad</code> 是 <code>Reader Monad</code> 最好的朋友:<br><img src="http://qiniu.forec.cn/adit-tum/state_monad.png"><br>她看起来和 <code>Reader Monad</code> 非常像,只不过它既可读又可写。这是 <code>State</code> 的定义:<br><figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">State</span> s a = <span class="type">State</span> &#123; runState :: s -&gt; (a, s) &#125;</span><br></pre></td></tr></table></figure></p>
<p><img src="http://qiniu.forec.cn/adit-tum/run_state_expanded.png"><br>你可以使用 <code>get</code> 获取状态,也可用 <code>put</code> 改变状态。举个例子:<br><figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="title">greeter</span> :: <span class="type">State</span> <span class="type">String</span> <span class="type">String</span></span><br><span class="line"><span class="title">greeter</span> = <span class="keyword">do</span></span><br><span class="line"> name &lt;- get</span><br><span class="line"> put <span class="string">"tintin"</span></span><br><span class="line"> return (<span class="string">"hello, "</span> ++ name ++ <span class="string">"!"</span>)</span><br><span class="line"></span><br><span class="line"><span class="title">runState</span> greeter $ <span class="string">"adit"</span></span><br><span class="line">=&gt; (<span class="string">"hello, adit!"</span>, <span class="string">"tintin"</span>)</span><br></pre></td></tr></table></figure></p>
<p>没毛病!<code>Reader</code> 就像在说 “你无法改变我”,而 <code>State</code> 则对改变持兹瓷态度。<br><code>State</code> 和 <code>Reader</code> 的定义看起来非常相似:<br><code>return</code>:<br><img src="http://qiniu.forec.cn/adit-tum/state_return_definition.png"><br><figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="title">return</span> a = <span class="type">State</span> $ \s -&gt; (a, s)</span><br></pre></td></tr></table></figure></p>
<p><code>&gt;&gt;=</code>:<br><img src="http://qiniu.forec.cn/adit-tum/state_bind_definition.png"><br><figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="title">m</span> &gt;&gt;= k = <span class="type">State</span> $ \s -&gt; <span class="keyword">let</span> (a, s') = runState m s</span><br><span class="line"> <span class="keyword">in</span> runState (k a) s'</span><br></pre></td></tr></table></figure></p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p><img src="http://qiniu.forec.cn/adit-tum/conclusion.png"><br><code>Writer</code>、<code>Reader</code>、<code>State</code>。现在你已经将这三个强大的武器添加到你的兵器库了,请不遗余力地使用它们!</p>
<h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ul>
<li><a href="http://sigfpe.blogspot.com/2006/08/you-could-have-invented-monads-and.html" target="_blank" rel="external">Why Monads are useful</a></li>
<li><a href="http://www.maztravel.com/haskell/readerMonad.html" target="_blank" rel="external">A good explaination of the Reader monad</a></li>
</ul>
<hr>
<p>英文原文链接: <a href="http://adit.io/posts/2013-06-10-three-useful-monads.html" target="_blank" rel="external"><em>Three Useful Monads</em></a> (Written by <a href="https://github.com/egonSchiele" target="_blank" rel="external">Aditya Bhargava</a>) </p>
<p>原创作品,允许转载,转载时无需告知,但请务必以超链接形式标明文章<a href="http://blog.forec.cn/2017/03/02/translation-adit-tum/" target="_blank" rel="external">原始出处</a>(<a href="http://blog.forec.cn/2017/03/02/translation-adit-tum/" target="_blank" rel="external">http://blog.forec.cn/2017/03/02/translation-adit-tum/</a>) 、作者信息(<a href="http://forec.cn/" target="_blank" rel="external">Forec</a>)和本声明。</p>
</content>
<summary type="html">
<blockquote>
<p>这篇文章是 <a href="https://github.com/egonSchiele">Aditya Bhargava</a> 所著 <a href="http://adit.io/posts/2013-06-10-three-useful-monads.html">《Three Useful Monads》</a> 的中文译文,已联系原作者取得授权。</p>
</blockquote>
</summary>
<category term="Code" scheme="http://forec.github.io/categories/Code/"/>
<category term="Haskell" scheme="http://forec.github.io/tags/Haskell/"/>
<category term="函数式编程" scheme="http://forec.github.io/tags/%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B/"/>
</entry>
<entry>
<title>图解 Functor, Applicative 和 Monad</title>
<link href="http://forec.github.io/2017/03/02/translation-adit-faamip/"/>
<id>http://forec.github.io/2017/03/02/translation-adit-faamip/</id>
<published>2017-03-02T06:07:40.000Z</published>
<updated>2018-09-26T06:08:28.911Z</updated>
<content type="html"><blockquote>
<p>这篇文章是 <a href="https://github.com/egonSchiele" target="_blank" rel="external">Aditya Bhargava</a> 所著 <a href="http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html" target="_blank" rel="external">《Functors, Applicatives, And Monads In Pictures》</a> 的中文译文,已联系原作者取得授权。另一版本的中文译文由 <a href="https://github.com/jiyinyiyong" target="_blank" rel="external">题叶</a> 翻译,可在<a href="http://jiyinyiyong.github.io/monads-in-pictures/" target="_blank" rel="external">此处</a>查看。</p>
</blockquote>
<a id="more"></a>
<blockquote>
<p>This Article is the Chinese translation for <a href="http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html" target="_blank" rel="external"><em>Functors, Applicatives, And Monads In Pictures</em></a> (Written by <a href="https://github.com/egonSchiele" target="_blank" rel="external">Aditya Bhargava</a>).</p>
</blockquote>
<ul>
<li>英文原文写于 2013 年 4 月 17 日。</li>
</ul>
<h2 id="引文"><a href="#引文" class="headerlink" title="引文"></a>引文</h2><p>下图是一个简单的值:<br><img src="http://qiniu.forec.cn/adit-faamip/value.png"><br>将一个函数应用到这个值上:<br><img src="http://qiniu.forec.cn/adit-faamip/value_apply.png"><br>简直 Naive,让我们来扩展一个!假设一个值可以被放到上下文中。你可以把上下文想象成一个盒子,把值放入上下文的过程就如同把东西放到盒子里:<br><img src="http://qiniu.forec.cn/adit-faamip/value_and_context.png"><br>现在再把一个函数应用到这个值上,<strong>根据不同的上下文</strong>,我们将得到不同的结果。这就是 <code>Functor</code>、<code>Applicative</code>、<code>Monad</code>、<code>Arrows</code> 等概念的基础。就 <code>Maybe</code> 这一型别来说,它定义了两种相关联的上下文:<br><img src="http://qiniu.forec.cn/adit-faamip/context.png"><br>马上我们就会看到对 <code>Just a</code> 和 <code>Nothing</code> 应用一个函数的不同之处。在此之前,让我们先了解一下 <code>Functor</code> !</p>
<h2 id="Functors"><a href="#Functors" class="headerlink" title="Functors"></a>Functors</h2><p>如果一个值被封装在上下文中,你会发现普通函数无法直接对其操作:<br><img src="http://qiniu.forec.cn/adit-faamip/no_fmap_ouch.png"><br>这时 <code>fmap</code> 就会发挥作用了!<code>fmap</code> 能够和上下文谈笑风生,它对普通函数和被上下文包装的值施了一点魔法,让它们能够愉快相处。举个例子,你想把函数 <code>(+3)</code> 应用到 <code>Just 2</code> 上,那么只需要加上 <code>fmap</code>:<br><figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&gt; fmap (+<span class="number">3</span>) (<span class="type">Just</span> <span class="number">2</span>)</span><br><span class="line"><span class="type">Just</span> <span class="number">5</span></span><br></pre></td></tr></table></figure></p>
<p><img src="http://qiniu.forec.cn/adit-faamip/fmap_apply.png"><br>一颗赛艇!<code>fmap</code> 向我们展示了它的威力!但是 <code>fmap</code> 怎么知道如何应用一个函数呢?</p>
<h2 id="什么是-Functor"><a href="#什么是-Functor" class="headerlink" title="什么是 Functor"></a>什么是 Functor</h2><p><code>Functor</code> 是一个 <a href="http://learnyouahaskell.com/types-and-typeclasses#typeclasses-101" target="_blank" rel="external">类型类</a>,这是它的定义:<br><img src="http://qiniu.forec.cn/adit-faamip/functor_def.png"><br>任何型别,只要能用 <code>fmap</code> 操作,就是一个 <code>Functor</code>。下面这张图展示了 <code>fmap</code> 各个参数的含义:<br><img src="http://qiniu.forec.cn/adit-faamip/fmap_def.png"><br>我们之所以能够执行 <code>fmap (+3) (Just 2)</code> ,是因为 <code>Maybe</code> 也是一个 <code>Functor</code> 。下面的定义指明了 <code>fmap</code> 在面对 <code>Just</code> 和 <code>Nothing</code> 时的处理方式:<br><figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">instance</span> <span class="type">Functor</span> <span class="type">Maybe</span> <span class="keyword">where</span></span></span><br><span class="line"> fmap func (<span class="type">Just</span> val) = <span class="type">Just</span> (func val)</span><br><span class="line"> fmap func <span class="type">Nothing</span> = <span class="type">Nothing</span></span><br></pre></td></tr></table></figure></p>
<p>下图说明了执行 <code>fmap (+3) (Just 2)</code> 的整个过程:<br><img src="http://qiniu.forec.cn/adit-faamip/fmap_just.png"><br>假设你灵机一动,让 <code>fmap</code> 把 <code>(+3)</code> 应用到 <code>Nothing</code> 上:<br><img src="http://qiniu.forec.cn/adit-faamip/fmap_nothing.png"><br><figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&gt; fmap (+<span class="number">3</span>) <span class="type">Nothing</span></span><br><span class="line"><span class="type">Nothing</span></span><br></pre></td></tr></table></figure></p>
<p><img src="http://qiniu.forec.cn/adit-faamip/baozou.jpg"><br>就像身经百战的董先森(原文为黑客帝国里的墨菲斯,我也不知作者这么比喻是为啥),<code>fmap</code> 知道自己该做什么:从 <code>Nothing</code> 开始就从 <code>Nothing</code> 结束!这也是 <code>Maybe</code> 类型存在的意义。我们通常使用类似如下的 Python 代码处理数据库:<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">post = Post.find_by_id(<span class="number">1</span>)</span><br><span class="line"><span class="keyword">if</span> post:</span><br><span class="line"> <span class="keyword">return</span> post.title</span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">None</span></span><br></pre></td></tr></table></figure></p>
<p>用 <code>Haskell</code> 可以写成 <code>fmap (getPostTitle) (findPost 1)</code>。如果 <code>findPost</code> 返回了一篇文章,我们就可以通过 <code>getPostTitle</code> 获取其标题。如果 <code>findPost</code> 返回了 <code>Nothing</code>,我们当然也应该返回 <code>Nothing</code>!是不是很简洁?<code>&lt;$&gt;</code> 是 <code>fmap</code> 的中缀版本,所以写成 <code>getPostTitle &lt;$&gt; (findPost 1)</code> 也是允许的,并且这种写法更常见。</p>
<p>再来看一个例子:把一个函数应用到 <code>List</code> 上会发生什么呢?<br><img src="http://qiniu.forec.cn/adit-faamip/fmap_list.png"><br><code>List</code> 也是 <code>Functor</code>!这是它的定义:<br><figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">instance</span> <span class="type">Functor</span> [] <span class="keyword">where</span></span></span><br><span class="line"> fmap = map</span><br></pre></td></tr></table></figure></p>
<p>我想你应该理解得差不多了,最后一个例子:如果把函数应用到另一个函数上呢,比如 <code>fmap (+3) (+1)</code> ?</p>
<p>这是一个函数:<br><img src="http://qiniu.forec.cn/adit-faamip/function_with_value.png"><br>将某个函数应用到另一个函数:<br><img src="http://qiniu.forec.cn/adit-faamip/fmap_function.png"><br>得到的结果是一个新函数!<br><figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&gt; <span class="import"><span class="keyword">import</span> Control.Applicative</span></span><br><span class="line">&gt; <span class="keyword">let</span> foo = fmap (+<span class="number">3</span>) (+<span class="number">2</span>)</span><br><span class="line">&gt; foo <span class="number">10</span></span><br><span class="line"><span class="number">15</span></span><br></pre></td></tr></table></figure></p>
<p>由此可见,函数同样是 <code>Functor</code>:<br><figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">instance</span> <span class="type">Functor</span> <span class="container">((-&gt;)</span> r) <span class="keyword">where</span></span></span><br><span class="line"> fmap f g = f . g</span><br></pre></td></tr></table></figure></p>
<p>函数的 <code>fmap</code> 其实就是函数复合。</p>
<h2 id="Applicative"><a href="#Applicative" class="headerlink" title="Applicative"></a>Applicative</h2><p><code>Applicative</code> 将 <code>Functor</code> 又提高了一个层次。与 <code>Functor</code> 类似,<code>Applicative</code> 中的值也被封装在上下文中:<br><img src="http://qiniu.forec.cn/adit-faamip/value_and_context.png"><br>不同之处在于,现在函数也被封装到上下文中:<br><img src="http://qiniu.forec.cn/adit-faamip/function_and_context.png"><br><code>Control.Applicative</code> 定义了 <code>&lt;*&gt;</code>,它知道如何将一个 <em>包装在上下文中的</em> 函数应用到 <em>包装在上下文的</em> 值上。举个例子,<code>Just (+3) &lt;*&gt; Just 2 == Just 5</code>:<br><img src="http://qiniu.forec.cn/adit-faamip/applicative_just.png"><br>使用 <code>&lt;*&gt;</code> 会产生很多有趣的情况,看看下面的 <code>List</code> 会发生什么:<br><figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&gt; [(*<span class="number">2</span>), (+<span class="number">3</span>)] &lt;*&gt; [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>]</span><br><span class="line">[<span class="number">2</span>, <span class="number">4</span>, <span class="number">6</span>, <span class="number">4</span>, <span class="number">5</span>, <span class="number">6</span>]</span><br></pre></td></tr></table></figure></p>
<p><img src="http://qiniu.forec.cn/adit-faamip/applicative_list.png"><br>下面是一些 <code>Applicative</code> 有而 <code>Functor</code> 不具备的功能。如何将一个接收两个参数的函数应用到两个被封装的值呢?<br><figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&gt; (+) &lt;$&gt; (<span class="type">Just</span> <span class="number">5</span>)</span><br><span class="line"><span class="type">Just</span> (+<span class="number">5</span>)</span><br><span class="line">&gt; <span class="type">Just</span> (+<span class="number">5</span>) &lt;$&gt; (<span class="type">Just</span> <span class="number">4</span>)</span><br><span class="line"><span class="type">ERROR</span> ??? <span class="type">WHAT</span> <span class="type">DOES</span> <span class="type">THIS</span> <span class="type">EVEN</span> <span class="type">MEAN</span> <span class="type">WHY</span> <span class="type">IS</span> <span class="type">THE</span> <span class="type">FUNCTION</span> <span class="type">WRAPPED</span> <span class="type">IN</span> <span class="type">A</span> <span class="type">JUST</span></span><br></pre></td></tr></table></figure></p>
<p><code>Applicative</code>:<br><figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&gt; (+) &lt;$&gt; (<span class="type">Just</span> <span class="number">5</span>)</span><br><span class="line"><span class="type">Just</span> (+<span class="number">5</span>)</span><br><span class="line">&gt; <span class="type">Just</span> (+<span class="number">5</span>) &lt;*&gt; (<span class="type">Just</span> <span class="number">3</span>)</span><br><span class="line"><span class="type">Just</span> <span class="number">8</span></span><br></pre></td></tr></table></figure></p>
<p><code>Applicative</code> 把 <code>Functor</code> 丢到了一边。“我今天就教你们一点人生经验”,<code>Applicative</code> 如是说,“在装备了 <code>&lt;$&gt;</code> 和 <code>&lt;*&gt;</code> 后,我可以接受任何函数,之后我把对应的封装值喂给它们,最后我就得到了一个封装好的值!哈哈哈哈哈!”<br><figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&gt; (*) &lt;$&gt; <span class="type">Just</span> <span class="number">5</span> &lt;*&gt; <span class="type">Just</span> <span class="number">3</span></span><br><span class="line"><span class="type">Just</span> <span class="number">15</span></span><br></pre></td></tr></table></figure></p>
<p>对了,这种模式可以用 <code>liftA2</code> 简化:<br><figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&gt; liftA2 (*) (<span class="type">Just</span> <span class="number">5</span>) (<span class="type">Just</span> <span class="number">3</span>)</span><br><span class="line"><span class="type">Just</span> <span class="number">15</span></span><br></pre></td></tr></table></figure></p>
<h2 id="Monad"><a href="#Monad" class="headerlink" title="Monad"></a>Monad</h2><p>如何学习 <code>Monad</code>:</p>
<ul>
<li>在计算机科学专业取得博士学位。</li>
<li>不学。因为这一节你根本用不到它!</li>
</ul>
<p><code>Functor</code> 将一个普通函数应用到被封装的值上:<br><img src="http://qiniu.forec.cn/adit-faamip/fmap.png"><br><code>Applicative</code> 将一个封装的函数应用到封装值上:<br><img src="http://qiniu.forec.cn/adit-faamip/applicative.png"><br><code>Monad</code> 将一个 <strong>“接受一个普通值并回传一个被封装的值”</strong> 的函数应用到一个被封装的值上,这一任务由函数 <code>&gt;&gt;=</code> (读作 “bind”)完成。听起来似乎很拗口,让我们来看个例子吧,还是熟悉的 <code>Maybe</code>:<br><img src="http://qiniu.forec.cn/adit-faamip/context.png"><br>假设 <code>half</code> 是只对偶数感兴趣的函数:<br><figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="title">half</span> x = <span class="keyword">if</span> even x</span><br><span class="line"> <span class="keyword">then</span> <span class="type">Just</span> (x `div` <span class="number">2</span>)</span><br><span class="line"> <span class="keyword">else</span> <span class="type">Nothing</span></span><br></pre></td></tr></table></figure></p>
<p><img src="http://qiniu.forec.cn/adit-faamip/half.png"><br>如果给 <code>half</code> 一个被封装的值会怎样?<br><img src="http://qiniu.forec.cn/adit-faamip/half_ouch.png"><br>这时我们需要用 <code>&gt;&gt;=</code> 把被封装的值挤到 <code>half</code> 中。看看 <code>&gt;&gt;=</code> 的照片:<br><img src="http://qiniu.forec.cn/adit-faamip/plunger.jpg"><br>再看看它的效果:<br><figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">&gt; <span class="type">Just</span> <span class="number">3</span> &gt;&gt;= half</span><br><span class="line"><span class="type">Nothing</span></span><br><span class="line">&gt; <span class="type">Just</span> <span class="number">4</span> &gt;&gt;= half</span><br><span class="line"><span class="type">Just</span> <span class="number">2</span></span><br><span class="line">&gt; <span class="type">Nothing</span> &gt;&gt;= half</span><br><span class="line"><span class="type">Nothing</span></span><br></pre></td></tr></table></figure></p>
<p>这其中究竟发生了什么?<code>Monad</code> 是另一种类型类,这是它定义的一部分:<br><figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="type">Monad</span> m <span class="keyword">where</span></span></span><br><span class="line"> (&gt;&gt;=) :: m a -&gt; (a -&gt; m b) -&gt; m b</span><br></pre></td></tr></table></figure></p>
<p>下图展示了 <code>&gt;&gt;=</code> 各个参数的意义:<br><img src="http://qiniu.forec.cn/adit-faamip/bind_def.png"><br>下面的定义让 <code>Maybe</code> 成为了 <code>Monad</code>:<br><figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">instance</span> <span class="type">Monad</span> <span class="type">Maybe</span> <span class="keyword">where</span></span></span><br><span class="line"> <span class="type">Nothing</span> &gt;&gt;= func = <span class="type">Nothing</span></span><br><span class="line"> <span class="type">Just</span> val &gt;&gt;= func = func val</span><br></pre></td></tr></table></figure></p>
<p>来看看执行 <code>Just 3 &gt;&gt;= half</code> 时发生了什么:<br><img src="http://qiniu.forec.cn/adit-faamip/monad_just.png"><br>如果传入 <code>Nothing</code> 就更容易了:<br><img src="http://qiniu.forec.cn/adit-faamip/monad_nothing.png"><br>这些调用过程还可以被连起来,比如执行 <code>Just 20 &gt;&gt;= half &gt;&gt;= half &gt;&gt;= half</code> 会得到 <code>Nothing</code>:<br><img src="http://qiniu.forec.cn/adit-faamip/monad_chain.png"><br><img src="http://qiniu.forec.cn/adit-faamip/whoa.png"><br>流弊!现在我们知道,<code>Maybe</code> 既是 <code>Functor</code>,又是 <code>Applicative</code>,还是 <code>Monad</code>。 </p>
<p>再来看另一个例子:<code>IO Monad</code>。<br><img src="http://qiniu.forec.cn/adit-faamip/io.png"> </p>
<p>介绍三个函数先。 </p>
<ul>
<li><code>getLine</code> 不接受参数并获取用户输入(<code>getLine :: IO String</code>):<br><img src="http://qiniu.forec.cn/adit-faamip/getLine.png"> </li>
<li><code>readFile</code> 接受一个字符串(文件路径)并返回文件的内容(<code>readFile :: FilePath -&gt; IO String</code>,<code>FilePath</code> 是 <code>String</code> 的别名):<br><img src="http://qiniu.forec.cn/adit-faamip/readFile.png"> </li>
<li><code>putStrLn</code> 接受一个字符串并打印它(<code>putStrLn :: String -&gt; IO ()</code>):<br><img src="http://qiniu.forec.cn/adit-faamip/putStrLn.png"> </li>
</ul>
<p>这三个函数都接受一个正常的值(或者不接受值)并且回传一个被封装在 <code>IO Monad</code> 中的值。我们可以用 <code>&gt;&gt;=</code> 把它们串起来!<br><img src="http://qiniu.forec.cn/adit-faamip/monad_io.png"><br><figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="title">getLine</span> &gt;&gt;= readFile &gt;&gt;= putStrLn</span><br></pre></td></tr></table></figure></p>
<p>Haskell 还为我们提供了 <code>do</code>,它是 <code>Monad</code> 的语法糖:<br><figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="title">foo</span> = <span class="keyword">do</span></span><br><span class="line"> filename &lt;- getLine</span><br><span class="line"> contents &lt;- readFile filename</span><br><span class="line"> putStrLn contents</span><br></pre></td></tr></table></figure></p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ol>
<li>实现了 <code>Functor</code> 类型类的数据类型被称为 functor。</li>
<li>实现了 <code>Applicative</code> 类型类的数据类型被称为 applicative。</li>
<li>实现了 <code>Monad</code> 类型类的数据类型被称为 monad。</li>
<li><code>Maybe</code> 实现了这三种类型类,所以它同时是 functor、applicative 和 monad。 </li>
</ol>
<p>它们三个之间的区别是什么呢?<br><img src="http://qiniu.forec.cn/adit-faamip/recap.png"> </p>
<ul>
<li><strong><code>functors</code></strong> :使用 <code>fmap</code> 或 <code>&lt;$&gt;</code> 把一个普通函数应用到被封装的值上</li>
<li><strong><code>applicatives</code></strong> :使用 <code>&lt;*&gt;</code> 或 <code>liftA</code> 把一个被封装的函数应用到被封装的值上</li>
<li><strong><code>monads</code></strong> :使用 <code>&gt;&gt;=</code> 或 <code>liftM</code> 把一个接受普通值、回传封装值的函数应用到一个被封装的值上</li>
</ul>
<p>亲爱的朋友(我觉得我们算是朋友了),现在你是否觉得 monad 是一个简单并聪明的概念呢?既然你已经读完了这篇 “科普文”,不如进一步了解一下 monad:LYAH 编写的 <a href="http://learnyouahaskell.com/a-fistful-of-monads" target="_blank" rel="external">Monad 章节</a> 中包含了许多我在本文中忽略的信息,他写的非常棒,我就不在此赘述了。</p>
<p>更多与 Monad 相关的图文介绍,请看 <a href="http://blog.forec.cn/2017/03/02/translation-adit-tum/" target="_blank" rel="external">三种实用 monad</a>。</p>
<hr>
<p>英文原文链接: <a href="http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html" target="_blank" rel="external"><em>Functors, Applicatives, And Monads In Pictures</em></a> (Written by <a href="https://github.com/egonSchiele" target="_blank" rel="external">Aditya Bhargava</a>) </p>
<p>原创作品,允许转载,转载时无需告知,但请务必以超链接形式标明文章<a href="http://blog.forec.cn/2017/03/02/translation-adit-faamip/" target="_blank" rel="external">原始出处</a>(<a href="http://blog.forec.cn/2017/03/02/translation-adit-faamip/" target="_blank" rel="external">http://blog.forec.cn/2017/03/02/translation-adit-faamip/</a>) 、作者信息(<a href="http://forec.cn/" target="_blank" rel="external">Forec</a>)和本声明。</p>
</content>
<summary type="html">
<blockquote>
<p>这篇文章是 <a href="https://github.com/egonSchiele">Aditya Bhargava</a> 所著 <a href="http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html">《Functors, Applicatives, And Monads In Pictures》</a> 的中文译文,已联系原作者取得授权。另一版本的中文译文由 <a href="https://github.com/jiyinyiyong">题叶</a> 翻译,可在<a href="http://jiyinyiyong.github.io/monads-in-pictures/">此处</a>查看。</p>
</blockquote>
</summary>
<category term="Code" scheme="http://forec.github.io/categories/Code/"/>
<category term="Haskell" scheme="http://forec.github.io/tags/Haskell/"/>
<category term="函数式编程" scheme="http://forec.github.io/tags/%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B/"/>
</entry>
<entry>
<title>来看几种基本 Monad</title>
<link href="http://forec.github.io/2017/03/01/talk-about-some-simple-monads/"/>
<id>http://forec.github.io/2017/03/01/talk-about-some-simple-monads/</id>
<published>2017-03-01T13:52:16.000Z</published>
<updated>2018-09-26T06:08:29.431Z</updated>
<content type="html"><blockquote>
<p>@Fallenwood 选修的 《Foundations of Programming Languages》 课程让我看的很手痒。整理一下基本的 Typeclass 和 Monad,准备跟随贵科步伐重新学习 Haskell。</p>
</blockquote>
<a id="more"></a>
<ul>
<li><strong>只做整理不做总结,绝不写任何有关自己对 Monad 的理解。</strong></li>
</ul>
<h2 id="基本-Typeclass"><a href="#基本-Typeclass" class="headerlink" title="基本 Typeclass"></a>基本 Typeclass</h2><h3 id="Functor"><a href="#Functor" class="headerlink" title="Functor"></a>Functor</h3><ul>
<li><code>Functor</code> (<code>Data.Functor</code>)类型类表明型别可以被 <code>map</code> ,类型类声明为:</li>
</ul>
<figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="type">Functor</span> f <span class="keyword">where</span></span></span><br><span class="line"> fmap :: (a -&gt; b) -&gt; f a -&gt; f b</span><br></pre></td></tr></table></figure>
<ul>
<li><code>Functor</code> 类型类定义中 <code>f</code> 的 kind 是 <code>* -&gt; *</code>;<code>&lt;$&gt;</code> 是 <code>fmap</code> 的语法糖。</li>
<li><code>Functor</code> 类型类需要遵守以下守则:<ul>
<li><code>fmap id = id</code></li>
<li><code>fmap (f . g) = fmap f . fmap g</code></li>
</ul>
</li>
</ul>
<h3 id="Applicative"><a href="#Applicative" class="headerlink" title="Applicative"></a>Applicative</h3><ul>
<li><code>Applicative</code> (<code>Control.Applicative</code>)算是 <code>Functor</code> 的加强版,将一个 “包装” 在某个抽象型别中的函数应用到对应型别的值中,类型类声明为:</li>
</ul>
<figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="container">(<span class="type">Functor</span> <span class="title">f</span>)</span> =&gt; <span class="type">Applicative</span> f <span class="keyword">where</span></span></span><br><span class="line"> pure :: a -&gt; f a</span><br><span class="line"> (&lt;*&gt;) :: f (a -&gt; b) -&gt; f a -&gt; f b</span><br><span class="line"> (&lt;*) :: f a -&gt; f b -&gt; f a</span><br><span class="line"> (*&gt;) :: f a -&gt; f b -&gt; f b</span><br></pre></td></tr></table></figure>
<ul>
<li><code>Applicative</code> 类型类中定义的型别 <code>f</code> 必须也是 <code>Functor</code> 类型类的实例。<code>pure</code> 函数将值包装到 <code>Applicative Functor</code> 中。<code>&lt;*</code> 和 <code>*&gt;</code> 函数均有默认实现。</li>
<li>对于一个纯粹的函数 <code>func :: a -&gt; b</code>,可以通过 <code>fmap</code> 将其作用到一个 <code>Functor</code> 类型类上,也可以通过 <code>pure</code> 将 <code>func</code> 提升到 <code>Applicative Functor</code> 中,再利用 <code>&lt;*&gt;</code> 将其运用到该类型类包装的值上。例如,<code>pure (+) &lt;*&gt; (Just 2) &lt;*&gt; (Just 3)</code> 的结果是 <code>Just 5</code>。可以利用 <code>&lt;$&gt;</code> 这一语法糖简化为 <code>(+) &lt;$&gt; (Just 2) &lt;*&gt; (Just 3)</code>。</li>
<li><code>Applicative</code> 类型类需要遵守如下守则(必然也满足 <code>Functor Laws</code>):<ul>
<li><code>pure f &lt;*&gt; x = fmap f x</code></li>
<li><code>pure id &lt;*&gt; x = x</code></li>
<li><code>pure (.) &lt;*&gt; u &lt;*&gt; v &lt;*&gt; w = u &lt;*&gt; (v &lt;*&gt; w)</code></li>
<li><code>pure f &lt;*&gt; pure x = pure (f x)</code></li>
<li><code>u &lt;*&gt; pure y = pure ($ y) &lt;*&gt; u</code></li>
</ul>
</li>
</ul>
<h3 id="Monoid"><a href="#Monoid" class="headerlink" title="Monoid"></a>Monoid</h3><ul>
<li><code>Monoid</code> (<code>Data.Monoid</code>)类型类的定义如下,它对应实例的型别 <code>m</code> 的 kind 是 <code>*</code>:</li>
</ul>
<figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="type">Monoid</span> m <span class="keyword">where</span></span></span><br><span class="line"> mempty :: m</span><br><span class="line"> mappend :: m -&gt; m -&gt; m</span><br><span class="line"> mconcat :: [m] -&gt; m</span><br><span class="line"> mconcat = foldr mappend mempty</span><br></pre></td></tr></table></figure>
<ul>
<li><code>Monoid</code> 指群论中的半群,其需满足封闭性和右结合律。<code>Monoid</code> 的名字看起来像是 <code>mono</code> 和 <code>id</code> 的组合,即 “单幺元”。这里的 <code>mempty</code> 和下面 <code>MonadPlus</code> 中的 <code>mzero</code> 均为幺元。</li>
<li>几种常见的 <code>Monoid</code> 如: <code>List</code>、<code>Any</code>、<code>All</code>、<code>Sum</code>、<code>Product</code>、<code>Ordering</code>、<code>Maybe</code> 等。</li>
<li><code>Monoid</code> 需要遵守如下守则:<ul>
<li><code>mappend mempty x = x</code></li>
<li><code>mappend x mempty = x</code></li>
<li><code>mappend (mappend x y) z = mappend x (mappend y z)</code></li>
</ul>
</li>
<li><code>Monoid</code> 也应用在 <code>Foldable</code> 类型类的 <code>foldMap</code> 函数中。<code>foldMap</code> 的型别声明为 <code>(Foldable t, Monoid m) =&gt; (a -&gt; m) -&gt; t a -&gt; m</code>。可以为自定义类型实作 <code>Foldable</code> 类型类,即可通过 <code>foldMap</code> 对自定义类型的元素做 map over、折叠等操作。注意 <code>foldMap</code> 和 <code>fmap</code> 的区别在于 <code>foldMap</code> 不会将函数返回值再次包装到原类型类中,而是包装到 <code>Monoid</code> 中。举个例子,对于自定义类型 <code>data Tree a = Node a (Tree a) (Tree a) | Empty</code>,要想确定树中有无小于 0 的元素,只需 <code>getAny $ foldMap (\x -&gt; Any $ x &lt; 0) tree</code>,这里 <code>foldMap</code> 把树中每个元素映射到 <code>Any Monoid</code> 中。</li>
</ul>
<h3 id="Monad"><a href="#Monad" class="headerlink" title="Monad"></a>Monad</h3><ul>
<li><code>Monad</code> (<code>Control.Monad</code>)类型类定义如下:</li>
</ul>
<figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="type">Applicative</span> m =&gt; <span class="type">Monad</span> m <span class="keyword">where</span></span></span><br><span class="line"> (&gt;&gt;=) :: m a -&gt; (a -&gt; m b) -&gt; m b</span><br><span class="line"> (&gt;&gt;) :: m a -&gt; m b -&gt; m b</span><br><span class="line"> x &gt;&gt; y = x &gt;&gt;= \_ -&gt; y</span><br><span class="line"> return :: a -&gt; m a</span><br><span class="line"> fail :: <span class="type">String</span> -&gt; m a</span><br><span class="line"> fail msg = error msg</span><br></pre></td></tr></table></figure>
<ul>
<li><code>Monad</code> 的实例本身必须是 <code>Applicative</code> 的实例。其类型类定义中已经默认实现了 <code>&gt;&gt;</code> 和 <code>fail</code>,定义实例时可以重写这些函数,也可以只实现 <code>return</code> 和 <code>&gt;&gt;=</code>。<code>return</code> 等价于 <code>pure</code>。</li>
<li>语法 <code>do</code> 可帮助把一些 <code>Monad</code> 操作连接在一起,其包裹的代码的每一行均为一个 <code>Monad</code> 实例的值。</li>
<li><code>List</code> 的 <code>Monad</code> 实例定义如下:</li>
</ul>
<figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">instance</span> <span class="type">Monad</span> [] <span class="keyword">where</span></span></span><br><span class="line"> return x = [x]</span><br><span class="line"> xs &gt;&gt;= f = concat (map f xs)</span><br><span class="line"> fail _ = []</span><br></pre></td></tr></table></figure>
<ul>
<li>从 <code>List</code> 实例定义可看出,<code>&gt;&gt;=</code> 类似过程式语言中的循环嵌套,即将 <code>&gt;&gt;=</code> 左侧的 List 中的每个元素依次应用到右侧的函数上。List Comprehension 仅仅是它的语法糖,如 <code>[x | x &lt;- [1..10], x</code>mod<code>3 == 0]</code> 等价于 <code>[1..10] &gt;&gt;= \x -&gt; if x</code>mod<code>3 == 0 then [x] else []</code>。</li>
<li><code>Monad</code> 需要遵守如下守则:<ul>
<li><code>return x &gt;&gt;= f = f x</code></li>
<li><code>m &gt;&gt;= return = m</code></li>
<li><code>(m &gt;&gt;= f) &gt;&gt;= g = m &gt;&gt;= (\x -&gt; f x &gt;&gt;= g)</code></li>
</ul>
</li>
</ul>
<h3 id="MonadPlus"><a href="#MonadPlus" class="headerlink" title="MonadPlus"></a>MonadPlus</h3><ul>
<li>上面的 List Comprehension 也等价于 <code>[1..10] &gt;&gt;= \x -&gt; guard (x</code>mod<code>3 == 0) &gt;&gt; return x</code>。这需要用到 <code>MonadPlus</code> 类型类,它指同时表现为 <code>Monoid</code> 的 <code>Monad</code>,其定义为:</li>
</ul>
<figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="type">Monad</span> m =&gt; <span class="type">MonadPlus</span> m <span class="keyword">where</span></span></span><br><span class="line"> mzero :: m a</span><br><span class="line"> mplus :: m a -&gt; m a -&gt; m a</span><br></pre></td></tr></table></figure>
<ul>
<li><code>mzero</code> 等价于 <code>mempty</code>,<code>mplus</code> 等价于 <code>mappend</code>。 <code>guard</code> 函数的定义如下。当 <code>guard</code> 监察的 <code>Bool</code> 变量为 <code>True</code> 时,<code>return</code> 会返回一个空 unit,否则 在 List Comprehension 例子中,<code>mzero = []</code> 不会产生任何结果。 </li>
</ul>
<figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="title">guard</span> :: (<span class="type">MonadPlus</span> m) =&gt; <span class="type">Bool</span> -&gt; m ()</span><br><span class="line"><span class="title">guard</span> <span class="type">True</span> = return ()</span><br><span class="line"><span class="title">guard</span> <span class="type">False</span> = mzero</span><br></pre></td></tr></table></figure>
<h2 id="常用-Monad"><a href="#常用-Monad" class="headerlink" title="常用 Monad"></a>常用 Monad</h2><h3 id="Writer"><a href="#Writer" class="headerlink" title="Writer"></a>Writer</h3><ul>
<li><code>Writer</code> (<code>Control.Monad.Writer</code>)的定义和 <code>Monad</code> 实例定义如下,该模块并未导出其值构造子。</li>
</ul>
<figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="typedef"><span class="keyword">newtype</span> <span class="type">Writer</span> w a = <span class="type">Writer</span> <span class="container">&#123; <span class="title">runWriter</span> :: (<span class="title">a</span>, <span class="title">w</span>) &#125;</span></span></span><br><span class="line"><span class="class"></span><br><span class="line"><span class="keyword">instance</span> <span class="container">(<span class="type">Monoid</span> <span class="title">w</span>)</span> =&gt; <span class="type">Monad</span> <span class="container">(<span class="type">Writer</span> <span class="title">w</span>)</span> <span class="keyword">where</span></span></span><br><span class="line"> return x = <span class="type">Writer</span> (x, mempty)</span><br><span class="line"> (<span class="type">Writer</span> (x, v)) &gt;&gt;= f = <span class="keyword">let</span> <span class="type">Writer</span> (y, v') = f x <span class="keyword">in</span> <span class="type">Writer</span> (y, v `mappend` v')</span><br></pre></td></tr></table></figure>
<ul>
<li>可用 <code>tell</code> 向 Writer 加入 log。</li>
</ul>
<h3 id="gt-r"><a href="#gt-r" class="headerlink" title="((-&gt;) r)"></a>((-&gt;) r)</h3><ul>
<li>函数也是 <code>Monad</code>,其实例为:</li>
</ul>
<figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">instance</span> <span class="type">Monad</span> <span class="container">((-&gt;)</span> r) <span class="keyword">where</span></span></span><br><span class="line"> return = const</span><br><span class="line"> g &gt;&gt;= f = \v -&gt; f (g v) v</span><br></pre></td></tr></table></figure>
<h3 id="Reader"><a href="#Reader" class="headerlink" title="Reader"></a>Reader</h3><ul>
<li><code>Reader</code> (对函数 Monad 的一种包装):</li>
</ul>
<figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="typedef"><span class="keyword">newtype</span> <span class="type">Reader</span> s a = <span class="type">Reader</span><span class="container">&#123; <span class="title">runReader</span> :: <span class="title">s</span> -&gt; <span class="title">a</span> &#125;</span></span></span><br><span class="line"><span class="class"></span><br><span class="line"><span class="keyword">instance</span> <span class="type">Monad</span> <span class="container">(<span class="type">Reader</span> <span class="title">s</span>)</span> <span class="keyword">where</span></span></span><br><span class="line"> return x = <span class="type">Reader</span> (const x)</span><br><span class="line"> m &gt;&gt;= k = <span class="type">Reader</span> $ \r -&gt; runReader (k (runReader m r)) r</span><br></pre></td></tr></table></figure>
<h3 id="State"><a href="#State" class="headerlink" title="State"></a>State</h3><ul>
<li><code>State</code> (<code>Control.Monad.State</code>)常用来表示状态迁移,代表了改变状态的操作:</li>
</ul>
<figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="typedef"><span class="keyword">newtype</span> <span class="type">State</span> s a = <span class="type">State</span><span class="container">&#123;<span class="title">runState</span> :: <span class="title">s</span> -&gt; (<span class="title">a</span>, <span class="title">s</span>)&#125;</span></span></span><br><span class="line"><span class="class"></span><br><span class="line"><span class="keyword">instance</span> <span class="type">Monad</span> <span class="container">(<span class="type">State</span> <span class="title">s</span>)</span> <span class="keyword">where</span></span></span><br><span class="line"> return x = <span class="type">State</span> $ \s -&gt; (x, s)</span><br><span class="line"> (<span class="type">State</span> h) &gt;&gt;= f = <span class="type">State</span> $ \s -&gt; <span class="keyword">let</span> (a, newState) = h s</span><br><span class="line"> (<span class="type">State</span> g) = f a</span><br><span class="line"> <span class="keyword">in</span> g newState</span><br></pre></td></tr></table></figure>
<hr>
<p>原创作品,允许转载,转载时无需告知,但请务必以超链接形式标明文章<a href="http://blog.forec.cn/2017/03/01/talk-about-some-simple-monads/" target="_blank" rel="external">原始出处</a>(<a href="http://blog.forec.cn/2017/03/01/talk-about-some-simple-monads/" target="_blank" rel="external">http://blog.forec.cn/2017/03/01/talk-about-some-simple-monads/</a>) 、作者信息(<a href="http://forec.cn/" target="_blank" rel="external">Forec</a>)和本声明。</p>
</content>
<summary type="html">
<blockquote>
<p>@Fallenwood 选修的 《Foundations of Programming Languages》 课程让我看的很手痒。整理一下基本的 Typeclass 和 Monad,准备跟随贵科步伐重新学习 Haskell。</p>
</blockquote>
</summary>
<category term="Code" scheme="http://forec.github.io/categories/Code/"/>
<category term="Haskell" scheme="http://forec.github.io/tags/Haskell/"/>
<category term="函数式编程" scheme="http://forec.github.io/tags/%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B/"/>
</entry>
<entry>
<title>形式语言</title>
<link href="http://forec.github.io/2017/02/25/formal-languages-and-automata1/"/>
<id>http://forec.github.io/2017/02/25/formal-languages-and-automata1/</id>
<published>2017-02-25T15:33:13.000Z</published>
<updated>2018-09-26T06:08:29.234Z</updated>
<content type="html"><blockquote>
<p>这个寒假遇到的一些问题让我想起之前形式语言与自动机的内容,程序执行的本质是状态的变化,我觉得有必要将这部分理论捡起来,需要的时候方便自己回忆。</p>
</blockquote>
<a id="more"></a>
<h2 id="概念整理"><a href="#概念整理" class="headerlink" title="概念整理"></a>概念整理</h2><ul>
<li><strong>形式语言</strong> 是形式化描述的 <strong>字母表</strong> 上的 <strong>字符串</strong> 的集合。<em>字母表</em> 为字符的有限集合,多用 <code>T</code> 表示;<em>字符串</em> 指字母表中的字符构成的 <strong>有限</strong> 序列。</li>
<li><strong>自动机</strong> 接受一定的输入,执行一定的动作,产生一定的结果。使用状态迁移描述整个过程。例如可实现一个用于识别字符串的自动机系统,根据输入的字符串解析形式语言。<strong>状态</strong> 是一个标识,用于区分自动机在不同时刻的状况;自动机的本质是 <strong>根据状态、输入和规则决定下一个状态</strong> (状态迁移)。</li>
<li>有限自动机可以认为是由一个带有读写头的有限控制器和一条写有字符的输入带组成。</li>
<li>关系:<table><tbody><tr><td>形式语言</td><td>非限定性语言</td><td>上下文有关语言</td><td>上下文无关语言</td><td>正则语言</td></tr><br><tr><td>自动机</td><td>图灵机</td><td>线性有界自动机</td><td>下推式存储自动机</td><td>有限自动机</td></tr></tbody></table>
</li>
</ul>
<h2 id="语言及文法"><a href="#语言及文法" class="headerlink" title="语言及文法"></a>语言及文法</h2><ul>
<li>字符串的运算:字符串 <code>ω</code> 的长度记为 <code>|ω|</code>, <code>ω</code> 的逆记为 <code>ω^T</code>。字符串的 <strong>连接</strong> 满足结合律,且有 <code>εω = ωε = ω</code>,<code>|ω1ω2| = |ω1| + |ω2|</code>。空串 <code>ε</code> 是任何串的前/后缀和字串。</li>
<li>字母表的 <strong>幂运算</strong>:设 T 为字母表,n∈N。<ol>
<li>T0 = {ε}</li>
<li>设 x∈T(n-1),a∈T,则 ax∈Tn</li>
<li>Tn 中的元素只能由 (1) 和 (2) 构成</li>
</ol>
</li>
<li><strong>闭包</strong>(*):<code>T* = Σ Tk (k = 0..n) = T0 ∪ T1 ∪ ... ∪ Tn</code>,闭包(+):<code>T+ = Σ Tk (k = 1..n)</code>,即 <code>T* = T+ ∪ {ε}</code>。</li>
<li><strong>语言</strong>:T 为字母表,任何集合 L ⊆ T* 是字母表 T 上的一个语言。<ul>
<li>积运算:L1 与 L2 的积是 L1 和 L2 中字符串相连的集合,不满足交换律</li>
<li>幂运算:<code>L0 = {ε}, Ln = L · L(n-1) = L(n-1) · L (n ≥ 1)</code></li>
<li><code>∅</code> 表示不存在任何句子的语言,<code>{ε}</code> 表示仅存在空句子的语言</li>
</ul>
</li>
<li><strong>文法</strong>:用来定义语言的数学模型称为文法。<ul>
<li>语言 L 为有限集合:通过列举表示</li>
<li>语言 L 为无限集合,通过文法产生系统或机器识别系统</li>
<li>元语言:描述语言的语言,文法是一种元语言</li>
</ul>
</li>
<li><strong>BNF(巴科斯范式)</strong>:</li>
</ul>
<figure class="highlight gherkin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable">&lt;数字&gt;</span> ::= 0 |<span class="string"> 1 </span>|<span class="string"> 2 </span>|<span class="string"> .. </span>|<span class="string"> 9</span><br><span class="line">&lt;字母&gt; ::= A </span>|<span class="string"> B </span>|<span class="string"> C </span>|<span class="string"> .. </span>|<span class="string"> Z </span>|<span class="string"> a </span>|<span class="string"> b </span>|<span class="string"> c </span>|<span class="string"> .. </span>|<span class="string"> z</span><br><span class="line">&lt;标识符&gt; ::= &lt;字母&gt; </span>|<span class="string"> &lt;标识符&gt;&lt;字母&gt; </span>|<span class="string"> &lt;标识符&gt;&lt;数字&gt;</span></span><br></pre></td></tr></table></figure>
<ul>
<li><strong>Chomsky 文法体系</strong>:任何一种文法必包含有:两个不同的有限符号集合(终结符号集合 N 和非终结符号集合 T);一个形式化规则的有限集合 P (又称生成式集合);一个起始符 S。</li>
<li>文法的 <strong>形式定义</strong> :文法 G 是一个四元组,<code>G = (N, T, P, S)</code>。其中 N ∩ T = ∅;P 为形式为 <code>α → β</code> 的生成式 <strong>有限</strong> 集合,且 <code>α ∈ (N∪T)* N+ (N∪T)*</code>,<code>β ∈ (N∪T)*</code>;S 是起始符,S ∈ N。</li>
</ul>
<h2 id="推导和句型"><a href="#推导和句型" class="headerlink" title="推导和句型"></a>推导和句型</h2><ul>
<li>直接推导:<code>G = (N, T, P, S)</code>,有 <code>A → β</code> 是 P 中的生成式,α 和 γ 均属于 <code>(N ∪ T)*</code>,则 <code>αAγ ⇒ αβγ</code> 称 αAγ 直接导出 αβγ,或者说 αβγ 是 αAγ 的直接推导;</li>
<li>推导序列:<code>α = α0 ⇒ α1 ⇒ ... ⇒ αn</code> 是长度为 n 的推导序列,<code>α = α0</code> 是长度为 0 的推导序列。对 α 推导出 α’,记为 <code>α ⇒(*, G) α&#39;</code>,若 α 推导出 α’ 用了长度大于 0 的推导序列,则记为 <code>α ⇒(+, G) α&#39;</code>。推导序列的每一步都会产生一个字符串,这些字符串称为 <strong>句型</strong>。</li>
<li>字符串 <code>α</code> 是文法 G 的句型,当且仅当 <code>S ⇒(*, G) α</code>,且 <code>α ∈ (N ∪ T)*</code>。句型包含 <strong>句子</strong>,ω 是 G 的句子,当且仅当 <code>S ⇒(*, G) ω</code> 且 <code>ω ∈ T*</code>,即必须由起始符集合 S 推导出,并且由终结符集合构成的。</li>
<li>例:括号匹配语言,定义集合基本元素 <code>()</code>,递归产生其他句子:若 S 为合法串,则 <code>(S)</code> 和 <code>SS</code> 均合法。故 P 中包含三种生成式:<code>S → (); S → (S); S → SS</code>。</li>
</ul>
<h2 id="Chomsky-文法体系分类"><a href="#Chomsky-文法体系分类" class="headerlink" title="Chomsky 文法体系分类"></a>Chomsky 文法体系分类</h2><ul>
<li>0 型文法:无限制文法 ⇒ 无限制语言,递归可枚举语言(图灵机)</li>
<li><strong>1 型文法</strong>:上下文有关文法,CSG ⇒ 上下文有关语言,CSL(线性有界自动机,LBA)<ul>
<li>生成式形式:<code>α → β</code>,其中 <code>|α| ≤ |β|, β ∈ (N ∪ T)+, α ∈ (N ∪ T)* N+ (N ∪ T)*</code>,即满足生成式的 <strong>左侧短于右侧</strong> ,且 <strong>不包含 <code>A → ε</code></strong> 。</li>
<li>应用:过程式语言调用时形参与实参的一致性检查。</li>
</ul>
</li>
<li><strong>2 型文法</strong>:上下文无关文法,CFG ⇒ 上下文无关语言,CEL(下推自动机,PDA)<ul>
<li>生成式形式:<code>A → B</code>,其中 <code>A ∈ N, β ∈ (N ∪ T)*</code>,可以包含 <code>A → ε</code>。</li>
<li>每个生成式左侧都是 <strong>单个非终结符</strong>。</li>
</ul>
</li>
<li><strong>3 型文法</strong>:正则文法 ⇒ 正则语言(有限自动机,FSA)<ul>
<li>左线性文法:<code>A → Bω</code> 或 <code>A → ω</code>,其中 <code>A, B ∈ N, ω ∈ T*</code>;</li>
<li>右线性文法:<code>A → ωB</code> 或 <code>A → ω</code>,其中 <code>A, B ∈ N, ω ∈ T*</code>。</li>
</ul>
</li>
<li>已知语言求特定文法,得到的文法不是唯一的。</li>
<li>几种文法之间关系:<ul>
<li>0 型:无限制,包括 1、2、3 型文法;</li>
<li>1 型:不允许 A → ε,包含不含 A → ε 的 2、3 型文法</li>
<li>2 型:包含 3 型文法</li>
</ul>
</li>
</ul>
<hr>
<p>专栏目录:<a href="http://blog.forec.cn/columns/cs-basic.html" target="_blank" rel="external">计算机理论基础</a><br>此专栏的上一篇文章:无<br>此专栏的下一篇文章:<a href="http://blog.forec.cn/2017/03/18/formal-languages-and-automata2/" target="_blank" rel="external">有限自动机</a> </p>
<p>参考资料:《形式语言与自动机》,王柏、杨娟编著,北京邮电大学出版社 </p>
<p>原创作品,允许转载,转载时无需告知,但请务必以超链接形式标明文章<a href="http://blog.forec.cn/2017/02/25/formal-languages-and-automata1/" target="_blank" rel="external">原始出处</a>(<a href="http://blog.forec.cn/2017/02/25/formal-languages-and-automata1/" target="_blank" rel="external">http://blog.forec.cn/2017/02/25/formal-languages-and-automata1/</a>) 、作者信息(<a href="http://forec.cn/" target="_blank" rel="external">Forec</a>)和本声明。</p>
</content>
<summary type="html">
<blockquote>
<p>这个寒假遇到的一些问题让我想起之前形式语言与自动机的内容,程序执行的本质是状态的变化,我觉得有必要将这部分理论捡起来,需要的时候方便自己回忆。</p>
</blockquote>
</summary>
<category term="计算机理论基础" scheme="http://forec.github.io/categories/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80/"/>
<category term="自动机" scheme="http://forec.github.io/tags/%E8%87%AA%E5%8A%A8%E6%9C%BA/"/>
</entry>
<entry>
<title>用 Haskell 实现解释器</title>
<link href="http://forec.github.io/2017/02/14/talk-about-interpreter/"/>
<id>http://forec.github.io/2017/02/14/talk-about-interpreter/</id>
<published>2017-02-14T12:38:16.000Z</published>
<updated>2018-09-26T06:08:29.212Z</updated>
<content type="html"><blockquote>
<p>这篇文章主要基于王垠早年发过的文章<a href="http://www.yinwang.org/blog-cn/2012/08/01/interpreter" target="_blank" rel="external">《怎样写一个解释器》</a>,我参考了 Racket 版本的 R2 解释器,并用 Haskell 实现 <code>H2Lang</code> 的简单解释器,较 R2 的功能做了一点改进。</p>
</blockquote>
<a id="more"></a>
<h2 id="代码的表示"><a href="#代码的表示" class="headerlink" title="代码的表示"></a>代码的表示</h2><ul>
<li>王垠的 R2 解释器用 Racket 实现,Racket 可以很容易地用 <code>&#39;(op e1 e1)</code> 的形式表示 S-expr,并且 lambda 表达式也可以复用。<a href="http://fallenwood.github.io/2017/02/04/writing-a-simple-lisp/" target="_blank" rel="external">Fallenwood</a> 也用 Python 实现了一个类似的 Lisp 解释器,他将操作符和表达式均以列表的形式存储,利用了 Python 的动态类型。知乎上 “<a href="https://www.zhihu.com/question/20115358" target="_blank" rel="external">如何写 Lisp 解释器</a>” 这个问题下,答主 <a href="https://www.zhihu.com/people/be5invis/answers" target="_blank" rel="external">Belleve</a> 给出了 JS 实现的 Lisp 解释器,并实现了 <code>call/cc</code>。</li>
<li>Haskell 是静态类型,没法把动态类型列表迭代那一套搬过来,因此基本思路和王垠文章中所述类似。为了方便起见,我声明新的类型,并用字符串表示值操作符:</li>
</ul>
<figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="typedef"><span class="keyword">data</span> <span class="type">Exp</span> = <span class="type">Value</span> <span class="type">Float</span> |</span></span><br><span class="line"> <span class="type">Boolean</span> <span class="type">Bool</span> |</span><br><span class="line"> <span class="type">String'</span> <span class="type">String</span> |</span><br><span class="line"> <span class="type">Param</span> <span class="type">String</span> |</span><br><span class="line"> <span class="type">Error</span> <span class="type">String</span> |</span><br><span class="line"> <span class="type">Op</span> <span class="type">String</span> <span class="type">Exp</span> <span class="type">Exp</span> |</span><br><span class="line"> <span class="type">Lambda</span> <span class="type">Exp</span> <span class="type">Exp</span> |</span><br><span class="line"> <span class="type">If</span> <span class="type">Exp</span> <span class="type">Exp</span> <span class="type">Exp</span> |</span><br><span class="line"> <span class="type">Let</span> <span class="type">Exp</span> <span class="type">Exp</span> <span class="type">Exp</span> |</span><br><span class="line"> <span class="type">Closure</span> <span class="type">Exp</span> <span class="type">Env</span> |</span><br><span class="line"> <span class="type">Call</span> <span class="type">Exp</span> <span class="type">Exp</span> <span class="keyword">deriving</span> (<span class="type">Show</span>)</span><br></pre></td></tr></table></figure>
<ul>
<li>在上面的型别声明中,提供了三种类型的数据(<code>Float</code>、<code>Bool</code> 和 <code>String</code>),以及变量(<code>Param</code>)、错误信息(<code>Error</code>)、运算式(<code>Op</code>)、函数(<code>Lambda</code>)、条件表达式(<code>If</code>)、绑定(<code>Let</code>)、闭包(<code>Closure</code>)和函数调用(<code>Call</code>)。</li>
<li>出于方便考虑,只支持了二元运算符,这从 <code>Exp</code> 的声明中也能看出。如果想支持一元运算符,最简单的方式是增加型别的值构造子,并修改解释器的模式匹配;如果想支持多元运算符,可以绑定嵌套。</li>
<li><code>Closure</code> 值构造子有一个参数为 <code>Env</code>,它用于维护闭包内表达式所处的环境的副本。</li>
</ul>
<h2 id="变量、值的绑定"><a href="#变量、值的绑定" class="headerlink" title="变量、值的绑定"></a>变量、值的绑定</h2><ul>
<li>有了上述型别,简单的值可以通过对应的值构造子产生,如 <code>Value 2.34</code>、<code>Boolean True</code>、<code>String&#39; &quot;test&quot;</code> 等。</li>
<li>变量与值的绑定通过类似 <code>Data.Map</code> 的结构,因为值和函数、运算等都可归一为表达式 <code>Exp</code>,因此用一个 <code>[(String, Exp)]</code> 的 list 存放对当前代码区域可见的变量-值绑定,称之为环境。函数 ·extEnv` 扩展已有的环境。</li>
</ul>
<figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="typedef"><span class="keyword">type</span> <span class="type">Env</span> = [<span class="container">(<span class="type">String</span>, <span class="type">Exp</span>)</span>]</span></span><br><span class="line"><span class="title">extEnv</span> :: <span class="type">String</span> -&gt; <span class="type">Exp</span> -&gt; <span class="type">Env</span> -&gt; <span class="type">Env</span></span><br><span class="line"><span class="title">extEnv</span> x v env = (x, v) : env</span><br></pre></td></tr></table></figure>
<ul>
<li>需要查找变量时,在当前环境中检查有无对应的键。因为 <code>extEnv</code> 将后绑定的变量插入到环境的头部,因此可以屏蔽先插入的同名变量,从而模拟出变量的就近原则。</li>
</ul>
<h2 id="运算符的计算"><a href="#运算符的计算" class="headerlink" title="运算符的计算"></a>运算符的计算</h2><ul>
<li>为了保持解释器主体部分简短,我将运算符的计算提取成单独的函数。其大致结构如下:</li>
</ul>
<figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="title">calc</span> :: <span class="type">String</span> -&gt; <span class="type">Exp</span> -&gt; <span class="type">Exp</span> -&gt; <span class="type">Exp</span></span><br><span class="line"><span class="title">calc</span> <span class="string">"+"</span> (<span class="type">Value</span> v1') (<span class="type">Value</span> v2') = <span class="type">Value</span> (v1' + v2')</span><br><span class="line"><span class="title">calc</span> <span class="string">"-"</span> (<span class="type">Value</span> v1') (<span class="type">Value</span> v2') = <span class="type">Value</span> (v1' - v2')</span><br><span class="line"><span class="title">calc</span> <span class="string">"*"</span> (<span class="type">Value</span> v1') (<span class="type">Value</span> v2') = <span class="type">Value</span> (v1' * v2')</span><br><span class="line"><span class="title">calc</span> <span class="string">"/"</span> (<span class="type">Value</span> v1') (<span class="type">Value</span> v2') = <span class="type">Value</span> (v1' / v2')</span><br><span class="line"><span class="title">calc</span> ...... <span class="comment">-- other patterns</span></span><br></pre></td></tr></table></figure>
<ul>
<li>为了支持 <code>String&#39;</code> 和 <code>Boolean</code> 类型的计算,<code>calc</code> 函数必须为每种类型均增加模式匹配。</li>
</ul>
<h2 id="函数声明和调用"><a href="#函数声明和调用" class="headerlink" title="函数声明和调用"></a>函数声明和调用</h2><ul>
<li><code>Exp</code> 型别有一个 <code>Lambda</code> 值构造子用来声明函数,解释器遇到 <code>Lambda</code> 表达式时,会将其转化为 <code>Closure</code> 值类型,即将该函数所处的环境保存下来,这么做的目的与 Lexical Scoping 和 Dynamic Scoping 有关。这一点在王垠的文章中讲的很清楚,这里简单提一下。Lexical Scoping,中文为静态域或者词法定界,Dynamic Scoping 为动态作用域,举个例子,<code>let x = 2 in (let f = \y-&gt; x * y in (let x = 4 in (f 3)))</code>,如果结果为 6 就是 Lexical Scoping,结果为 12 就是 Dynamic Scoping。Dynamic Scoping 会带来很多意想不到的后果,因此要想实现静态域,就要在函数定义时保存其所处的环境,并在函数调用时从该环境中提取变量绑定。</li>
<li>实现的 <code>H2Lang</code> 解释器会在匹配到 <code>Lambda</code> 表达式时将其转化为闭包:<code>interp s@(Lambda _ _) env = Closure s env</code>。</li>
<li>为了方便区分普通表达式和函数调用,我在 <code>Exp</code> 的型别中声明了 <code>Call</code> 值构造子,它将两个表达式组合到一起,并认定第一个表达式代表函数,第二个表达式代表某个变量或者值。因为多元函数可以用柯里化不断简化,因此解释器就不做处理了,在调用时可以通过 <code>Call</code> 的嵌套实现。</li>
<li>当解释器匹配到 <code>Call e1 e2</code> 时,根据当前环境递归调用解释器计算出 <code>e2</code> 最终的表达式,假设 <code>e1</code> 匹配了 <code>Closure (Lambda (Param x) e) env&#39;)</code>,则将计算出 <code>e2</code> 的结果绑定到变量 <code>x</code>,并计算函数的值。</li>
</ul>
<h2 id="解释器"><a href="#解释器" class="headerlink" title="解释器"></a>解释器</h2><ul>
<li>解释器的主体代码如下,完整代码在 <a href="https://github.com/Forec/learn/blob/master/2017.1/haskell-interpreter/h2lang.hs" target="_blank" rel="external">h2lang.hs</a>:</li>
</ul>
<figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="title">interp</span> :: <span class="type">Exp</span> -&gt; <span class="type">Env</span> -&gt; <span class="type">Exp</span></span><br><span class="line"><span class="title">interp</span> (<span class="type">Param</span> x) env = fromMaybe (<span class="type">Error</span> (<span class="string">"undefined variable"</span> ++ x)) (lookup x env)</span><br><span class="line"><span class="title">interp</span> (<span class="type">Value</span> x) _ = <span class="type">Value</span> x</span><br><span class="line"><span class="title">interp</span> (<span class="type">Boolean</span> x) _ = <span class="type">Boolean</span> x</span><br><span class="line"><span class="title">interp</span> (<span class="type">String'</span> x) _ = <span class="type">String'</span> x</span><br><span class="line"><span class="title">interp</span> s@(<span class="type">Lambda</span> _ _) env = <span class="type">Closure</span> s env</span><br><span class="line"><span class="title">interp</span> (<span class="type">Let</span> (<span class="type">Param</span> x) e1 e2) env = interp e2 (extEnv x (interp e1 env) env)</span><br><span class="line"><span class="title">interp</span> (<span class="type">Op</span> op e1 e2) env = <span class="keyword">let</span> v1 = interp e1 env</span><br><span class="line"> v2 = interp e2 env</span><br><span class="line"> <span class="keyword">in</span> calc op v1 v2</span><br><span class="line"><span class="title">interp</span> (<span class="type">If</span> cond e1 e2) env = <span class="keyword">let</span> c = interp cond env</span><br><span class="line"> <span class="keyword">in</span> <span class="keyword">case</span> c <span class="keyword">of</span></span><br><span class="line"> <span class="type">Error</span> _ -&gt; <span class="type">Error</span> <span class="string">"syntax error"</span></span><br><span class="line"> <span class="type">Boolean</span> <span class="type">False</span> -&gt; interp e2 env</span><br><span class="line"> _ -&gt; interp e1 env</span><br><span class="line"><span class="title">interp</span> (<span class="type">Call</span> e1 e2) env = <span class="keyword">case</span> v2 <span class="keyword">of</span> </span><br><span class="line"> <span class="type">Value</span> _ -&gt; callExp</span><br><span class="line"> <span class="type">Boolean</span> _ -&gt; callExp</span><br><span class="line"> <span class="type">String'</span> _ -&gt; callExp</span><br><span class="line"> _ -&gt; <span class="type">Error</span> <span class="string">"syntax error"</span></span><br><span class="line"> <span class="keyword">where</span> </span><br><span class="line"> v2 = interp e2 env</span><br><span class="line"> col = interp e1 env</span><br><span class="line"> callExp = <span class="keyword">case</span> col <span class="keyword">of</span></span><br><span class="line"> (<span class="type">Closure</span> (<span class="type">Lambda</span> (<span class="type">Param</span> x) e) env') -&gt; interp e (extEnv x v2 env')</span><br><span class="line"> _ -&gt; <span class="type">Error</span> <span class="string">"syntax error"</span></span><br></pre></td></tr></table></figure>
<ul>
<li>效果:</li>
</ul>
<figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="title">h2</span> (<span class="type">Let</span> (<span class="type">Param</span> <span class="string">"x"</span>) (<span class="type">Value</span> <span class="number">2</span>) </span><br><span class="line"> (<span class="type">Let</span> (<span class="type">Param</span> <span class="string">"f"</span>) (<span class="type">Lambda</span> (<span class="type">Param</span> <span class="string">"y"</span>) (<span class="type">Op</span> <span class="string">"*"</span> (<span class="type">Param</span> <span class="string">"x"</span>) (<span class="type">Param</span> <span class="string">"y"</span>)))</span><br><span class="line"> (<span class="type">Let</span> (<span class="type">Param</span> <span class="string">"x"</span>) (<span class="type">Value</span> <span class="number">4</span>)</span><br><span class="line"> (<span class="type">Call</span> (<span class="type">Param</span> <span class="string">"f"</span>) (<span class="type">Value</span> <span class="number">3</span>)))))</span><br><span class="line"><span class="comment">-- Value 6.0</span></span><br><span class="line"><span class="title">h2</span> (<span class="type">Let</span> (<span class="type">Param</span> <span class="string">"x"</span>) (<span class="type">Value</span> <span class="number">3.9</span>) (<span class="type">Op</span> <span class="string">"/"</span> (<span class="type">Param</span> <span class="string">"x"</span>) (<span class="type">Value</span> <span class="number">4.32</span>)))</span><br><span class="line"><span class="comment">-- Value 0.9027778</span></span><br><span class="line"><span class="title">h2</span> (<span class="type">Let</span> (<span class="type">Param</span> <span class="string">"x"</span>) (<span class="type">Value</span> <span class="number">8.75</span>) (<span class="type">Op</span> <span class="string">"&gt;="</span> (<span class="type">Param</span> <span class="string">"x"</span>) (<span class="type">Value</span> <span class="number">7</span>)))</span><br><span class="line"><span class="comment">-- Boolean True</span></span><br><span class="line"><span class="title">h2</span> (<span class="type">Op</span> <span class="string">"=="</span> (<span class="type">Boolean</span> <span class="type">True</span>) (<span class="type">Boolean</span> <span class="type">False</span>))</span><br><span class="line"><span class="comment">-- Boolean False</span></span><br><span class="line"><span class="title">h2</span> (<span class="type">Op</span> <span class="string">"++"</span> (<span class="type">String'</span> <span class="string">"test"</span>) (<span class="type">String'</span> <span class="string">" case"</span>))</span><br><span class="line"><span class="comment">-- String' "test case"</span></span><br><span class="line"><span class="title">h2</span> (<span class="type">If</span> (<span class="type">Op</span> <span class="string">"&gt;="</span> (<span class="type">Value</span> <span class="number">2.3</span>) (<span class="type">Value</span> (-<span class="number">2.754</span>))) (<span class="type">String'</span> <span class="string">"Yes"</span>) (<span class="type">String'</span> <span class="string">"No"</span>))</span><br><span class="line"><span class="comment">-- String' "Yes"</span></span><br></pre></td></tr></table></figure>
<hr>
<p>参考资料: </p>
<ul>
<li><a href="http://www.yinwang.org/blog-cn/2012/08/01/interpreter" target="_blank" rel="external">王垠 - 怎样写一个解释器</a></li>
<li><a href="https://fallenwood.github.io/2017/02/04/writing-a-simple-lisp/" target="_blank" rel="external">Fallenwood - 怎样写一个解释器</a></li>
<li><a href="http://norvig.com/lispy.html" target="_blank" rel="external">(How to Write a (Lisp) Interpreter (in Python))</a></li>
<li><a href="https://www.zhihu.com/question/20115358" target="_blank" rel="external">知乎 - 如何写 Lisp 解释器</a></li>
</ul>
<p>原创作品,允许转载,转载时无需告知,但请务必以超链接形式标明文章<a href="http://blog.forec.cn/2017/02/14/talk-about-interpreter/" target="_blank" rel="external">原始出处</a>(<a href="http://blog.forec.cn/2017/02/14/talk-about-interpreter/" target="_blank" rel="external">http://blog.forec.cn/2017/02/14/talk-about-interpreter/</a>) 、作者信息(<a href="http://forec.cn/" target="_blank" rel="external">Forec</a>)和本声明。</p>
</content>
<summary type="html">
<blockquote>
<p>这篇文章主要基于王垠早年发过的文章<a href="http://www.yinwang.org/blog-cn/2012/08/01/interpreter">《怎样写一个解释器》</a>,我参考了 Racket 版本的 R2 解释器,并用 Haskell 实现 <code>H2Lang</code> 的简单解释器,较 R2 的功能做了一点改进。</p>
</blockquote>
</summary>
<category term="Code" scheme="http://forec.github.io/categories/Code/"/>
<category term="Haskell" scheme="http://forec.github.io/tags/Haskell/"/>
</entry>
<entry>
<title>HMM 关键词检索</title>
<link href="http://forec.github.io/2017/02/06/talk-about-hmm/"/>
<id>http://forec.github.io/2017/02/06/talk-about-hmm/</id>
<published>2017-02-06T08:22:30.000Z</published>
<updated>2018-09-26T06:08:29.413Z</updated>
<content type="html"><blockquote>
<p>记一下 HMM 的一些总是忘记的名词和计算过程。</p>
</blockquote>
<a id="more"></a>
<h2 id="HMM"><a href="#HMM" class="headerlink" title="HMM"></a>HMM</h2><ul>
<li>离散一阶马尔可夫链:系统在 t 时刻状态只和其在 t-1 时刻的状态相关。</li>
<li>马尔可夫模型:随机过程独立于时间 t,且状态转移概率 <code>Σ a_ij = 1 (j=1..N)</code>。</li>
<li>HMM 可观察到的事件是状态的随机函数,状态转移过程隐蔽(马尔可夫链),事件是一般随机过程。一个随机事件由观察值序列 <code>O = O_1, O_2, .., O_T</code> 表示,该事件背后隐藏着实际的状态序列 <code>Q = q_1, q_2, .., q_T</code>。HMM 的关键在于将两个序列联系起来,用可观察明字符组成的观察序列去表征由离散隐状态组成的状态序列(路径)。</li>
<li>HMM 要求满足马尔可夫性假设(状态构成一阶马尔可夫链)、不动性假设(状态和具体时间无关)以及输出独立性假设(输出,也就是观察到的值仅与背后的状态有关)。</li>
<li>HMM 由五元组 <code>λ = (N, M, A, B, π)</code> 描述:<ul>
<li><code>N = {q_1, q_2, .., q_N}</code>:有限状态集合</li>
<li><code>M = {v_1, v_2, .., v_M}</code>:有限观察值集合</li>
<li><code>A = {a_ij}</code>:状态转移概率矩阵</li>
<li><code>B = {b_jk}, b_jk = P(O_t = v_k | q_t = Sj)</code>:观察值概率分布矩阵</li>
<li><code>π = π_i</code>:初始状态概率分布</li>
</ul>
</li>
<li>给定 HMM 模型 <code>λ = (A, B, π)</code>,观察序列通过状态的不断转移和 <code>B</code> 矩阵产生,初始根据 <code>π</code> 选择 <code>q_1</code>,根据状态转移概率生成 <code>q_t</code>,并根据 <code>q_t = i</code> 和 <code>b_ik</code> 生成 <code>O_t = v_k</code>。</li>
<li>对于给定模型 <code>λ = (π, A, B)</code>,令 <code>O = O_1, O_2, .., O_T</code> 为观察值序列,三个基本问题及解决方案:<ul>
<li>评估问题(前向算法):对于给定模型,求任意观察值序列的概率 <code>P(O | λ)</code>。</li>
<li>解码问题(韦特比算法):对于给定模型和观察值序列,求可能性最大的状态序列 <code>maxQ{P(Q | O, λ)}</code>,也称 Q 为最优路径。即有效选择 “最优” 状态序列以尽量好地解释观察序列。</li>
<li>学习问题(向前向后算法):给定观察值序列,调整 <code>λ</code> 使该观察值序列出现的概率 <code>P(O | λ)</code> 最大。</li>
</ul>
</li>
</ul>
<h2 id="前向算法"><a href="#前向算法" class="headerlink" title="前向算法"></a>前向算法</h2><ul>
<li><code>α(t, i) = P(o_1, o_2, .., o_T, q_t = S_i | λ)</code> 指 “在时刻 t,得到 t 之前的所有明符号序列,且时刻 t 的状态是 S_i” 这一事件的概率。<ul>
<li><code>α(1, i) = P(o_1, q_1 = S_i | λ) = π(i)b(i, o_1)</code>;</li>
<li>递推:`α(t+1, j) = [Σ α(t, i) · a(i, j), i=1..N] × b(j, o_t+1);</li>
<li><code>α(T, i) = P(o_1, .., o_T, q_T = S_i | λ)</code>;</li>
<li><code>P(O | λ) = Σ α(T, i), (i = 1..N)</code></li>
</ul>
</li>
</ul>
<h2 id="Viterbi-算法"><a href="#Viterbi-算法" class="headerlink" title="Viterbi 算法"></a>Viterbi 算法</h2><ul>
<li>算法和卷积码的韦特比解码同名,因为本质就一样。</li>
<li><code>δ(t, i)</code> 为在 1..t 时刻按照状态序列 <code>q_1, .., q_t</code> 且 <code>q_t = S_i</code> 能够产生出 <code>o_1, o_2, .., o_t</code> 的最大概率,即 <code>δ(t, i) = max{ P(q_1, .., q_t-1, q_t = Si, o_1, .., o_t | λ) }</code>。序列 <code>o</code> 和系统 <code>λ</code> 都是确定的,<code>max</code> 根据序列 <code>q</code> 的变动选取最优解。</li>
<li>记忆变量:<code>φ(t, i)</code> 记录概率最大路径上当前状态的前一个状态。</li>
<li>初始化:<code>δ(1, i) = π(i)b(i, O_1)</code>,<code>φ(1, i) = 0</code>;</li>
<li>递推 :<code>δ(t, j) = max{δ(t-1, j) × a_ji} × b(i, O_t), 2 ≤ t ≤ T, 1 ≤ i ≤ N</code>;</li>
<li>终止:<code>p* = max{δ(T, i)}</code>;</li>
<li>路径回溯:<code>q* = φ(t+1, q*_t+1), t = T-1, T-2, .., 1</code>。</li>
</ul>
<h2 id="向前向后算法"><a href="#向前向后算法" class="headerlink" title="向前向后算法"></a>向前向后算法</h2><ul>
<li>最大似然估计无法解决学习问题,因为 HMM 中的状态序列为隐变量,无法被观察到。EM 算法由交替的 “期望” 过程(E)和 “极大似然估计” 过程(M)组成,E 过程从条件期望中构造完全数据的似然函数值,M 过程利用参数的统计量重新估计概率模型的参数,使训练数据对数似然最大。</li>
<li>初始化:满足概率条件的情况下随机给 <code>π_i</code>、<code>a_ij</code> 和 <code>b_jk</code> 赋值,得模型 <code>λ_0</code>,设 <code>i = 0</code>;</li>
<li>E 过程:由 <code>λ_i</code> 根据下面公式计算期望值 <code>ε(t, i, j)</code>(给定模型和观察序列,在时间 t 位于状态 <code>S_i</code>,时间 t+1 位于状态 <code>S_j</code> 的概率) 和 <code>γ(t, i)</code>(给定模型和观察序列,在时间 t 位于状态 i 的概率),E 过程的期望是根据上一个 M 过程重估后的模型计算的;</li>
</ul>
<figure class="highlight gcode"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">ε<span class="comment">(t, i, j)</span> = P<span class="comment">(q_t = S_i, q_t+1 = S_j | O, λ)</span></span><br><span class="line"> = P<span class="comment">(q_t = S_i, q_t+1 = S_j, O | λ)</span> / P<span class="comment">(O | λ)</span></span><br><span class="line"> = α<span class="comment">(t, i)</span>· a<span class="comment">(i, j)</span> · b<span class="comment">(j, O_t+1)</span> · β<span class="comment">(t+1, j)</span> / P<span class="comment">(O | λ)</span></span><br><span class="line"> = α<span class="comment">(t, i)</span>· a<span class="comment">(i, j)</span> · b<span class="comment">(j, O_t+1)</span> · β<span class="comment">(t+1, j)</span> / &#123;Σi Σj α<span class="comment">(t, i)</span> · a<span class="comment">(i, j)</span> · b<span class="comment">(j, O_t+1)</span> · β<span class="comment">(t+1, j)</span> &#125;</span><br><span class="line">γ<span class="comment">(t, i)</span> = Σj ε<span class="comment">(t, i, j)</span></span><br></pre></td></tr></table></figure>
<ul>
<li>M 过程:根据 E 过程得出的期望值,根据下面公式重新估计 <code>πi</code>,<code>a_ij</code> 和 <code>b_jk</code>,得到模型 <code>λ_i+1</code>。</li>
</ul>
<figure class="highlight gcode"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">π<span class="comment">(i)</span> = P<span class="comment">(q_1 = S_i)</span> = γ<span class="comment">(1, i)</span></span><br><span class="line">a<span class="comment">(i, j)</span> = &#123;Σt ε<span class="comment">(t, i, j)</span>&#125; / &#123;Σt γ<span class="comment">(t, i)</span>&#125;, t = <span class="number">1.</span>.T<span class="number">-1</span></span><br><span class="line">b<span class="comment">(j, k)</span> = &#123;Σt γ<span class="comment">(t, j)</span> × δ<span class="comment">(O_t, v_k)</span>&#125; / &#123;Σt γ<span class="comment">(t, j)</span>&#125;, t = <span class="number">1.</span>.T</span><br></pre></td></tr></table></figure>
<ul>
<li>重复 E、M 过程直到模型收敛。</li>
</ul>
<h2 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h2><ul>
<li>前向算法、Viterbi 和 Baum-Welch 算法的概率值连续乘法运算容易下溢。</li>
<li>前向算法中每步运算都可以乘一个比例因子 <code>c(t)</code>,如下:</li>
</ul>
<figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">α(t+<span class="number">1</span>, j) = [Σ α(t, i) · <span class="function"><span class="title">a</span><span class="params">(i, j)</span></span>, i=<span class="number">1</span>..N] × <span class="function"><span class="title">b</span><span class="params">(j, o_t+<span class="number">1</span>)</span></span></span><br><span class="line">α<span class="string">'(t+1, j) = c(t) × [Σ α(t, i)'</span> · <span class="function"><span class="title">a</span><span class="params">(i, j)</span></span>, i=<span class="number">1</span>..N] × <span class="function"><span class="title">b</span><span class="params">(j, o_t+<span class="number">1</span>)</span></span></span><br><span class="line"><span class="function"><span class="title">c</span><span class="params">(t)</span></span> = <span class="number">1</span> / Σ α(t, i) , <span class="tag">i</span> = <span class="number">1</span>..N</span><br></pre></td></tr></table></figure>
<ul>
<li>Viterbi 算法可以将概率值取对数(乘积化为对数求和)。</li>
</ul>
<h2 id="相关链接"><a href="#相关链接" class="headerlink" title="相关链接"></a>相关链接</h2><ul>
<li><a href="https://www.zhihu.com/question/20962240" target="_blank" rel="external">逼乎 “如何用通俗易懂的例子解释 HMM”</a></li>
<li><a href="http://blog.csdn.net/ppn029012/article/details/8923501" target="_blank" rel="external">我邮学长 Nong Bloody 的博客</a></li>
<li><a href="http://www.hankcs.com/ml/hidden-markov-model.html" target="_blank" rel="external">hankcs 的隐马模型笔记</a></li>
</ul>
<hr>
<p>原创作品,允许转载,转载时无需告知,但请务必以超链接形式标明文章<a href="http://blog.forec.cn/2017/02/06/talk-about-hmm/" target="_blank" rel="external">原始出处</a>(<a href="http://blog.forec.cn/2017/02/06/talk-about-hmm/" target="_blank" rel="external">http://blog.forec.cn/2017/02/06/talk-about-hmm/</a>) 、作者信息(<a href="http://forec.cn/" target="_blank" rel="external">Forec</a>)和本声明。</p>
</content>
<summary type="html">
<blockquote>
<p>记一下 HMM 的一些总是忘记的名词和计算过程。</p>
</blockquote>
</summary>
<category term="计算机理论基础" scheme="http://forec.github.io/categories/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80/"/>
<category term="Algorithms" scheme="http://forec.github.io/tags/Algorithms/"/>
<category term="机器学习" scheme="http://forec.github.io/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
</entry>
</feed>