-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathintro.tex
More file actions
2325 lines (1929 loc) · 121 KB
/
Copy pathintro.tex
File metadata and controls
2325 lines (1929 loc) · 121 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
% Created 2015-08-27 木 18:07
\documentclass[11pt,a4paper]{jarticle}
\usepackage[utf8]{inputenc}
\usepackage[dvipdfmx]{graphicx}
\usepackage{ascmac} % 枠付き環境のため
\usepackage{amsmath}
\usepackage[dvipdfmx]{hyperref}
\usepackage{xcolor}
\usepackage{listings,jlisting} % jlistingはtex-live-fullでも入ってない。手動
\usepackage{fancyvrb}
\usepackage{geometry}
\geometry{left=25mm,right=25mm,top=20mm,bottom=20mm}
\usepackage{atbegshi}
\AtBeginShipoutFirst{\special{pdf:tounicode EUC-UCS2}}
% itemize環境の行間隔を縮める
\let\olditemize\itemize
\renewcommand{\itemize}{
\olditemize
\setlength{\itemindent}{0pt} %5. 最初のインデント
\setlength{\itemsep}{1pt}
\setlength{\parskip}{0pt}
\setlength{\parsep}{0pt}
}
% \usepackage{courier}
%\usepackage{DejaVuSansMono}
\usepackage[T1]{fontenc}
% \usepackage{lmodern}
% \usepackage{luximono}
\usepackage[scaled=0.85]{beramono}
% \date{\today}
\lstdefinelanguage{SML} {
keywords = {
if,then,else,val,rec,let,in,end,fun,fn,exception,handle,raise,
case,of,datatype,type,op,where,
ref,struct,structure,signature,sig,functor,before,local,as,
SOME,NONE
},
morecomment=[n]{(*}{*)},%
ndkeywords={true,false,int,bool,string,char},
ndkeywordstyle=
}
\lstset{
language={SML},% プログラミング言語
basicstyle={\ttfamily\small},% ソースコードのテキストのスタイル
keywordstyle={\bfseries},% 予約語等のキーワードのスタイル
commentstyle={\itshape},% コメントのスタイル
stringstyle={},% 文字列のスタイル
frame=trlb,% ソースコードの枠線の設定 (none だと非表示)
numbers=none,% 行番号の表示 (left だと左に表示)
numberstyle={},% 行番号のスタイル
xleftmargin=5pt,% 左余白
xrightmargin=5pt,% 右余白
keepspaces=true,% 空白を表示する
mathescape=true,% $ で囲った部分を数式として表示する ($ がソースコード中で使えなくなるので注意)
backgroundcolor=\color[gray]{0.7},
% 手動強調表示の設定
moredelim=[is][\fontfamily{pcr}\selectfont\itshape]{@/}{/@},
moredelim=[is][\color{red}]{@r\{}{\}@},
moredelim=[is][\color{blue}]{@b\{}{\}@},
moredelim=[is][\color{DarkGreen}]{@g\{}{\}@},
moredelim=[is][\fontfamily{pcr}\itshape]{@@}{@},
}
\title{SMLコア言語入門}
\author{インターネットの闇 (@no\_maddo)}
\begin{document}
\maketitle
% \tableofcontents
\setlength{\parskip}{0.1cm}
\newcommand{\prog}[1]{\colorbox[gray]{0.7}{\lstinline{#1}}}
\newcommand{\sml}[1]{\lstinline{#1}}
% \newcommand{\beforesection}[1]{
% \vspace{-5mm}
% }
% \newcommand{\beforesubsection}[1]{
% \vspace{-5mm}
% }
\section{Standard MLの世界へようこそ!}
Standard ML(以下SML)の世界へようこそ!\\
SMLは非純粋(副作用、破壊的代入が出来る)な関数型言語の一種です。
Strictな(Cなどと同じ評価順序)評価戦略・静的な(コンパイル時の)型付け・高階関数(関数を操作する関数)など
静的型付けを行う関数型言語として標準的な機能を持つ、MLと呼ばれる歴史ある言語ファミリに属しています。\\
特徴としてはSchemeのように言語の定義が存在します\footnote{\url{https://github.com/SMLFamily/The-Definition-of-Standard-ML-Revised}}
言語の定義が存在すると、言語を処理系を叩いて理解するのではなく、より定式化されたものを対象に理解することが出来ます。
個人的な私見ですが、MLは関数型言語を学ぶための言語として最適です。
わかりやすい言語設計で、特に型システムは関数型言語でも標準的で分かりやすいです。
日本では関数型言語として名前が上がるのはまずHaskell, F\#などかもしれませんが、
これらの型システムもベースはMLで主に用いられるHindley-Milner型システムをベースにしています。
特に言語処理系に興味がある人にとっては、SMLは様々な実験的な処理系が作られてきたのでSMLを
学ぶ意義が大いにあります。
CPS変換によってすべての関数呼び出しが末尾再帰呼出し化される''Standar ML of New Jersey''や
Whole optimized compilerとして有名な、分割コンパイルを諦めて強力な最適化がかかる''MLton'',
定理照明器Isabelle/HOLの開発に用いられているPoly/MLなど。
Type Passingを全面的に利用しようとして夢破れたTILなど、実験的な論文レベルの処理系もたくさんあります。
ぜひこの同人誌を楽しく読むために、SMLのコア言語を学んで下さい。
\subsection{本チュートリアルの読み方}
このチュートリアルは足早に他のプログラミング言語を何か1つ以上知っている人向けに
SMLのコア言語の書き方をSML/NJを題材に説明します。
関数型言語の知識はあると理解が簡単かと思いますが必要としません。その都度説明します。\\
途中のコラムでは、プログラミング言語マニア向けに言語デザイン・SMLの考え方・他の言語との比較に触れます。
コラムはそこまでの知識では理解できない用語や概念が含まれますので、
分からなければ後で読んでもらうのが良いと思います。
\subsection{SML/NJ}
\subsubsection{SML/NJのインストール}
基本的にはインストールの指示に従えば特に困難なくインストールすることが出来ます。\\
\url{http://www.smlnj.org/dist/working/index.html} で最新版を確認してください。
\begin{itemize}
\item Windowsの場合\\
Windows向けにインストーラが存在します。
上で示したURLから最新版をクリックし、.msiファイルをダウンロードし、指示に従って下さい。\\
Windowsではコマンドプロンプト上でSML/NJの対話環境が動くことになります。
コマンドプロンプトではコピーアンドペーストや基本操作がしづらいと思うので
Emacsなど他のプログラム上でコマンドプロンプトを実行するようなプログラムが必要かもしれません。
\item Linux系OSの場合\\
多くのメジャーディストリビューションではパッケージ管理ソフト経由でインストールすることが出来ます。
執筆現在(2015年10月12日)で最新版は110.78です、パッケージ管理ソフト経由で
インストールできるコンパイラは古いかもしれませんが基本的には問題ないはずです。
ml-buildなどツールがうまく動かないようならばソースコードからコンパイルして下さい。
\end{itemize}
\subsubsection{対話環境について}
WIndowsを例に見方を説明します。
\begin{figure}[htbp]
\centering
\includegraphics[scale=0.5]{./interactive.eps}
\caption{対話環境}
\label{fig:hello}
\end{figure}
コマンドプロンプト上でsmlと入力し、SML/NJを起動しましょう。
その後上の画像のように、\prog{print "Hello, World !\n";}と入力してみて下さい。
セミコロンも必ず必要です。画面に正しくプリントされれば成功です!
対話環境の見方を説明します。
\begin{itemize}
\item 各行の最初のハイフン(\prog{-})は対話環境が挿入するプロンプトです。
プログラムの一部ではないので気にしてくて大丈夫です。
\item プログラムが複数行に渡る時は、対話環境がイコール(\prog{=})記号を自動的に挿入します。
これもプログラムの一部ではないので気にしてくて大丈夫です。
\item 行の最後に必要なセミコロン(\prog{;})は対話環境に入力が終わったことを
伝えるために必要な文字です。これがなければずっと対話環境は入力待ちをします。
\item 画面で\prog{Hello, World !}の次の行にかかれているように、
プログラムを実行(これ以降評価と呼びます)すると計算結果とそれの型がプリントされます。
これによってユーザはインタラクティブに関数を定義し、計算結果を手早く
確認しながらプログラミングを続けることが出来ます。
\item 対話環境にいちいち打ち込むのは面倒かもしれません。
ファイルのロードには\prog{use "./test.sml";}のように、\prog{use}関数を使って
ファイルを読み込みます。この場合はファイルはカレントディレクトリにあるため特に指定していませんが、
ファイルのパスを入力して下さい。そうすると、ファイルに書かれた定義の読み込みやプログラムの評価が行われます。
\end{itemize}
\section{基礎文法}
\subsection{式を組み合わせる}
SMLでは(関数宣言・型宣言などの宣言は除いて)全ての要素は{\bfseries 値}({\itshape value})を返します。
値を返すものを{\bfseries 式}({\itshape expression})と呼びましょう。
それ以外のものは{\bfseries 文}または宣言と呼びます。
SMLに限らず一般に関数型言語では式を組み合わせてプログラミングしていきます。
以後、式と文は明確に区別されているので気をつけて下さい。
値とは直感的には整数・浮動小数点数・文字列・関数とそれを組み合わせて作った
データ構造などこれ以上簡単に出来ないプログラムのことです。
例えば\prog{print "Hello, world!"}は関数と文字列の適用なので値ではありません。
これの評価結果(計算結果)である\prog{()}(ユニット)は値です。
同様にif式もCの三項演算子のように値を返します。
if式のelse節は省略できません。
if式の構文は\prog{if @@e1@ then @@e2@ else @@e3@}(ただし式1の型はbool, 式2の型=式3の型)です。
型付けに関しては\ref{sec:static-typing}章で詳しく説明します。
SMLでは、プログラムの実行というのは{\bfseries 式を評価して値にすること}です。
後述の静的な型検査によって{\bfseries 評価途中にある種のエラーにならない}ことを保証できます。
\begin{lstlisting}[caption=if文は式,label=code:if-statement]
- if true then 1 else 2;
@/val it = 1 : int/@
(* if式は式なので自由に組み合わせられる *)
- (if false then 1 + 2 else 3 + 4) * (if true then 4 else 6);
@/val it = 28 : int/@
\end{lstlisting}
\subsection{静的な型検査}
\label{sec:static-typing}
SMLではプログラムは{\bfseries 静的に}(コンパイル時に)型付けされます。
動的型付き(実行時に型の整合性を確認する)言語と異なり、型が合わないものはこの時エラーになります。
静的な型付けを行う言語の例として、CやJavaを思い浮かべると静的型付けというのは
変数を宣言するたびに冗長な型を書かなくてはいけない、いちいち変更するのに面倒
というイメージがあるかもしれません。\\
SMLには{\bfseries 型推論}({\itshape type inference})と呼ばれる、型をプログラマが書かなくても
処理系が補ってくれる、という機能があります。
そのためソースコード\ref{code:if-statement}でも一切型を書かなくても
返り値は\sml{int}だな、と判断してくれました。
さて、型エラーになる例を見てみましょう。ソースコード\ref{code:type-error1}を見て下さい。\\
最初の例では\sml{int}型の値と
\sml{real}型(Cの\sml{double}のこと)の値を加算しようとして型エラーになりました。
C言語などでは暗黙のキャストにより型検査に成功しますが、
SMLではこのような仕組みは存在しません。キャストする関数により明示的に変換する必要があります。
これは書くのには面倒ですが、プログラムを安全にします。
\footnote{暗黙のキャストはプログラムを危険にします。例えば浮動小数点数演算の精度・ポインタのキャストなどがプログラマの意識外で行われてしまいます。}\\
その次にif式に関する型エラーです。
SMLのif式は、then節・else節で同じ型の値を返さなければなりません。
下の例でthen節は\sml{int}型の値を返していますが、else節では\sml{bool}型の値を返しています。
そのため型エラーが発生しました。
\begin{lstlisting}[label=code:type-error1,caption=型エラー1]
- 1 + 2.0;
@/stdIn:5.1-5.8 Error: operator and operand don't agree [literal] /@
@/ operator domain: int * int /@
@/ operand: int * real /@
@/ in expression: /@
@/ 1 + 2.0 /@
- if 1 > 2 then 1 else false;
@/stdIn:21.1-21.27 Error: types of if branches do not agree [literal]/@
@/ then branch: int /@
@/ else branch: bool /@
@/ in expression: /@
@/ if 1 > 2 then 1 else false /@
\end{lstlisting}
次に関数が登場する例を見てみましょう。ソースコード\ref{code:type-error2}を見て下さい。
下の例では、まず関数\sml{print}の型を確認し、それを使おうとしています。
関数の型は\prog{T1 -> T2}などと、アロー(\sml{->})を用いて表されます。
今はこの読み方は「\sml{T1}型の値を受け取ったら\sml{T2}型の値を返す」という理解でOKです。
関数適用の書式ですが \prog{@@f@ @@e1@ @@e2@ ...} というような記法で書きます。
C言語で関数適用を書くのとは違いカッコが必要ありません。
\prog{print("12")}と書いてもエラーではありませんがMLらしく
省略できるカッコは省略していきましょう。
さて、int型の値である12をプリントしようとしていますが、
print関数の型は\prog{string -> unit}で、
\sml{int}を受け取るようにはできておらず、型エラーになります。
\begin{lstlisting}[caption=型エラー2,label=code:type-error2]
(* print関数の型を確認しよう *)
- print;
@/val it = fn : string -> unit/@
(* print型はstring型の値を受け取ってunit型の値を返すので、
intを受け取ったら型エラー *)
- print 12;
@/stdIn:4.1-4.9 Error: operator and operand don't agree [literal]/@
@/ operator domain: string /@
@/ operand: int /@
@/ in expression: /@
@/ print 12 /@
(* int型の値をstring型の値にキャストする関数をはさもう
^ は文字列の結合のための演算子 *)
- print (Int.toString 12 ^ "\n");
@/12 /@
@/val it = () : unit /@
\end{lstlisting}
\subsection{値の束縛・パターンマッチ}
\subsubsection{val文}
さて、変数・関数を定義する方法を学びましょう。まずは変数から。
\begin{lstlisting}[caption={val文}, label={code:first-definition}]
- val x = 12;
@/val x = 12 : int/@
- val y =
if x > 100
then Bool.toString true else Real.toString 3.14;
@/val y = "3.14" : string/@
(* 新しい要素:タプル *)
- val t1 = (1, 3.14);;
@/val t1 = (1,3.14) : int * real/@
- val t2 = (1 + 2, t1);;
@/val t2 = (3,(1,3.14)) : int * (int * real)/@
\end{lstlisting}
val文は\prog{val @@x@ = @@e@}という形で書かれます。
注意すべきなのは、デフォルトで変数は{\bfseries 変更不可能}({\itshape immutable})であることです。
そのため一度定義した値が変わらないことをコンパイラが保証してくれるので
プログラムを安全に開発することができます。
変数が変更可能な言語でその変数を複数箇所で書き換えるプログラムと、
変数が変更不可能な言語で書かれたプログラムをデバッグすることを想像して下さい。
例えばある変数の不変条件をチェックするアサーションが失敗した時、
その変数がどうしてその値になったのか調べる時に、1箇所定義部分だけを確認するのと
変更している部分全てを見てなぜその値になったのか考えるのはどっちが楽でしょうか。
文献では''Effective Java''などでも変更不可能なオブジェクトは
プログラムをシンプルにする・デフォルトでスレッドセーフである、
など沢山の利点があることが主張されています。
興味があればEffective JavaやYegor Bugayenkoの``Objects Should Be Immutable''
\footnote{\url{http://www.yegor256.com/2014/06/09/objects-should-be-immutable.html}}
を読んでみてください。
さて、ソースコード\ref{code:vals}の最後の例で新しい型、{\bfseries タプル}({\itshape tuple})が現れました。
タプルというのは組み型と呼ばれ、複数の型の異なる要素をまとめた構造を作ることができます。
例えば\prog{int}と\prog{real}型の値を1つにしているときには、
\prog{int * real}のようにこの型は表現されます。タプルの要素は任意の型の要素が許されるので、
\prog{int * (int * real)}などとタプルがネストすることも考えられます。
ちなみに\prog{int * int * real}と\prog{int * (int * real)}は違う型なので注意して下さい。
前者は3つの要素が入ったタプル型ですが、後者は2つの要素が入ったタプル型で2番目の要素がタプル型
と読むことが出来ます。
さて、タプルを作る方法はただ\prog{(1, "2")}などと書くだけです。
作り方がわかったところで、この構造を破壊する方法を学びましょう。
その前に、スコープの話をしましょう。ソースコード\ref{code:vals}を見て下さい。
SMLでは{\bfseries レキシカルスコープ}({\itshape lexical scope})を採用しています。
そのため一度参照したものの参照先が変わることはありません。
\begin{lstlisting}[caption=valのスコープ,label=code:vals]
- val name = "nadesico";
@/val name = "nadesico" : string/@
(* 変数nameを使ってタプルt1を定義 *)
- val t1 = (name, 1996);
@/val t1 = ("nadesico",1996) : string * int/@
(* 変数nameをもう一度定義
この先nameと書いた時に指されるものはこっちの定義になる *)
- val name = "bebop";
@/val name = "bebop" : string/@
(* t2を定義しようとする時、name="bebop"となっている *)
- val t2 = (name, 1998);
@/val t2 = ("bebop",1998) : string * int/@
(* 変数nameを再定義しても、t1でnameが指しているものが変わるわけではない *)
- t1;
@/val it = ("nadesico",1996) : string * int/@
\end{lstlisting}
\begin{itembox}[l]{ヒント:関数の結合の強さ}
関数\prog{f}があったときに、\prog{f x + y}というプログラムを書いたならば
どういう風にパースされるでしょうか?これはとても重要です。
これに正しく括弧を付けるとしたら\prog{((f x) + y)}です。
決して\prog{(f (x + y))}ではない事に気をつけて下さい。
関数型言語では関数の結合の強さが最高だと覚えておいてください。
他の演算子の結合の強さは概ね他の言語と同じようなものです。
困っても自由にinfix宣言により変更できます。
\end{itembox}
\subsubsection{val文を用いたパターンマッチ}
タプルから値を取り出すには、主に{\bfseries パターンマッチ}({\itshape pattern match})を用います。
具体例を見ていきましょう。ソースコード\ref{code:pattern-match-vals}に例を示します。
\begin{lstlisting}[caption=val文でパターンマッチ,label=code:pattern-match-vals]
- val t1 = ("nj", "poly", "alice");
@/val t1 = ("nj","poly","alice") : string * string * string/@
- val (a, b, c) = t1;
@/val a = "nj" : string /@
@/val b = "poly" : string /@
@/val c = "alice" : string/@
\end{lstlisting}
さて、上の例ではまずタプル\prog{t1}を定義し、それをパターンマッチによって中身の要素を取り出しました。
val文には\prog{val @@x@ = @@e@}という形以外に
\prog{val @@pat@ = @@e@}という形を書くことが出来きこちらのほうがより一般的です。
例えば\prog{val (a,b,c) = t1}と書いた時、\prog{t1}の第一要素が\prog{a}に束縛・
第二要素が\prog{b}に束縛・第三要素が\prog{c}に束縛されます。
タプルの中身の要素を取り出したい、
でもタプル自体にも名前をつけてアクセスできるようにしたい場合にはasパターンを使います。
ソースコード\ref{code:as-pattern}を見て下さい。
\begin{lstlisting}[caption=asパターン,label=code:as-pattern]
- val t1 as (a, b) = (1 + 2, 3 * 4);
@/val t1 = (3,12) : int * int/@
@/val a = 3 : int/@
@/val b = 12 : int/@
\end{lstlisting}
パターンマッチは、val式の左辺(パターンの部分)と右辺(式の部分)の形が一致していなければなりません。
ソースコード\ref{code:bad-pat}を見てみましょう。
\begin{lstlisting}[caption=型が合わないパターンマッチ,label=code:bad-pat]
- val (a, b) = (1, 2, 3);
@/stdIn:20.5-20.23 Error: /@
@/pattern and expression in val dec don't agree [tycon mismatch] /@
@/ pattern: 'Z * 'Y /@
@/ expression: int * int * int/@
@/ in declaration: /@
@/ (a,b) = (1,2,3) /@
\end{lstlisting}
上の例ではval文の左辺のパターンは2つの要素を受け取る形をしていますが、
右辺の式の部分は3つの要素を持つタプルです。
エラーメッセージでもパターンと式がミスマッチだと言っていますよね。
また、データの一部を名前を付ける必要がないときには、
\prog{val (_, x) = (1, 2)}のように
ワイルドカード(\prog{_})を使用できます。
% \begin{itembox}[l]{コラム:識別子に使える文字}
% あまり意識する必要はありませんが、SMLの識別子には2つの区別があります。
% \begin{itemize}
% \item アルファベット英数(\textit{alphanumeric}) \\
% 文字[a-z,A-Z]かプライム(')で始まり、
% 2文字目以降が文字[a-z,A-Z]・数字[0-9]・プライム(')・アンダーバー(\_)であるもの
% \item シンボリック(\textit{symbolic})\\
% 以下のシンボルのみで作られた列(\verb(# |(など基本機能と被るものを除く)\\
% \Verb(! % & $ # + - / : < = > ? @ \ ~ ` ^ | *(
% \end{itemize}
% シンボリックルールで作る識別子を定義するには\prog{op}が必要です。\\
% この識別子は変数・関数・モジュール名・シグニチャ名で共通です。
% \prog{structure ## = struct ... end}などと出来るので、
% 識別子を見ても例えばそれがモジュール名なのかコンストラクタなのか
% 区別する事は見た目だけでは出来ません。
% \end{itembox}
% \begin{itembox}[l]{コラム:どんなパターンが存在するのか}
% パターンは以下のBNFで表されます。慣れが必要ですがぜひ読んでみてください。
% \prog{<>}でくくられたものはオプション(省略可能)です。
% \begin{Verbatim}
% pat := atpat % 単純な場合(アトミックパターン)
% | <op> longvid atpat % ヴァリアントパターン
% | pat vid pat % ヴァリアントの中置形式
% | pat : ty % 型注釈付き
% | <op> pat <:ty> as pat % asパターン
% atpat := ... % ワイルドカード
% | sconst % 定数
% | <op> longvid % 変数
% | {<patrow>} % レコード
% | () % unit定数
% | (pat,...,pat) % タプルパターン
% | [pat,...,pat] % リストパターン
% | (pat) % カッコ
% patrow := ... % ワイルドカード
% | label = pat <, patrow> % フィールドパターン
% | vid<:ty> <as pat> <,patrow> % レイヤード(layered)
% longvid % ヴァリアント名
% vid % 変数名
% ty % 型
% \end{Verbatim}
% これを見て重箱の隅をつついてみましょう。まず定数はパターンです!
% \prog{val 1 = 2}みたいな定数パターンのパターンマッチで、正しいコードです。
% (ただし実行時エラーになります)。
% またヴァリアントを中置演算子にした形はパターンマッチすることが許されるので、
% \begin{lstlisting}[caption=中置形式のヴァリアントのパターンマッチ]
% datatype t = A of int * int
% infix A
% val x A y = ...
% \end{lstlisting}
% みたいなコードもかけます!
% 最後に、無駄に複雑なパターンを書いて終わりましょう。
% ほぼすべてのパターンのケースを用いて複雑なパターンマッチを書いてみました。
% \begin{lstlisting}[caption=複雑なパターン]
% - datatype t = A of t list * int | B;
% - infix A;
% - val a as ([x, y A _, B : t] A 2, {r1,...}) =
% ([B, [] A 1, B] A 2, {r1=1,r2="fuga",r3=[]});
% \end{lstlisting}
% \end{itembox}
\subsubsection{let式}
これまではトップレベルに定義を並べるだけでしたが、より複雑な定義をする時にはこれでは不便な時があります。
ネストした定義、定義の中にそれの補助のための定義を書けるようにしましょう。
これの必要性を理解するために、ライブラリを作ることを想像して下さい。
例えばグラフ操作ライブラリを作っている時、
ユーザに公開する関数はグラフ構造を壊さない関数のみにしたいですよね。
処理の途中でのみ使う、受け取ったグラフを不正なグラフにして返すような関数は
安全性のために利用者が使えないようにしたいです。
そのために、カプセル化などの仕組みを用いて
途中で用いる操作関数や途中状態の値はアクセス出来ないようにすることが多いですよね。
これをライブラリやクラス単位ではなく、
より細かい単位である式の単位でローカルな定義ができればより安全性が高まると思いませんか。
それだけではなく、公開する関数と並べて書くのではなく外に公開しない形で書くことにより
この関数・値はこの式の中でしか使わないんだな、
といったプログラマの意図をコードの中に表しやすくなります。
このような用途のためにlet式を導入しましょう。
let式の書き方は\prog{let @@dec1@ @@dec2@ ... in @@e@ end}です
(Lisp系言語のlet式と殆ど一緒です)。
\prog{end}は忘れやすいですが必須なので気をつけて下さい。
例を見てみましょう。ソースコード\ref{code:let-expression}を見て下さい。
\begin{lstlisting}[caption=let式,label=code:let-expression]
(* 半径2の円の面積を計算してみる *)
- val area =
let
val pi = 3.14
val r = 2.0
in
pi * r * r
end;
@/val area = 12.56 : real/@
(* let式は式なので自由に組み合わせられる *)
- (let val r = 2 in r * r end) + 1;
@/val it = 5 : int/@
\end{lstlisting}
上の例では、まず半径2の円の面積(\sml{area})を計算するために
円周率\prog{pi}と\prog{r}を定義して\sml{in}以降で使用しています。
let式はれっきとした式です。
なので他の\prog{1}や\prog{"Himawari" ^ "Sakurako"}といった式と同様に使うことが出来ます。
またlet式はスコープを作ります。
\prog{area}を定義するために定義した変数である\prog{pi}や\prog{r}には
\sml{let}式の外側からはアクセスすることが出来ません。
\begin{lstlisting}[caption=\sml{let}式のスコープ,label=code:let-scope]
(* let式の中ではrにアクセスできるが、外側からはアクセスできない *)
- (let val r = 2 in r * r end) + r;
@/stdIn:19.6 Error: unbound variable or constructor: r/@
- val name = "Akito";
@/val name = "Akito" : string/@
(* 変数nameをlet式内で定義して使っているが、外側ではname="Akito"として使える *)
- (let val name = "Yurika" in name ^ ", " end) ^ name;
@/val it = "Yurika, Akito" : string/@
\end{lstlisting}
\subsection{関数・リスト}
\subsubsection{関数を定義する}
いよいよ関数型言語の味噌である関数について扱っていきます!
関数は主に\prog{fun @@f@ @@pat1@ @@pat2@ ... = @@e@}
という\sml{fun}宣言により定義されます。
ソースコード\ref{code:simple-funs}を見てみましょう。
\begin{lstlisting}[caption={単純な関数の定義},label={code:simple-funs}]
- fun double a = a * 2;
@/val double = fn : int -> int/@
- val x = double 12;
@/val x = 24 : int/@
(* 関数の仮引数部分でもval文同様のパターンマッチが使える *)
- fun plus (x, y) = x + y;;
@/val plus = fn : int * int -> int/@
- plus (100, 200);
@/val it = 300 : int/@
\end{lstlisting}
関数適用は以前説明したとおり、\prog{@@f@ @@e1@ @@e2@ ...}という形で書かれます。
C言語のようにカッコは必要ありません。
関数定義に一切型を書いていないのにも関わらず、関数の型が推論されていることに注意して下さい。
関数\sml{plus}の中でプラス演算子を使っていますが、
これは\sml{int}型の要素を2つ取ることを
コンパイラ\footnote{正確にはこの場合対話環境ですが :)}は知っているため、
\prog{x}、\prog{y}の型は\sml{int}型であると推論されます。
引数の型も推論でき、\prog{x + y}の返り値もintであるので、
関数\sml{plus}の型は\prog{(int * int) -> int}となります。
\sml{fun}文の引数部分のパターンに対しても型推論が働きます。
関数\sml{plus}では、引数部分にタプルパターンを書いています。
この場合関数\sml{plus}は引数は1つで、それはタプルであると推論されます。
その後仮引数のタプルの要素である\sml{x}、\sml{y}の型は
\sml{int}型であることが使われ方からわかるので、
関数\sml{plus}は\prog{(int * int) -> int}型を持つことがわかります。
関数の定義の中にlet式を使うことが出来ます。
let式の書き方は\prog{let @@dec1@ @@dec2@ ... in @@e@ end}と書きましたが、
この宣言をfun文の中に書くことも出来ます。
これによって関数宣言の中でしか有効でない、定義に必要な補助関数を定義することが出来ます。
\begin{lstlisting}[caption=ネストした関数宣言,label=code:nested-fun]
- fun printPow x n =
let
fun pow x n =
if n = 0 then 1 else x * pow x (n - 1)
fun printWithBreak str =
print (str ^ "\n")
in
printWithBreak (Int.toString (pow x n))
end;
@/val printPow = fn : int -> int -> unit/@
- printPow 3 3;
@/27/@
@/val it = () : unit/@
\end{lstlisting}
\subsubsection{型注釈}
型推論によってSMLでは多くの場合型を書く必要がありません。
しかし推論結果が理解できない、なぜ型エラーになるのかわからない時などに型の注釈を書くことができます。
一般に\prog{13 : int}など、\prog{@@e@ : @@typ@}と書きます。
多くの場合\prog{val (x : int) = 13}などと注釈を書いたならば括弧で括る方が
わかりづらいパースエラーが起こらないため安全です。
\subsubsection{演算子の定義}
今まで演算子というものが存在するとしてきましたが、
演算子にパラメータを渡す事は、単に関数適用のシンタックスシュガーに過ぎません。
演算子の型を確認してみましょう。演算子には、\prog{op}をつけると通常の関数として扱えます。
\begin{lstlisting}[caption=演算子を評価する]
(* 型を確認する *)
- op +;
@/val it = fn : int * int -> int/@
(* 普通の関数みたいに使ってみよう *)
- op + (1, 2);
@/val it = 3 : int/@
\end{lstlisting}
演算子というのは記法は異なりますがただの関数だということが分かりました。
反対に関数も演算子にすることが出来ます。
例を見てみましょう。ソースコード\ref{code:infix}を見て下さい。
このコードでは、べき乗関数\sml{power}を定義し、それを中置記法で使えるようにしています。
\begin{lstlisting}[caption=演算子の定義,label=code:infix]
- fun power (x, n) =
if n = 0 then 1 else x * power (x, n - 1);
@/val power = fn : (int * int) -> int/@
(* powerを中置記法でかけるようにする宣言 *)
- infix 4 power;
@/nonfix power/@
(* 使ってみよう *)
- 2 power 10;
@/val it = 1024 : int/@
\end{lstlisting}
\sml{power}関数は普通の数学で扱うようなべき乗の定義通りです。
infix宣言をするとこれを中置記法で使えるようになります(infixの後の数字は演算子の結合の強さを指定。省略可)。
中置記法でかけるようにするためには、
関数は型が\prog{('a * 'b) -> ...}という形をしている必要があります。
そうでないと演算子を定義できても、その演算子にどんな入力を与えても型エラーになります。
さて、演算子にはやはり記号を使いたいですよね。記号で構成された関数も他の関数と同様の方法で定義できます。
ソースコード\ref{code:operator}では\prog{power}と書かずに
\prog{**}で住むように新たな演算子を定義しています。
\begin{lstlisting}[caption=演算子の定義,label=code:operator]
(* 記号から始まる関数はopをつけてから関数名を書く *)
- fun op ** (x, n) =
if n = 0 then 1 else x * power (x, n - 1);
@/val power = fn : (int * int) -> int/@
(* 普通の関数と同じように使えるよー *)
- ** (2, 10);
@/val it = 1024 : int/@
(* powerを中置記法でかけるようにする宣言 *)
- infix **;
@/infix **/@
- 2 ** 10;
@/val it = 1024 : int/@
\end{lstlisting}
\sml{infix}宣言により中置記法で書くこととなった関数は\sml{nonfix}により
普通の記法で書かれるように出来ます。
また\sml{infix}宣言は宣言であるため、\sml{let}式の宣言が並ぶ部分に書くことが出来ます。
利便性のため一部分でだけで中置記法を用いることが出来るようになります。
\footnote{つまりSMLではプログラム中で演算子の結合の強さが変わったり中置記法になったりそうでなくなったりするわけです。パーサーがどうなっているのか想像してみて下さい。SML KitのParsingの論文である''Parsing in the SML Kit''には''Syntactacally, SML is a nightmare''と書かれています。}
\subsubsection{パラメトリック多相性}
今までの関数はすべての型の構成要素が具体的な型(\sml{int}や\sml{real}とそのタプル)になっていました。
型推論の説明でも、定義の中に現れる演算子や関数の型からわからない変数の型を決定すると説明しました。
さて、もし関数を定義するとき、型の制約が何もなかったらどんな型を付ければよいでしょうか?
具体的には\prog{fun id x = x}のように関数\sml{id}を定義した時、
\sml{x}にはなんの型の制約はありません。この関数はどんな型を持つべきでしょうか。
正解は{\bfseries どんな型でもいい、型変数'a}を用いて、\prog{'a -> 'a}という型がつきます!
ソースコード\ref{code:id}を見て下さい。
\begin{lstlisting}[caption=多相関数,label=code:id]
- fun id x = x;
@/val id = fn : 'a -> 'a/@
(* id関数を使ってみる *)
- (id 1, id 3.0, id "hoge");
@/val it = (1,3.0,"hoge") : int * real * string/@
\end{lstlisting}
\prog{'a -> 'a}の\prog{'a}はどんな型にもなれます。
例えば\prog{id 1}というのを計算するとき、\prog{'a}は\prog{int}に
{\bfseries 単一化}({\itshape instantiation})されています。
そのため、\prog{id 1}の返り値も\sml{int}であることがわかります。
このような、型変数が含まれる関数のことを{\bfseries 多相関数}({\itshape polymorphic function})と呼びます。
単純ですがよく使われる多相関数をいくつか定義してみましょう。
\begin{lstlisting}[caption=多相関数たち,label=code:poly]
- fun fst (x, y) = x;
@/val fst = fn : 'a * 'b -> 'a/@
- fun snd (x, y) = y;
@/val snd = fn : 'a * 'b -> 'b/@
(* 使ってみる *)
- val t1 = (1, "hoge");;
@/val t1 = (1,"hoge") : int * string/@
- fst t1 + 2;
@/val it = 3 : int/@
\end{lstlisting}
\prog{'a}と\prog{'b}は違う型変数であることに気をつけて下さい。
もし\prog{fst:'a * 'a -> 'a}ならば、同じ型の要素が2つ入った
タプルしか受け取ることが出来ませんが、\prog{fst: 'a * 'b -> 'a}なので、
違う型の要素が2つ入ったタプルを受け取ることが出来ます。
このあたりの型の制約が書けることが、ただC言語の\sml{void*}型とは異なる部分です。
\sml{void*}に対しては型チェックをされませんが、型変数に対しては整合性が確認されます。
\subsubsection{再帰関数}
さて、いよいよ繰り返しを含むプログラムを書いていきましょう。
関数型言語ではよく繰り返しを書くために、{\bfseries 再帰関数}({\itshape recursive function})を用います。
例を見てきましょう。ソースコード\ref{code:recursive-funs}をみてください。
基本的には他の言語で書き下したのと同様です。
\begin{lstlisting}[caption=再帰関数,label=code:recursive-funs]
- fun fact n = if n = 1 then 1 else n * fact (n - 1);
@/val fact = fn : int -> int/@
- fact 5;
@/val it = 120 : int/@
\end{lstlisting}
再帰関数で気をつけることは特にありません。
せいぜい、再帰呼出しのために今定義しようとしている関数名が
fun文の右辺(fun文のイコール以降の式)に現れる、くらいでしょうか。
関数\sml{fact}を見てみましょう。ご存知のように、階乗は以下のように定義されます。
$fact \ n = \begin{cases} 1 & (if \ n = 1) \\ n * fact (n - 1) & otherwise \end{cases}$
階乗の定義は入力が$n=1$であれば1を返し、そうでなければ$n * fact (n - 1)$というものです。
この定義を素直に書き下しています。
% 再帰関数は帰納法的な考え方で構築することが出来ます。
% というのは関数\sml{fact}の場合、
% \begin{itemize}
% \item ベースケースを記述する(この場合$n=1$のとき)
% \item 帰納法の仮定(\prog{fact (n - 1)}は正しく計算される)をつかって、どうすれば\prog{fact n}の計算式は正しくなるのか考える
% \end{itemize}
% というものです。
% 「$fact (n - 1)$ は正しく$n - 1$の階乗として計算されるので、
% $n * fact (n - 1)$も$n$の階乗を計算する式として正しい」
% と考えることができれば完璧です。
% この考え方を用いて後で何回も再帰関数の説明をします。
\subsubsection{高階関数}
関数型(アロー型)の表現は\prog{T1 -> T2}という形で表されます。
この意味は\sml{T1}型の値を引数に取り、\sml{T2}型の値を返す関数という意味でした。
この例で\sml{T1}や\sml{T2}はアロー型であることも許されます。
そのような関数はどんな性質を持ちうるのでしょうか?例を見てみましょう。
\begin{lstlisting}[caption=第一級関数,label=code:first-order-funs]
(* T1 -> T2のT2が関数型である場合 *)
- fun plus x y = x + y;
@/val plus = fn : int -> int -> int/@
- val plusOne = plus 1;
@/val plusOne = fn : int -> int/@
- val plusTwo = plus 2;
@/val plusTwo = fn : int -> int/@
- plusTwo (plusOne 1);
@/val it = 4 : int/@
(* T1 -> T2のT1が関数型である場合 *)
- fun twice f = f (f 1);
@/val twice = fn : (int -> int) -> int/@
- twice plusOne;
@/val it = 3 : int/@
\end{lstlisting}
まずは\sml{T2}が関数型の例から見てみます。
ソースコード\ref{code:first-order-funs}の上の例では、
\prog{int -> (int -> int)}型であるの、
2つの引数を取りその引数を足す関数\sml{plus}を定義しています。
これはプログラム中の\prog{int -> int -> int}はカッコを省略した形式です。
今後省略できる括弧はすべて省略していきます。
この型に対して今までと同じ解釈をすることが出来ます。
すなわち、関数\sml{plus}は\sml{int}型の値を1つ受けると\sml{int -> int}型の値を返す、
{\bfseries 関数を返す関数}なのです!
関数を返す関数は、同じような関数をたくさん定義する場合に便利です。
例では同じような形をするだろう、関数\sml{plusOne}と\sml{plusTwo}を定義しています。
関数\sml{plusOne}は関数で、一つ引数をとり1足す関数になっています
(関数\sml{plus}の仮引数のxにすでに1が入っていると考えて下さい)。
関数\sml{plusTwo}も同様です。
T1が関数型であるような関数どういう解釈が出来るかというと、関数を受け取る関数であると考えらる事が出来ます。
関数\sml{twice}は1に対して同じ関数を2回適用する関数です。
\footnote{
ちなみに「関数を引数に適用する」という言葉は関数と引数の順番をよく間違えられています。
私は英語で覚えています。
apply A to Bは、意味はAをB当てはめるという意味です。
ジェネリックな存在である関数を実際の引数に当てはめる、という覚え方が簡単かなぁと思います}
\prog{int -> int}型の関数を引数にとり、それを2度使います。
このように関数が他の\sml{int}型や\sml{string}型の値と同じように
扱えることを指して「{\bfseries 関数が第一級である}」と言うことがあります。
また関数を返す関数や、関数を操作する関数を指して「{\bfseries 高階関数}」と呼びます。
さて、最後にこれらの要素を使った例として微分関数\sml{diff}を定義してみましょう。
ソースコード\ref{code:diff}を見て下さい。
微分演算子$'$(この例では\sml{diff}関数)
はまず\sml{real -> real}型の関数fを受け取り、一引数関数を返す関数です。
微分演算の定義を確認してみると、
$f' = \lim_{a \to 0}\frac{f(x+a) - f(x)}{a}$
でしたね。今回は極限は近似して$a = 0.0000000001$として計算してみます。
\begin{lstlisting}[caption=高階関数の例,label=code:diff]
- fun diff f x = let val a = 0.0000000001 in (f (x + a) - f x) / a end;
@/val diff = fn : (real -> real) -> real -> real/@
(* Math.sinはMathモジュールに含まれるsin関数にアクセスする記法 *)
- val myCos = diff Math.sin;
@/val myCos = fn : real -> real/@
(* 正しく動いているか確認 *)
- (myCos Math.pi, Math.cos Math.pi);
@/val it = (~1.00000008274,~1.0) : real * real/@
- (myCos (Math.pi / 2.0), Math.cos (Math.pi / 2.0));
@/val it = (0.0,6.12303176911E~17) : real * real/@
- (myCos (Math.pi / 3.0), Math.cos (Math.pi / 3.0));
@/val it = (0.50000004137,0.5) : real * real/@
\end{lstlisting}
関数\sml{diff}を定義した後、
確認のため標準ライブラリのMathモジュールに含まれる
\sml{sin}関数を微分して\sml{myCos}を定義しました。
その後、ライブラリに含まれる\sml{cos}関数と\sml{myCos}に同じ引数を与え、
出力結果を比べられるようにしました。誤差はあるものの、概ね\sml{cos}関数として動作しているように見えますね。
\subsubsection{local文}
関数定義のために補助的な定義を書きたいことは沢山あります。
しかしlet文がネストするのは避けたい場合というのはあると思いますが、
local文を用いると解決します。local文は \\
\prog{local @@dec1@ @@dec2@ ... in @@dec3@ @@dec4@ ... end}という文法です。
\sml{local}から\sml{in}までの定義は\sml{in}以降からはアクセスできますが、
外側からアクセスすることは出来ません。
\begin{lstlisting}[caption=local文]
- local
val a = 0.0000000001
fun diff f x = (f (x + a) - f x) / a
in
fun myCos r = diff Math.sin r
val f' = diff (fn x => x * x)
end;
@/val myCos = fn : real -> real/@
@/val f' = fn : real -> real/@
\end{lstlisting}
% \begin{itembox}[l]{コラム:for文にあたる関数を作る}
% % TODO: これいらない?
% 高階関数・多相性がある言語ではfor文やwhile文といった、言語組み込みの制御構造を作る構文に相当する
% ものすら関数として表すことが出来ます。まずはfor文を作ってみましょう。
% 大域脱出のために例外を用いています。
% \begin{lstlisting}[caption=for文相当の関数,label=code:for]
% exception Break
% fun for f s e step =
% let
% fun loop cnt = if cnt > e
% then () else ((f cnt handle Break => ()); loop (cnt + step))
% in
% loop s
% end;
% @/val iteri = fn : (int -> 'a) -> int -> int -> int -> unit/@
% - for (fn x => print (Int.toString x)) 0 10 1;
% 012345678910val it = () : unit
% \end{lstlisting}
% これではうまくグローバル変数を書き換えたりできない?クロージャーをうまく使いましょう。
% \begin{lstlisting}[caption=関数forを使ってみる,label=code:use-iteri]
% - fun fact n = let val sum = ref 1 in
% for (fn x => sum := x * !sum) 1 n 1; !sum
% end;
% val fact = fn : int -> int
% - fact 5;
% val it = 120 : int
% \end{lstlisting}
% \end{itembox}
\begin{itembox}[l]{コラム:アドホック多相性}
関数\sml{plus}の型を見て不自然に思った方もいるかもしれません。\\
プラス演算子は\prog{3.14 + 1.1}のように、real型の値の演算にも用いられます。
しかし、ソースコード\ref{code:first-order-funs}の関数\sml{plus}は
\prog{int -> int -> int}型の関数であると推論されています。
関数\sml{plus}に\prog{int -> int -> int}型がつくことは少し変です。
プラス演算子はreal型にも使えるはずなので、より一般的な型がついてもいいはずです。
これはSMLの型システムに由来します。
SMLの型システムで表せるのは「\textbf{何でも受け取る'a}」もしくは
「\sml{int}、\sml{real}などとそれを組み合わせたな具体型」だけです。
そのため、SMLではプラス演算子にようにアドホックに演算子が多相的になっているものがありますが、
それを型として表すことが出来ません。
この問題に対する解決策はいくつか考えられています。
一つは型クラスです。型クラスでは、「型パラメータ'a、ただし'aの動く範囲は制限されている」という事がかけます。
Haskellでは例えば、加算演算子は\prog{Num a => a -> a -> a}という型を持ちます。
\prog{Num a}の意味は「型パラメータaはNumに属している型に限られる」という意味です。
加算、減算などが可能な値の型は\sml{Num}に属するようになっています。
\begin{lstlisting}[caption=Haskellでのplusの型付け]
Prelude> let plus x y = x + y
Prelude> :t plus
@/plus :: Num a => a -> a -> a/@
\end{lstlisting}
東北大学の大堀研で開発されているSML\#
\footnote{\url{http://www.pllab.riec.tohoku.ac.jp/smlsharp/ja/}}では、
第一級オーバーローディングが実装されています。
例えば、関数\sml{plus}の型は以下のように表されます。
\begin{lstlisting}[caption=SML\#でのplusの型付け]
fun plus x = x + x;
@/val plus = fn/@
@/ : ['a::int, IntInf.int, real, Real32.real, word, Word8.word. 'a -> 'a] /@
\end{lstlisting}
\prog{'a::int, IntInf.int, real, Real32.real, word, Word8.word.}
は型変数'aはint, IntInt.int, real, ...のどれかであることを表しています。
このような型を持つ関数もユーザが書くことも出来ます。
\end{itembox}
\label{sec:list}
\subsection{リスト}
\subsubsection{リストの基本}
リストは変更不可能なデータ構造で、データの列を扱うときに
関数型プログラミングでは良く用いられる重要なデータ構造です。
リストは今までの型とは少し異なり、\prog{list}だけでは型を表しません。
型の一部をパラメータ化しており、
\prog{int list}や\prog{string list}など、
「{\bfseries 何かのリスト}」であることを型の上で表現します。
まずは例を見てみましょう。ソースコード\ref{code:list}を見て下さい。
\begin{lstlisting}[caption=色々なリスト,label=code:list]
- [1,2,3];
@/val it = [1,2,3] : int list/@
- ["sml", "ocaml", "haskell", "fsharp"];
@/val it = ["sml","ocaml","haskell","fsharp"] : string list/@
- [[(1, 2)],[(3, 4)]];
@/val it = [[(1,2)],[(3,4)]] : (int * int) list list/@
\end{lstlisting}
\sml{'a list}の\prog{'a}部分が色々な型に変わっていますね。
\prog{'a}は何が代入されてもいいので、リストがネストしたり中にタプルが入っても構いません。
空のリストなど、\prog{'a list}の\prog{'a}部分が単相化(具体的な型に置き換わってない)
されていないリストも考えられます。そのため、多相的なリストというものも考えられます。
\begin{lstlisting}[caption=多相的なリスト,label=code:poly-list]
- val empty = [];;
@/val empty = [] : 'a list/@
- 1 :: 2 :: empty;
@/val it = [1,2] : int list/@
- "hoge" :: empty;;
@/val it = ["hoge"] : string list/@
- [] :: empty;;
@/val it = [[]] : 'a list list/@
\end{lstlisting}
リストを扱うためにプリミティブな演算子を紹介します。
\begin{itemize}
\item \prog{::} \ 演算子は、リストの先頭に要素を追加して新しいリストを作る演算子です。
\item \prog{@} \ 演算子は、リスト同士を結合する演算子です。
\end{itemize}
\begin{lstlisting}[caption=リスト操作演算子の型,label=code:list-operators]
- op ::;
@/val it = fn : 'a * 'a list -> 'a list/@
- op @;
@/val it = fn : 'a list * 'a list -> 'a list/@
\end{lstlisting}
注意することは、\prog{::}は右結合的だということです。
例えば\prog{1 :: 2 :: []}と書いた時には \\
\prog{1 :: (2 :: [])}のように演算子が結合します。