-
Notifications
You must be signed in to change notification settings - Fork 26
/
References_J.v
2710 lines (2419 loc) · 123 KB
/
References_J.v
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
(** * References_J: 変更可能な参照の型付け *)
(* * References: Typing Mutable References *)
(* $Date: 2011-06-03 13:58:55 -0400 (Fri, 03 Jun 2011) $ *)
Require Export Smallstep_J.
(* So far, we have considered a variety of _pure_ language features,
including functional abstraction, basic types such as numbers and
booleans, and structured types such as records and variants. These
features form the backbone of most programming languages -- including
purely functional languages such as Haskell, "mostly functional"
languages such as ML, imperative languages such as C, and
object-oriented languages such as Java.
Most practical programming languages also include various _impure_
features that cannot be described in the simple semantic framework
we have used so far. In particular, besides just yielding
results, evaluation of terms in these languages may assign to
mutable variables (reference cells, arrays, mutable record fields,
etc.), perform input and output to files, displays, or network
connections, make non-local transfers of control via exceptions,
jumps, or continuations, engage in inter-process synchronization
and communication, and so on. In the literature on programming
languages, such "side effects" of computation are more generally
referred to as _computational effects_.
In this chapter, we'll see how one sort of computational
effect -- mutable references -- can be added to the calculi we have
studied. The main extension will be dealing explicitly with a
_store_ (or _heap_). This extension is straightforward to define;
the most interesting part is the refinement we need to make to the
statement of the type preservation theorem. *)
(** ここまでは、いろいろな「純粋な」(_pure_)言語機能を考えてきました。
関数抽象、数値やブール値などの基本型、レコードやバリアントのような構造型などです。
これらの機能はほとんどのプログラミング言語のバックボーンを構成しています。
その言語の中にはHaskellのような純粋な関数型言語、MLのような
「ほとんど関数型の」("mostly functional")言語、Cのような命令型言語、
Javaのようなオブジェクト指向言語を含みます。
ほとんどの実際のプログラミング言語は、
ここまで使ってきた単純な意味論の枠組みでは記述できない様々な「不純な」(_impure_)
機能も持っています。特に、これらの言語では項を評価することで、単に結果を得る他に、
変更可能な変数(あるいは参照セル、配列、変更可能なレコードフィールド、等)に代入したり、
ファイルや画面やネットワークに入出力したり、例外やジャンプ、
継続によってローカルな枠を越えて制御を移したり、プロセス間の同期や通信を行ったりします。
プログラミング言語についての文献では、
これらの計算の副作用("side effects")はより一般に計算作用(_computational effects_)
と参照されます。
この章では、
ここまで学習してきた計算体系に一つの計算作用「変更可能な参照」を追加する方法を見ます。
主要な拡張は、記憶(_store_、あるいはヒープ(_heap_))を明示的に扱うことです。
この拡張は直接的に定義できます。
一番興味深い部分は、型保存定理の主張のために必要なリファインメント(refinement)です。 *)
(* ###################################################################### *)
(* ** Definitions *)
(** ** 定義 *)
(* Pretty much every programming language provides some form of
assignment operation that changes the contents of a previously
allocated piece of storage. (Coq's internal language is a rare
exception!)
In some languages -- notably ML and its relatives -- the
mechanisms for name-binding and those for assignment are kept
separate. We can have a variable [x] whose _value_ is the number
[5], or we can have a variable [y] whose value is a
_reference_ (or _pointer_) to a mutable cell whose current
contents is [5]. These are different things, and the difference
is visible to the programmer. We can add [x] to another number,
but not assign to it. We can use [y] directly to assign a new
value to the cell that it points to (by writing [y:=84]), but we
cannot use it directly as an argument to an operation like [+].
Instead, we must explicitly _dereference_ it, writing [!y] to
obtain its current contents.
In most other languages -- in particular, in all members of the C
family, including Java -- _every_ variable name refers to a mutable
cell, and the operation of dereferencing a variable to obtain its
current contents is implicit.
For purposes of formal study, it is useful to keep these
mechanisms separate. The development in this chapter will closely
follow ML's model. Applying the lessons learned here to C-like
languages is a straightforward matter of collapsing some
distinctions and rendering some operations such as dereferencing
implicit instead of explicit.
In this chapter, we study adding mutable references to the
simply-typed lambda calculus with natural numbers. *)
(** ほとんどすべてのプログラミング言語が、
記憶に以前に置かれた内容を変更する何らかの代入操作を持っています
(Coqの内部言語は稀な例外です!)。
いくつかの言語(特にMLやその親戚)では、
名前束縛の機構と代入の機構を区別しています。
「値」として数値[5]を持つ変数[x]を持つことも、
現在の内容が[5]である変更可能なセルへの参照(_reference_、
またはポインタ(_pointer_))を値とする変数[y]を持つこともできます。
この2つは別のものです。プログラマにも両者の違いは見ることができます。
[x]と別の数を足すことは可能ですが、それを[x]に代入することはできません。
[y]を直接使って、[y]が指すセルに別の値を代入することが([y:=84] と書くことで)できます。
しかし、この値は[+]のような操作の引数として直接使うことはできません。
その代わり、現在の内容を得るために明示的に参照を手繰る
(_dereference_、逆参照する)ことが必要です。これを[!y]と書きます。
他のほとんどの言語、特にJavaを含むCファミリーのメンバーのすべてでは、
すべての変数名は変更可能なセルを指します。
そして、現在の値を得るための変数の逆参照操作は暗黙に行われます。
形式的な学習の目的には、この2つの機構を分離しておいた方が便利です。
この章の進行は、MLのやり方にほとんど従います。
ここでやったことをCのような言語に適用するのは、分離していたものを一緒にすることと、
逆参照のような操作を明示的なものから暗黙のものにするという単純な問題です。
この章では、自然数を持つ単純型付きラムダ計算に変更可能な参照を追加すること学習します。 *)
(* ###################################################################### *)
(* ** Syntax *)
(** ** 構文 *)
Module STLCRef.
(* The basic operations on references are _allocation_,
_dereferencing_, and _assignment_.
- To allocate a reference, we use the [ref] operator, providing
an initial value for the new cell. For example, [ref 5]
creates a new cell containing the value [5], and evaluates to
a reference to that cell.
- To read the current value of this cell, we use the
dereferencing operator [!]; for example, [!(ref 5)] evaluates
to [5].
- To change the value stored in a cell, we use the assignment
operator. If [r] is a reference, [r := 7] will store the
value [7] in the cell referenced by [r]. However, [r := 7]
evaluates to the trivial value [unit]; it exists only to have
the _side effect_ of modifying the contents of a cell. *)
(** 参照についての基本操作はアロケート(_allocation_)、逆参照(_dereferencing_)、
代入(_assignment_)です。
- 参照をアロケートするには、[ref]演算子を使います。
これにより新しいセルに初期値が設定されます。
例えば [ref 5] は値[5]を格納した新しいセルを生成し、
そのセルへの参照に評価されます。
- セルの現在の値を読むためには、逆参照演算子[!]を使います。
例えば [!(ref 5)] は [5] に評価されます。
- セルに格納された値を変更するには、代入演算子を使います。
[r]が参照ならば、[r := 7] は [r] によって参照されるセルに値[7]を格納します。
しかし [r := 7] はどうでも良い値 [unit] に評価されます。
この演算子はセルの内容を変更するという副作用のためだけに存在します。 *)
(* ################################### *)
(* *** Types *)
(** *** 型 *)
(* We start with the simply typed lambda calculus over the
natural numbers. To the base natural number type and arrow types
we need to add two more types to deal with references. First, we
need the _unit type_, which we will use as the result type of an
assignment operation. We then add _reference types_. *)
(** 自然数の上の単純型付きラムダ計算から始めます。
基本の自然数型と関数型に参照を扱う2つの型を追加する必要があります。
第一に「Unit型」です。これは代入演算子の結果の型として使います。
それから参照型(_reference types_)を追加します。 *)
(* If [T] is a type, then [Ref T] is the type of references which
point to a cell holding values of type [T].
<<
T ::= Nat
| Unit
| T -> T
| Ref T
>>
*)
(** [T]が型のとき、[Ref T] は型[T]の値を持つセルを指す参照の型です。
<<
T ::= Nat
| Unit
| T -> T
| Ref T
>>
*)
Inductive ty : Type :=
| ty_Nat : ty
| ty_Unit : ty
| ty_arrow : ty -> ty -> ty
| ty_Ref : ty -> ty.
(* ################################### *)
(* *** Terms *)
(** *** 項 *)
(* Besides variables, abstractions, applications,
natural-number-related terms, and [unit], we need four more sorts
of terms in order to handle mutable references:
<<
t ::= ... Terms
| ref t allocation
| !t dereference
| t := t assignment
| l location
>>
*)
(** 変数、関数抽象、関数適用、自然数に関する項、[unit]の他に、
変更可能な参照を扱うために4種類の項を追加する必要があります:
<<
t ::= ... Terms
| ref t allocation
| !t dereference
| t := t assignment
| l location
>>
*)
Inductive tm : Type :=
(* STLC with numbers: *)
| tm_var : id -> tm
| tm_app : tm -> tm -> tm
| tm_abs : id -> ty -> tm -> tm
| tm_nat : nat -> tm
| tm_succ : tm -> tm
| tm_pred : tm -> tm
| tm_mult : tm -> tm -> tm
| tm_if0 : tm -> tm -> tm -> tm
(* New terms: *)
| tm_unit : tm
| tm_ref : tm -> tm
| tm_deref : tm -> tm
| tm_assign : tm -> tm -> tm
| tm_loc : nat -> tm.
(* Intuitively...
- [ref t] (formally, [tm_ref t]) allocates a new reference cell
with the value [t] and evaluates to the location of the newly
allocated cell;
- [!t] (formally, [tm_deref t]) evaluates to the contents of the
cell referenced by [t];
- [t1 := t2] (formally, [tm_assign t1 t2]) assigns [t2] to the
cell referenced by [t1]; and
- [l] (formally, [tm_loc l]) is a reference to the cell at
location [l]. We'll discuss locations later. *)
(** 直観的には...
- [ref t] (形式的には [tm_ref t])は値[t]が格納された新しい参照セルをアロケートし、
新しくアロケートされたセルの場所(location)を評価結果とします。
- [!t] (形式的には [tm_deref t])は[t]で参照されるセルの内容を評価結果とします。
- [t1 := t2] (形式的には [tm_assign t1 t2])は[t1]で参照されるセルに[t2]を代入します。
- [l] (形式的には [tm_loc l])は場所[l]のセルの参照です。場所については後で議論します。 *)
(* In informal examples, we'll also freely use the extensions
of the STLC developed in the [MoreStlc] chapter; however, to keep
the proofs small, we won't bother formalizing them again here. It
would be easy to do so, since there are no very interesting
interactions between those features and references. *)
(** 非形式的な例では、[MoreStlc_J]章で行ったSTLCの拡張も自由に使います。
しかし、証明を小さく保つため、ここでそれらを再度形式化することに煩わされることはしません。
やろうと思えばそうすることは簡単です。なぜなら、
それらの拡張と参照とには興味深い相互作用はないからです。*)
Tactic Notation "tm_cases" tactic(first) ident(c) :=
first;
[ Case_aux c "tm_var" | Case_aux c "tm_app"
| Case_aux c "tm_abs" | Case_aux c "tm_zero"
| Case_aux c "tm_succ" | Case_aux c "tm_pred"
| Case_aux c "tm_mult" | Case_aux c "tm_if0"
| Case_aux c "tm_unit" | Case_aux c "tm_ref"
| Case_aux c "tm_deref" | Case_aux c "tm_assign"
| Case_aux c "tm_loc" ].
Module ExampleVariables.
Definition x := Id 0.
Definition y := Id 1.
Definition r := Id 2.
Definition s := Id 3.
End ExampleVariables.
(* ################################### *)
(* *** Typing (Preview) *)
(** *** 型付け (プレビュー) *)
(* Informally, the typing rules for allocation, dereferencing, and
assignment will look like this:
[[[
Gamma |- t1 : T1
------------------------ (T_Ref)
Gamma |- ref t1 : Ref T1
Gamma |- t1 : Ref T11
--------------------- (T_Deref)
Gamma |- !t1 : T11
Gamma |- t1 : Ref T11
Gamma |- t2 : T11
------------------------ (T_Assign)
Gamma |- t1 := t2 : Unit
]]]
The rule for locations will require a bit more machinery, and this
will motivate some changes to the other rules; we'll come back to
this later. *)
(** 非形式的には、アロケーション、逆参照、代入の型付け規則は以下のようになります:
[[
Gamma |- t1 : T1
------------------------ (T_Ref)
Gamma |- ref t1 : Ref T1
Gamma |- t1 : Ref T11
--------------------- (T_Deref)
Gamma |- !t1 : T11
Gamma |- t1 : Ref T11
Gamma |- t2 : T11
------------------------ (T_Assign)
Gamma |- t1 := t2 : Unit
]]
場所についての規則はもう少し仕掛けが必要になり、
それが他の規則にいくらかの変更を求めることになります。
これについては後にまた戻ってきます。*)
(* ################################### *)
(* *** Values and Substitution *)
(** *** 値と置換 *)
(* Besides abstractions and numbers, we have two new types of values:
the unit value, and locations. *)
(** 関数抽象と数値に加えて、新たに2種類の値を持ちます: unit値と場所です。 *)
Inductive value : tm -> Prop :=
| v_abs : forall x T t,
value (tm_abs x T t)
| v_nat : forall n,
value (tm_nat n)
| v_unit :
value tm_unit
| v_loc : forall l,
value (tm_loc l).
Hint Constructors value.
(* Extending substitution to handle the new syntax of terms is
straightforward. *)
(** 新しい項の構文を扱うための置換の拡張は直接的です。 *)
Fixpoint subst (x:id) (s:tm) (t:tm) : tm :=
match t with
| tm_var x' =>
if beq_id x x' then s else t
| tm_app t1 t2 =>
tm_app (subst x s t1) (subst x s t2)
| tm_abs x' T t1 =>
if beq_id x x' then t else tm_abs x' T (subst x s t1)
| tm_nat n =>
t
| tm_succ t1 =>
tm_succ (subst x s t1)
| tm_pred t1 =>
tm_pred (subst x s t1)
| tm_mult t1 t2 =>
tm_mult (subst x s t1) (subst x s t2)
| tm_if0 t1 t2 t3 =>
tm_if0 (subst x s t1) (subst x s t2) (subst x s t3)
| tm_unit =>
t
| tm_ref t1 =>
tm_ref (subst x s t1)
| tm_deref t1 =>
tm_deref (subst x s t1)
| tm_assign t1 t2 =>
tm_assign (subst x s t1) (subst x s t2)
| tm_loc _ =>
t
end.
(* ###################################################################### *)
(* * Pragmatics *)
(** * プラグマティクス(語用論) *)
(* ################################### *)
(* ** Side Effects and Sequencing *)
(** ** 副作用と順次処理 *)
(* The fact that the result of an assignment expression is the
trivial value [unit] allows us to use a nice abbreviation for
_sequencing_. For example, we can write
<<
r:=succ(!r); !r
>>
as an abbreviation for
<<
(\x:Unit. !r) (r := succ(!r)).
>>
This has the effect of evaluating two expressions in order and
returning the value of the second. Restricting the type of the first
expression to [Unit] helps the typechecker to catch some silly
errors by permitting us to throw away the first value only if it
is really guaranteed to be trivial.
Notice that, if the second expression is also an assignment, then
the type of the whole sequence will be [Unit], so we can validly
place it to the left of another [;] to build longer sequences of
assignments:
<<
r:=succ(!r); r:=succ(!r); r:=succ(!r); r:=succ(!r); !r
>>
*)
(** 代入式の結果がつまらない値[unit]であるという事実によって、
順次処理(_sequencing_)のうまい略記が可能になります。
例えば、
<<
(\x:Unit. !r) (r := succ(!r)).
>>
の略記として
<<
r:=succ(!r); !r
>>
と書くことができます。
これは2つの式を順番に評価するという作用を持ち、2つ目の式の値を返します。
1つ目の式の型を[Unit]に限定することで、
1つ目の値を捨てることができるのは本当にそれがつまらない値であることが保証されているときだけになり、
型チェッカで馬鹿なエラーをチェックするのに役立ちます。
なお、もし2つ目の式もまた代入ならば、2つの式の列全体の型が[Unit]になります。
これから、より長い代入の列を作るために別の[;]の左側に置いても問題ありません:
<<
r:=succ(!r); r:=succ(!r); r:=succ(!r); r:=succ(!r); !r
>>
*)
(* Formally, we introduce sequencing as a "derived form"
[tm_seq] that expands into an abstraction and an application. *)
(** 形式的には、順次処理を"derived form"として導入します。
この"derived form"は、関数抽象と関数適用に展開されます。 *)
Definition tm_seq t1 t2 :=
tm_app (tm_abs (Id 0) ty_Unit t2) t1.
(* ################################### *)
(* ** References and Aliasing *)
(** ** 参照と別名付け *)
(* It is important to bear in mind the difference between the
_reference_ that is bound to [r] and the _cell_ in the store that
is pointed to by this reference.
If we make a copy of [r], for example by binding its value to
another variable [s], what gets copied is only the _reference_,
not the contents of the cell itself.
For example, after evaluating
<<
let r = ref 5 in
let s = r in
s := 82;
(!r)+1
>>
the cell referenced by [r] will contain the value [82], while the
result of the whole expression will be [83]. The references [r]
and [s] are said to be _aliases_ for the same cell.
The possibility of aliasing can make programs with references
quite tricky to reason about. For example, the expression
<<
r := 5; r := !s
>>
assigns [5] to [r] and then immediately overwrites it with [s]'s
current value; this has exactly the same effect as the single
assignment
<<
r := !s
>>
_unless_ we happen to do it in a context where [r] and [s] are
aliases for the same cell! *)
(** [r]に束縛される参照(_reference_)と、
この参照によって指されているセル(_cell_)の違いを心に留めておく必要があります。
例えば[r]を別の変数[s]に束縛することで[r]のコピーを作るとすると、
コピーされるのは参照だけで、セルの中身自身ではありません。
例えば、次の式を評価します:
<<
let r = ref 5 in
let s = r in
s := 82;
(!r)+1
>>
するとその後で[r]によって参照されたセルは値[82]を格納している状態になります。
一方、式全体の結果は[83]になります。参照[r]と[s]は同じセルの別名(_aliases_)と言われます。
別名を付けられる能力があることによって、参照を持つプログラムに関する推論は、
きわめてトリッキーになります。例えば、式
<<
r := 5; r := !s
>>
は[r]に[5]を代入し、直ぐにそれを[s]の現在の値で上書きします。
これは、単一の代入
<<
r := !s
>>
と完全に同じ作用をします。
ただし、「[r]と[s]がたまたま同じセルの別名であるという状況でない限り」、です! *)
(* ################################### *)
(* ** Shared State *)
(** ** 共有状態 *)
(* Of course, aliasing is also a large part of what makes references
useful. In particular, it allows us to set up "implicit
communication channels" -- shared state -- between different parts
of a program. For example, suppose we define a reference cell and
two functions that manipulate its contents:
<<
let c = ref 0 in
let incc = \_:Unit. (c := succ (!c); !c) in
let decc = \_:Unit. (c := pred (!c); !c) in
...
>>
*)
(** もちろん、別名も、参照を便利なものにする大きな部分です。
特に参照は、プログラムの異なる部分の間の暗黙の通信チャンネル
("implicit communication channels")、
つまり共有状態(shared state)としてはたらきます。
例えば、参照セルと、その内容を扱う2つの関数を定義するとします:
<<
let c = ref 0 in
let incc = \_:Unit. (c := succ (!c); !c) in
let decc = \_:Unit. (c := pred (!c); !c) in
...
>>
*)
(* Note that, since their argument types are [Unit], the
abstractions in the definitions of [incc] and [decc] are not
providing any useful information to the bodies of the
functions (using the wildcard [_] as the name of the bound
variable is a reminder of this). Instead, their purpose is to
"slow down" the execution of the function bodies: since function
abstractions are values, the two [let]s are executed simply by
binding these functions to the names [incc] and [decc], rather
than by actually incrementing or decrementing [c]. Later, each
call to one of these functions results in its body being executed
once and performing the appropriate mutation on [c]. Such
functions are often called _thunks_.
In the context of these declarations, calling [incc] results in
changes to [c] that can be observed by calling [decc]. For
example, if we replace the [...] with [(incc unit; incc unit; decc
unit)], the result of the whole program will be [1]. *)
(** ここで、それぞれ引数の型は[Unit]なので、
[incc]と[decc]の定義において関数抽象は関数本体に特に有用な情報を提供しないことに注意します
(束縛変数名にワイルドカード[_]を使っているのは、このことを合図したものです)。
そうではなく、関数抽象の目的は関数本体の実行を「遅く」するためです。
関数抽象は値であることから、2つの[let]は単に2つの関数を名前[incc]と[decc]に束縛するだけで、
実際に[c]を増やしたり減らしたりはしません。後に、これらの関数の1つを呼び出すたびに、
その本体が1度実行され[c]について対応する変更が行われます。
こういった関数はしばしば _thunk_ と呼ばれます。
これらの宣言のコンテキストで、[incc]を呼ぶと[c]が変更されますが、
これは[decc]を呼ぶことで確認できます。
例えば [...] を [(incc unit; incc unit; decc unit)] に換えると、
プログラム全体の結果は[1]になります。 *)
(* ** Objects *)
(** ** オブジェクト *)
(* We can go a step further and write a _function_ that creates [c],
[incc], and [decc], packages [incc] and [decc] together into a
record, and returns this record:
<<
newcounter =
\_:Unit.
let c = ref 0 in
let incc = \_:Unit. (c := succ (!c); !c) in
let decc = \_:Unit. (c := pred (!c); !c) in
{i=incc, d=decc}
>>
*)
(** もう一歩進んで、[c]、[incc]、[decc]を生成し、[incc]と[decc]をレコードにパッケージ化し、
このレコードを返す「関数」を記述することもできます:
<<
newcounter =
\_:Unit.
let c = ref 0 in
let incc = \_:Unit. (c := succ (!c); !c) in
let decc = \_:Unit. (c := pred (!c); !c) in
{i=incc, d=decc}
>>
*)
(* Now, each time we call [newcounter], we get a new record of
functions that share access to the same storage cell [c]. The
caller of [newcounter] can't get at this storage cell directly,
but can affect it indirectly by calling the two functions. In
other words, we've created a simple form of _object_.
<<
let c1 = newcounter unit in
let c2 = newcounter unit in
// Note that we've allocated two separate storage cells now!
let r1 = c1.i unit in
let r2 = c2.i unit in
r2 // yields 1, not 2!
>>
*)
(** このとき、[newcounter]を呼ぶたびに、
同じ記憶セル[c]のアクセスを共有する2つの関数の新たなレコードが得られます。
[newcounter]を呼び出す側はこの記憶セルには直接手が届きませんが、
2つの関数を呼ぶことで間接的に影響を及ぼすことができます。
言い換えると、簡単な形のオブジェクト(_object_)を作ったのです。
<<
let c1 = newcounter unit in
let c2 = newcounter unit in
// ここで2つの別個の記憶セルをアロケートしたことに注意!
let r1 = c1.i unit in
let r2 = c2.i unit in
r2 // 1 を返します。2ではありません!
>>
*)
(* **** Exercise: 1 star *)
(** **** 練習問題: ★ *)
(* Draw (on paper) the contents of the store at the point in
execution where the first two [let]s have finished and the third
one is about to begin. *)
(** 最初の2つの[let]が完了し3つ目が始まろうとする時点の記憶の中身を(紙の上に)描きなさい。 *)
(* FILL IN HERE *)
(** [] *)
(* ################################### *)
(* ** References to Compound Types *)
(** ** 参照と合成型 *)
(* A reference cell need not contain just a number: the primitives
we've defined above allow us to create references to values of any
type, including functions. For example, we can use references to
functions to give a (not very efficient) implementation of arrays
of numbers, as follows. Write [NatArray] for the type
[Ref (Nat->Nat)].
Recall the [equal] function from the [MoreStlc] chapter:
<<
equal =
fix
(\eq:Nat->Nat->Bool.
\m:Nat. \n:Nat.
if m=0 then iszero n
else if n=0 then false
else eq (pred m) (pred n))
>>
Now, to build a new array, we allocate a reference cell and fill
it with a function that, when given an index, always returns [0].
<<
newarray = \_:Unit. ref (\n:Nat.0)
>>
To look up an element of an array, we simply apply
the function to the desired index.
<<
lookup = \a:NatArray. \n:Nat. (!a) n
>>
The interesting part of the encoding is the [update] function. It
takes an array, an index, and a new value to be stored at that index, and
does its job by creating (and storing in the reference) a new function
that, when it is asked for the value at this very index, returns the new
value that was given to [update], and on all other indices passes the
lookup to the function that was previously stored in the reference.
<<
update = \a:NatArray. \m:Nat. \v:Nat.
let oldf = !a in
a := (\n:Nat. if equal m n then v else oldf n);
>>
References to values containing other references can also be very
useful, allowing us to define data structures such as mutable
lists and trees. *)
(** 参照セルの中身は数値でなければならないわけではありません。
上で定義したプリミティブによって、任意の型の値への参照を作ることができます。
その任意の型の中には関数型も含まれます。
例えば、関数への参照を使って、数値の配列の(あまり効率的でない)実装をすることができます。
以下の通りです。型 [Ref (Nat->Nat)] を [NatArray] と書きます。
[MoreStlc_J]章での[equal]関数を思い出してください:
<<
equal =
fix
(\eq:Nat->Nat->Bool.
\m:Nat. \n:Nat.
if m=0 then iszero n
else if n=0 then false
else eq (pred m) (pred n))
>>
このとき、新しい配列を作るために、参照セルをアロケートし、そのセルに関数を入れます。
その関数はインデックスを与えられると常に[0]を返します。
<<
newarray = \_:Unit. ref (\n:Nat.0)
>>
配列の要素をとりだすためには、その関数を求められたインデックスに適用するだけです。
<<
lookup = \a:NatArray. \n:Nat. (!a) n
>>
このエンコードの興味深いところは[update]関数です。
[update]関数は、配列、インデックス、そのインデックスの場所に格納する新しい値をとり、
新しい関数を生成し(そしてそれを参照に格納し)ます。
その関数は、この特定のインデックスの値を尋かれたときには[update]に与えられた新しい値を返します。
他のインデックスについては、以前にその参照に格納されていた関数にまかせます。
<<
update = \a:NatArray. \m:Nat. \v:Nat.
let oldf = !a in
a := (\n:Nat. if equal m n then v else oldf n);
>>
別の参照を含む値への参照もまたとても有用です。
これにより、変更可能なリストや木などのデータ構造が定義できるようになります。 *)
(* **** Exercise: 2 stars *)
(** **** 練習問題: ★★ *)
(* If we defined [update] more compactly like this
<<
update = \a:NatArray. \m:Nat. \v:Nat.
a := (\n:Nat. if equal m n then v else (!a) n)
>>
would it behave the same? *)
(** もし[update]を次のようによりコンパクトに定義したとします。
<<
update = \a:NatArray. \m:Nat. \v:Nat.
a := (\n:Nat. if equal m n then v else (!a) n)
>>
これは前の定義と同じように振る舞うでしょうか? *)
(* FILL IN HERE *)
(** [] *)
(* ################################### *)
(* ** Null References *)
(** ** null参照 *)
(* There is one more difference between our references and C-style
mutable variables: in C-like languages, variables holding pointers
into the heap may sometimes have the value [NULL]. Dereferencing
such a "null pointer" is an error, and results in an
exception (Java) or in termination of the program (C).
Null pointers cause significant trouble in C-like languages: the
fact that any pointer might be null means that any dereference
operation in the program can potentially fail. However, even in
ML-like languages, there are occasionally situations where we may
or may not have a valid pointer in our hands. Fortunately, there
is no need to extend the basic mechanisms of references to achieve
this: the sum types introduced in the [MoreStlc] chapter already
give us what we need.
First, we can use sums to build an analog of the [option] types
introduced in the [Lists] chapter. Define [Option T] to be an
abbreviation for [Unit + T].
Then a "nullable reference to a [T]" is simply an element of the
type [Ref (Option T)]. *)
(** ここで定義した参照と、C言語スタイルの変更可能な変数にはもう一つの違いがあります。
Cのような言語では、ヒープへのポインタを持つ変数は値[NULL]を持つことがあります。
そのような「nullポインタ」の逆参照はエラーで、
例外になったり(Java)、プログラムが停止したり(C)します。
Cのような言語ではnullポインタは重大な問題を起こします。
任意のポインタがnullになる可能性があるという事実は、
任意の逆参照操作が潜在的に失敗の可能性を持つということです。
しかしMLのような言語でも、
時には正しいポインタを持つことを許すことも許さないこともできるようにしたい場合があります。
幸い、参照の基本メカニズムを拡張しなくてもこれは実現できます。
[MoreStlc_J]章で導入された直和型によってそれが可能になります。
最初に、直和を使って、[Lists_J]章で導入した[option]型に対応するものを構築します。
[Option T] を [Unit + T] の略記法として定義します。
すると、「nullになり得る[T]への参照」は単に型 [Ref (Option T)] の要素となります。 *)
(* ################################### *)
(* ** Garbage Collection *)
(** ** ガベージコレクション *)
(* A last issue that we should mention before we move on with
formalizing references is storage _de_-allocation. We have not
provided any primitives for freeing reference cells when they are
no longer needed. Instead, like many modern languages (including
ML and Java) we rely on the run-time system to perform _garbage
collection_, collecting and reusing cells that can no longer be
reached by the program.
This is _not_ just a question of taste in language design: it is
extremely difficult to achieve type safety in the presence of an
explicit deallocation operation. The reason for this is the
familiar _dangling reference_ problem: we allocate a cell holding
a number, save a reference to it in some data structure, use it
for a while, then deallocate it and allocate a new cell holding a
boolean, possibly reusing the same storage. Now we can have two
names for the same storage cell -- one with type [Ref Nat] and the
other with type [Ref Bool]. *)
(** 参照の形式化に移る前に述べておくべき最後の問題が、
記憶のデアロケーション(_de_-allocation)です。
参照セルが必要なくなったときにそれを解放する何らかのプリミティブを提供していません。
その代わり、多くの近代的な言語(MLとJavaを含む)のように、
実行時システムがガベージコレクション(_garbage collection_)を行うことに頼っています。
ガベージコレクションはプログラムから到達しなくなったセルを集め再利用するものです。
これは言語デザインの上で単なる趣味の問題ではありません。
明示的なデアロケーション操作が存在した場合、型安全性を保つのが極度に困難になるのです。
その理由はよく知られたダングリング参照(_dangling reference_)問題です。
数値を持つセルをアロケートし、何かのデータ構造にそれへの参照を持たせ、それをしばらく利用し、
そしてそれをデアロケートし、ブール値を持つ新しいセルをアロケートします。
このとき同じ記憶が再利用されるかもしれません。
すると、同じ記憶セルに2つの名前があることになります。1つは [Ref Nat] 型で、
もう1つは [Ref Bool] 型です。 *)
(* **** Exercise: 1 star *)
(** **** 練習問題: ★ *)
(* Show how this can lead to a violation of type safety. *)
(** このことがどのように型安全性の破壊につながるのか示しなさい。 *)
(* FILL IN HERE *)
(** [] *)
(* ###################################################################### *)
(* * Operational Semantics *)
(** * 操作的意味 *)
(* ################################### *)
(* ** Locations *)
(** ** 場所(Locations) *)
(* The most subtle aspect of the treatment of references
appears when we consider how to formalize their operational
behavior. One way to see why is to ask, "What should be the
_values_ of type [Ref T]?" The crucial observation that we need
to take into account is that evaluating a [ref] operator should
_do_ something -- namely, allocate some storage -- and the result
of the operation should be a reference to this storage.
What, then, is a reference?
The run-time store in most programming language implementations is
essentially just a big array of bytes. The run-time system keeps track
of which parts of this array are currently in use; when we need to
allocate a new reference cell, we allocate a large enough segment from
the free region of the store (4 bytes for integer cells, 8 bytes for
cells storing [Float]s, etc.), mark it as being used, and return the
index (typically, a 32- or 64-bit integer) of the start of the newly
allocated region. These indices are references.
For present purposes, there is no need to be quite so concrete.
We can think of the store as an array of _values_, rather than an
array of bytes, abstracting away from the different sizes of the
run-time representations of different values. A reference, then,
is simply an index into the store. (If we like, we can even
abstract away from the fact that these indices are numbers, but
for purposes of formalization in Coq it is a bit more convenient
to use numbers.) We'll use the word _location_ instead of
_reference_ or _pointer_ from now on to emphasize this abstract
quality.
Treating locations abstractly in this way will prevent us from
modeling the _pointer arithmetic_ found in low-level languages
such as C. This limitation is intentional. While pointer
arithmetic is occasionally very useful, especially for
implementing low-level services such as garbage collectors, it
cannot be tracked by most type systems: knowing that location [n]
in the store contains a [float] doesn't tell us anything useful
about the type of location [n+4]. In C, pointer arithmetic is a
notorious source of type safety violations. *)
(** 参照の扱いについての一番巧妙な面は、
操作的振る舞いをどのように形式化するかを考えるときに現れます。
それが何故かを見る1つの方法は、「何が型 [Ref T] の値であるべきか?」と問うことです。
考慮すべき重要な点は、[ref]演算子の評価は何かを(つまり記憶のアロケートを)
「行わ」なければならず、
操作の結果はこの記憶への参照とならなければならないということです。
それでは、参照とは何でしょうか?
ほとんどのプログラミング言語の実装では、実行時の記憶は本質的にはバイト(byte)
の(大きな)配列です。
実行時システムは、この配列のどの部分が現在使用されているかを常に監視しています。
新しい参照セルをアロケートしなければならないとき、
記憶の自由範囲から十分な大きさのセグメント
(整数セルには4バイト、[Float]のセルには8バイト、等)をアロケートします。
このセグメントに「使用中」のマークをして、新しくアロケートされた領域のインデックス
(典型的には32ビットまたは64ビットの整数)を返却します。
参照とはこのインデックスのことです。
現在の目的のためには、これほど具体的である必要はありません。
記憶を、バイトではなく「値」の配列と考え、異なる値の実行時表現のサイズの違いは捨象します。
すると参照は、単に記憶へのインデックスになります。
(望むならば、これらのインデックスが数値であるという事実さえも捨象することができます。
しかし、Coqで形式化する目的のためには、数値を使った方が若干便利です。)
この抽象度であることを強調するため、これ以降、用語として、「参照」や「ポインタ」の代わりに
「場所」(_location_)を使います。
この方法で場所を抽象的に扱うことで、Cのような低レベルの言語で見られる「ポインタ算術」
(_pointer arithmetic_)をモデル化することができなくなります。
この制約は意図的なものです。ポインタ算術は時には非常に便利です。
特にガベージコレクタのような低レベルのサービスを実装するときなどです。
しかし、ほとんどの型システムではポインタ算術を追跡することができません。
記憶の場所[n]に[float]が格納されていることを知っても、
場所[n+4]の型について何も意味のあることはわかりません。
Cにおいては、ポインタ算術は型安全性破壊の悪名高き温床です。 *)
(* ################################### *)
(* ** Stores *)
(** ** 記憶(Stores) *)
(* Recall that, in the small-step operational semantics for
IMP, the step relation needed to carry along an auxiliary state in
addition to the program being executed. In the same way, once we
have added reference cells to the STLC, our step relation must
carry along a store to keep track of the contents of reference
cells.
We could re-use the same functional representation we used for
states in IMP, but for carrying out the proofs in this chapter it
is actually more convenient to represent a store simply as a
_list_ of values. (The reason we couldn't use this representation
before is that, in IMP, a program could modify any location at any
time, so states had to be ready to map _any_ variable to a value.
However, in the STLC with references, the only way to create a
reference cell is with [tm_ref t1], which puts the value of [t1]
in a new reference cell and evaluates to the location of the newly
created reference cell. When evaluating such an expression, we can
just add a new reference cell to the end of the list representing
the store.) *)
(** IMPのスモールステップ操作的意味論では、ステップ関係は、
実行されるプログラムに加えて補助的な状態を持ちまわる必要があったことを思い出して下さい。
同様に、STLCに参照セルを追加すると、
参照セルの内容を追跡するために記憶を持ち回る必要が出てきます。
IMPの状態で使ったのと同じ関数表現を再利用することもできます。
しかし、この章での証明をするためには、記憶を単に値の「リスト」として表現した方が実際は便利です。
(この表現を以前には使えなかった理由は、
IMPでは、プログラムはいつでも任意の場所を変更することができるので、
状態はいつでも任意の変数を値に写像することができるようになっていないといけなかったからです。
しかし、STLCに参照を追加したものでは、参照セルを作る唯一の方法は [tm_ref t1] を使うことです。
ここで [tm_ref t1] は[t1]の値を新しい参照セルに置き、
評価結果として新しく生成された参照セルの場所を返します。
この式を評価するときには、記憶を表現するリストの最後に新しい参照セルを追加すればよいのです。)
*)
Definition store := list tm.
(* We use [store_lookup n st] to retrieve the value of the reference
cell at location [n] in the store [st]. Note that we must give a
default value to [nth] in case we try looking up an index which is
too large. (In fact, we will never actually do this, but proving
it will of course require some work!) *)
(** 記憶[st]の場所[n]のセルの値をとりだすために、
[store_lookup n st] を使います。
インデックスが大きすぎるときには[nth]にデフォルト値を与えなければならないことに注意します。
(実際には大きすぎるインデックスを与えることは行いません。
ただ、そうであることを証明することはもちろんちょっと作業をしなければなりません!。) *)
Definition store_lookup (n:nat) (st:store) :=
nth n st tm_unit.
(* To add a new reference cell to the store, we use [snoc]. *)
(** 記憶に新しい参照セルを追加するために、[snoc]を使います。 *)
Fixpoint snoc {A:Type} (l:list A) (x:A) : list A :=
match l with
| nil => x :: nil
| h :: t => h :: snoc t x
end.
(* We will need some boring lemmas about [snoc]. The proofs are
routine inductions. *)
(** [snoc]についていくつか退屈な補題が必要です。証明は決まりきった帰納法です。 *)
Lemma length_snoc : forall A (l:list A) x,
length (snoc l x) = S (length l).
Proof.
induction l; intros; [ auto | simpl; rewrite IHl; auto ]. Qed.
(* The "solve by inversion" tactic is explained in Stlc.v. *)
(* "solve by inversion" タクティックは Stlc_J.v で説明されています。 *)
Lemma nth_lt_snoc : forall A (l:list A) x d n,
n < length l ->
nth n l d = nth n (snoc l x) d.
Proof.
induction l as [|a l']; intros; try solve by inversion.
Case "l = a :: l'".
destruct n; auto.
simpl. apply IHl'.
simpl in H. apply lt_S_n in H. assumption.
Qed.
Lemma nth_eq_snoc : forall A (l:list A) x d,
nth (length l) (snoc l x) d = x.
Proof.
induction l; intros; [ auto | simpl; rewrite IHl; auto ].