-
Notifications
You must be signed in to change notification settings - Fork 16
/
Copy pathGoldfishLang.tmu
6263 lines (3549 loc) · 142 KB
/
GoldfishLang.tmu
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
<TMU|<tuple|1.1.0|2025.0.5>>
<style|<tuple|book|goldfish|literate|reduced-margins|python|padded-paragraphs|chinese>>
<\body>
<\hide-preamble>
<assign|r7rs|<flag|R7RS|dark cyan>>
<assign|srfi|<flag|SRFI|dark red>>
<assign|font|math=Latin Modern Math,cjk=Noto CJK SC,CMU>
<assign|typehint|<macro|body|<goldfish-lang|<arg|body>>>>
</hide-preamble>
<chapter|(liii lang)>
<section|许可证>
<\goldfish-chunk|goldfish/liii/lang.scm|false|true>
;
; Copyright (C) 2024 The Goldfish Scheme Authors
;
; Licensed under the Apache License, Version 2.0 (the "License");
; you may not use this file except in compliance with the License.
; You may obtain a copy of the License at
;
; http://www.apache.org/licenses/LICENSE-2.0
;
; Unless required by applicable law or agreed to in writing, software
; distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
; WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
; License for the specific language governing permissions and limitations
; under the License.
;
\;
</goldfish-chunk>
<\goldfish-chunk|tests/goldfish/liii/lang-test.scm|false|true>
;
; Copyright (C) 2024 The Goldfish Scheme Authors
;
; Licensed under the Apache License, Version 2.0 (the "License");
; you may not use this file except in compliance with the License.
; You may obtain a copy of the License at
;
; http://www.apache.org/licenses/LICENSE-2.0
;
; Unless required by applicable law or agreed to in writing, software
; distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
; WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
; License for the specific language governing permissions and limitations
; under the License.
;
\;
</goldfish-chunk>
<section|接口>
<\scm-chunk|goldfish/liii/lang.scm|true|true>
(define-library (liii lang)
(import (liii base) (liii string) (liii vector) (liii sort)
\ \ \ \ \ \ \ \ (liii list) (liii hash-table) (liii bitwise))
(export
\ \ @ typed-define
\ \ define-case-class case-class? == != chained-define display* object-\<gtr\>string
\ \ option none
\ \ rich-integer rich-float rich-char rich-string
\ \ rich-list rich-vector array rich-hash-table
\ \ box $
)
(begin
\;
</scm-chunk>
<section|测试>
<\scm-chunk|tests/goldfish/liii/lang-test.scm|true|true>
(import (liii check)
\ \ \ \ \ \ \ \ (liii lang)
\ \ \ \ \ \ \ \ (liii cut)
\ \ \ \ \ \ \ \ (liii case))
\;
(check-set-mode! 'report-failed)
\;
</scm-chunk>
<section|部分应用>
<paragraph*|@>
宏<scm|@>提供类似Scala语法的部分应用(Partial Application)功能,详情参考SFRI 26中宏<scm|cute>的说明。
宏<scm|@>的关键在于<strong|宏展开时的静态求值>,而非运行时的动态绑定。简短的来说,<scm|@>只在宏展开时求值并绑定占位符,而不是每次被调用时重新求值。
\;
<strong|通过示例和机制分步解释>
<\goldfish-code>
(let ((a 10))
\ \ (define add10 (@ + a _)) \ ; a 在宏展开时被求值为 10
\ \ (set! a 100)
\ \ (add10 5)) ; =\<gtr\> 15(而不是 105)
</goldfish-code>
<\enumerate>
<item><strong|宏展开阶段>:
当 <scm|(@ + a _)> 被展开时,宏会立即对非占位符参数(此处为 <scm|a>)进行求值。此时 <scm|a> 的值为 <scm|10>,所以宏生成的代码类似:
<\goldfish-code>
(define add10 (lambda (x) (+ 10 x))) ; a 被替换为固定值 10
</goldfish-code>
占位符 <scm|_> 对应后续传入的参数。
<item><strong|运行时阶段>:
即使后续通过 <scm|(set! a 100)> 修改了 <code*|<scm|a>>,<scm|add10> 内部已经固定为 <scm|10>,因此 <scm|(add10 5)> 等价于 <scm|(+ 10 5)>,结果为 <scm|15>。
</enumerate>
\;
<strong|与普通闭包的区别>
<\itemize>
若不用 <scm|@>,直接使用 <scm|lambda>:
<\goldfish-code>
(let ((a 10))
\ \ (define add10 (lambda (x) (+ a x))) ; 闭包捕获变量 a 的引用
\ \ (set! a 100)
\ \ (add10 5)) ; 结果为 105
</goldfish-code>
<strong|闭包行为>:<scm|lambda> 捕获的是变量 <scm|a> 的引用,而非其当前值。因此 <scm|a><verbatim|> 的修改会影响闭包的结果。
</itemize>
\;
<strong|宏 <scm|@> 的核心机制>
<\enumerate>
<item><strong|静态求值>:
在宏展开阶段,所有非占位符参数(如 <code*|<scm|a>>)会被立即求值,结果直接嵌入生成的代码中。这类似于 <strong|“值捕获”>。
<item><strong|占位符 <scm|_>>:
表示参数位置由后续调用时传入的值填充,类似 Scala 的 <scm|_> 占位符。例如 <scm|(@ f _ b _)> 会生成一个接受两个参数的函数。
<item><strong|与 <scm|cute> 宏的区别>:
<scm|cute> 宏支持更复杂的模式(如 <scm|\<...\>> 表示未定参数序列),而 <scm|@> 仅支持固定位置的 <scm|_>,且不延迟求值。
</enumerate>
\;
<strong|设计意义>
<\itemize>
<item><strong|性能优化>:若部分参数是常量或昂贵的计算,<scm|@> 可避免重复求值。
<item><strong|语义确定性>:部分应用的结果在定义时即固定,不受后续环境变化影响。
</itemize>
\;
<strong|实现函数>
<\goldfish-chunk|goldfish/liii/lang.scm|true|true>
(define-macro (@ . paras)
\ \ (letrec*
\ \ \ \ (
\ \ \ \ \ \ (slot? (lambda (x) (equal? '_ x)))
\ \ \ \ \ \ (exprs (filter (lambda (x) (not (slot? x))) paras))
\ \ \ \ \ \ (slots (filter slot? paras))
\;
\ \ \ \ \ \ (exprs-sym-list (map (lambda (x) (gensym)) exprs)) \
\ \ \ \ \ \ (slots-sym-list (map (lambda (x) (gensym)) slots))
\;
\ \ \ \ \ \ (lets (map list exprs-sym-list exprs))
\;
\ \ \ \ \ \ (parse
\ \ \ \ \ \ \ \ (lambda (exprs-sym-list slots-sym-list paras)
\ \ \ \ \ \ \ \ \ \ (cond
\ \ \ \ \ \ \ \ \ \ \ \ ((null? paras) paras)
\ \ \ \ \ \ \ \ \ \ \ \ ((not (list? paras)) paras)
\ \ \ \ \ \ \ \ \ \ \ \ ((slot? (car paras))\
\ \ \ \ \ \ \ \ \ \ \ \ \ \ `(,(car slots-sym-list)\
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ ,@(parse exprs-sym-list (cdr slots-sym-list) (cdr paras))))
\ \ \ \ \ \ \ \ \ \ \ \ (else\
\ \ \ \ \ \ \ \ \ \ \ \ \ \ `(,(car exprs-sym-list)\
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ ,@(parse (cdr exprs-sym-list) slots-sym-list (cdr paras))))))))
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \
\ \ `(let ,lets\
\ \ \ \ \ \ \ \ (lambda ,slots-sym-list\
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ ,(parse exprs-sym-list slots-sym-list paras)))))
\;
</goldfish-chunk>
<strong|测试用例>
测试:基本部分应用
<\goldfish-chunk|tests/goldfish/liii/lang-test.scm|true|true>
(check ((@ + _ 2) 1) =\<gtr\> 3)
(check ((@ list 1 _ 3 _ 5) 2 4) =\<gtr\> (list 1 2 3 4 5))
(check ((@ list _ _) 'a 'b) =\<gtr\> (list 'a 'b))
\;
</goldfish-chunk>
测试:只宏展开时求值并绑定占位符
<\goldfish-chunk|tests/goldfish/liii/lang-test.scm|true|true>
(check
\ \ (let ((a 10))
\ \ \ \ (define add (@ + (* a 2) _)) \
\ \ \ \ (set! a 100)
\ \ \ \ (add 5))
=\<gtr\> 25)
\;
(let ((x 5))
\ \ (check\
\ \ \ \ ((@ cons (+ x 1) _) 'y)\
\ \ \ =\<gtr\> (cons 6 'y)))
\;
</goldfish-chunk>
测试:无占位符时生成的函数无参数
<\goldfish-chunk|tests/goldfish/liii/lang-test.scm|true|true>
(check (procedure? (@ list 1 2)) =\<gtr\> #t)
(check ((@ list 1 2)) =\<gtr\> '(1 2))
\;
</goldfish-chunk>
测试:函数作为占位符参数
<\goldfish-chunk|tests/goldfish/liii/lang-test.scm|true|true>
(check ((@ _ 'a 'b) list) =\<gtr\> (list 'a 'b))
(check ((@ map _ '(1 2 3)) (lambda (x) (+ x 1))) =\<gtr\> '(2 3 4))
(check ((@ apply _ '(1 2 3)) +) =\<gtr\> 6)
\;
</goldfish-chunk>
测试:嵌套<scm|@>宏
<\goldfish-chunk|tests/goldfish/liii/lang-test.scm|true|true>
(check ((@ (@ + _ 1) _) 2) =\<gtr\> 3)
(check ((@ _ _) (@ * _ 2) 3) =\<gtr\> 6)
\;
</goldfish-chunk>
<section|typed-define>
<paragraph|typed-define>
<\goldfish-chunk|goldfish/liii/lang.scm|true|true>
(define-macro (typed-define name-and-params body . rest)
\ \ (let* ((name (car name-and-params))
\ \ \ \ \ \ \ \ \ \ (params (cdr name-and-params))
\ \ \ \ \ \ \ \ \ \ (param-names (map car params)))
\;
\ \ \ \ \ \ \ \ `(define*\
\ \ \ \ \ \ \ \ \ \ \ \ (,name\
\ \ \ \ \ \ \ \ \ \ \ \ ,@(map \
\ \ \ \ \ \ \ \ \ \ \ \ \ \ (lambda (param)
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (let \ ((param-name (car param))
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (type-pred (cadr param))
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (default-value (cddr param)))
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (if (null? default-value)
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ param-name
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ `(,param-name ,(car default-value)))))
\ \ \ \ \ \ \ \ \ \ \ \ \ \ params))
\;
\ \ \ \ \ \ \ \ ;; Runtime type check \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \
\ \ \ \ \ \ \ \ ,@(map (lambda (param)
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (let* ((param-name (car param))
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (type-pred (cadr param))
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ ;;remove the '?' in 'type?'
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (type-name-str\
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (let ((s (symbol-\<gtr\>string type-pred)))
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (if (and (positive? (string-length s))
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (char=? (string-ref s (- (string-length s) 1)) #\\?))
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (substring s 0 (- (string-length s) 1))
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ s))))
\;
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ `(unless\
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (,type-pred ,param-name)
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (type-error\
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (format #f "In funtion #\<less\>~a ~a\<gtr\>: argument *~a* must be *~a*! \ \ \ **Got ~a**"
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ ,name
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ ',param-names
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ ',param-name
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ ,type-name-str
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (object-\<gtr\>string ,param-name))))))
\ \ \ \ \ \ \ \ \ \ \ \ \ \ params)
\ \ \ \ \ \ \ ,body
\ \ \ \ \ \ \ ,@rest)))
\;
</goldfish-chunk>
<\goldfish-chunk|tests/goldfish/liii/lang-test.scm|true|true>
(typed-define (person (name string? "Bob") (age integer?))
\ \ (string-append name " is " (number-\<gtr\>string age) " years old"))
\;
(check (person :age 21) =\<gtr\> "Bob is 21 years old")
(check (person :name "Alice" :age 25) =\<gtr\> "Alice is 25 years old")
(check-catch 'type-error (person :name 123 :age 25))
\;
</goldfish-chunk>
<section|样本类>
<paragraph|define-case-class><index|define-case-class>
<scm|define-case-class>用于在Goldfish Scheme定义类似Scala的样本类。
样本类对象实际上是函数,该函数的第一个参数可以是字段或者是方法,字段统一使用<scm|'field>表示,方法统一使用<scm|:method>表示。实例方法采用<scm|%>作为前缀,静态方法采用<scm|@>作为前缀,内部方法没有<scm|@>和<scm|%>前缀。比如<scm|(bob :to-string)>实际上调用的是bob对象的实例方法<scm|%to-string>。静态方法只能通过样本类名调用,例如<scm|(person :default)>。实例方法只能通过该样本类构造的实例调用,例如 <scm|(bob :to-string)>。内部方法只能在定义实例方法时调用。例如
<\scm-code>
(define-case-class my-vector ((vec vector?) (size integer?))
\ \ (define (@empty) (my-vector #() 0))
\;
\ \ (define (check-bound n)
\ \ \ \ (when (or (\<less\> n 0) (\<gtr\>= n size))
\ \ \ \ \ \ (value-error "out of bound")))
\;
\ \ (define (%at n)
\ \ \ \ (check-bound n)
\ \ \ \ (vector-ref vec n)))
\;
(let1 vec (my-vector #(1 2 3) 3)
\ \ (check (my-vector :empty) =\<gtr\> (my-vector #() 0))
\ \ (check (vec :at 0) =\<gtr\> 1)
\ \ (check-catch 'value-error (vec :at 3)))
</scm-code>
在此例中,<scm|@empty> 是一个静态方法,<scm|%at> 是一个实例方法,<scm|check-bound> 是一个内部方法。
实例方法将第一个参数之外剩下的参数作为参数传入,我们通过只处理部分参数,递归处理未处理参数的方式,可以实现方法调用链。比如
<\goldfish-code>
(l :filter positive? :filter zero?)
=\<gtr\> ((l :filter positve?) :filter zero?)
</goldfish-code>
在这个filter方法的实现中,<scm|%filter>这个实例方法只处理第一个参数,得到的结果仍旧是一个样本类对象,再使用得到的样本类对象处理剩余的参数。
<scm|define-case-class>会自动生成两种实例方法。
其中一种是固定命名的实例方法:<scm|%equals>、<scm|%is-instance-of>、<scm|%to-string>和<scm|%apply>。其中<scm|%apply>是一个特殊方法,如果第一个参数没有命中字段或者方法,那么<scm|(person 1 2 3)>实际等价于<scm|(person :apply 1 2 3)>。
另外一种是将字段名用做实例方法名,表示拷贝一个对象,并修改相应的字段。在下面的示例中,<scm|bob_2024>这个对象的实例方法接受一个整数作为输入,返回的是新的对象<scm|bob_2025>,这两个对象是完全不同的对象,他们的区别仅仅在于age字段的值。
<\scm-code>
(define person ((name string?) (age integer?)))
(define bob_2024 (person "Bob" 24))
(define bob_2025 (bob_2024 :age (+ 1 (bob_2024 'age))))
</scm-code>
样本类的默认构造函数有类型校验,但不做任何值校验。如果需要做值校验,那么需要使用同名的函数覆盖<scm|define-case-class>生成的默认实现。该同名函数可称为该样本类的伴生函数。
<\goldfish-chunk|goldfish/liii/lang.scm|true|true>
(define-macro (define-case-class class-name fields . methods)
\ \ (let* ((key-fields
\ \ \ \ \ \ \ \ \ (map (lambda (field) (string-\<gtr\>symbol (string-append ":" (symbol-\<gtr\>string (car field)))))
\ \ \ \ \ \ \ \ \ \ \ \ \ \ fields))
\ \ \ \ \ \ \ \ \ (instance-methods
\ \ \ \ \ \ \ \ \ \ (filter (lambda (method) (string-starts? (symbol-\<gtr\>string (caadr method)) "%"))
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ methods))
\ \ \ \ \ \ \ \ \ (instance-method-symbols (map caadr instance-methods))
\ \ \ \ \ \ \ \ \ (instance-messages
\ \ \ \ \ \ \ \ \ \ (map (lambda (method)
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (let1 name (string-remove-prefix (symbol-\<gtr\>string method) "%")
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (string-\<gtr\>symbol (string-append ":" name))))
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ instance-method-symbols))
\ \ \ \ \ \ \ \ \ (static-methods
\ \ \ \ \ \ \ \ \ \ (filter (lambda (method) (string-starts? (symbol-\<gtr\>string (caadr method)) "@"))
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ methods))
\ \ \ \ \ \ \ \ \ (static-method-symbols (map caadr static-methods))
\ \ \ \ \ \ \ \ \ (static-messages
\ \ \ \ \ \ \ \ \ \ (map (lambda (method)
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (let1 name (string-remove-prefix (symbol-\<gtr\>string method) "@")
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (string-\<gtr\>symbol (string-append ":" name))))
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ static-method-symbols))
\ \ \ \ \ \ \ \ \ (internal-methods
\ \ \ \ \ \ \ \ \ \ \ (filter (lambda (method) (not (or (string-starts? (symbol-\<gtr\>string (caadr method)) "%")
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (string-starts? (symbol-\<gtr\>string (caadr method)) "@"))))
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ methods))
\ \ \ \ \ \ \ \ \ (this-symbol (gensym))
\ \ \ \ \ \ \ \ \ (f-make-case-class (string-\<gtr\>symbol (string-append "make-case-class-" (symbol-\<gtr\>string class-name)))))
\;
`(define (,class-name msg . args)
\;
(define (@is-type-of obj)
\ \ (and (case-class? obj)
\ \ \ \ \ \ \ (obj :is-instance-of ',class-name)))
\ \ \
,@static-methods
\;
(define (is-normal-function? msg)
\ \ (and \ (symbol? msg)\
\ \ \ \ \ \ \ \ (char=? (string-ref (symbol-\<gtr\>string msg) 0) #\\:)))
\;
(define (static-dispatcher msg . args)
\ \ \ \ (cond
\ \ \ \ \ ((eq? msg :is-type-of) (apply @is-type-of args))
\ \ \ \ \ ,@(map (lambda (method expected) `((eq? msg ,expected) (apply ,method args)))
\ \ \ \ \ \ \ \ \ \ \ \ static-method-symbols static-messages)
\ \ \ \ \ (else (value-error "No such static method " msg))))
\;
(typed-define (,f-make-case-class ,@fields)
\ \ (define ,this-symbol #f)
\ \ (define (%this . xs)
\ \ \ \ (if (null? xs)
\ \ \ \ \ \ ,this-symbol
\ \ \ \ \ \ (apply ,this-symbol xs)))
\;
\ \ (define (%is-instance-of x)
\ \ \ \ (eq? x ',class-name))
\ \ \ \ \ \ \ \ \
\ \ (typed-define (%equals (that case-class?))
\ \ \ \ (and (that :is-instance-of ',class-name)
\ \ \ \ \ \ \ \ \ ,@(map (lambda (field) `(equal? ,(car field) (that ',(car field))))
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ fields)))
\ \ \ \ \ \ \ \ \
\ \ (define (%apply . args)
\ \ \ \ (cond ((null? args)
\ \ \ \ \ \ \ \ \ \ \ (value-error ,class-name "Apply on zero args is not implemented"))
\ \ \ \ \ \ \ \ \ \ ((equal? ((symbol-\<gtr\>string (car args)) 0) #\\:)
\ \ \ \ \ \ \ \ \ \ \ (value-error ,class-name "No such method: " (car args)))
\ \ \ \ \ \ \ \ \ \ (else (value-error ,class-name "No such field: " (car args)))))
\ \ \ \ \ \ \ \ \
\ \ (define (%to-string)
\ \ \ \ (let ((field-strings
\ \ \ \ \ \ \ \ \ \ \ (list ,@(map (lambda (field key-field)
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ `(string-append
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ ,(symbol-\<gtr\>string key-field) " "
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (object-\<gtr\>string ,(car field))))
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ fields key-fields))))
\ \ \ \ \ \ (let loop ((strings field-strings)
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (acc ""))
\ \ \ \ \ \ \ \ (if (null? strings)
\ \ \ \ \ \ \ \ \ \ \ \ (string-append "(" ,(symbol-\<gtr\>string class-name) " " acc ")")
\ \ \ \ \ \ \ \ \ \ \ \ (loop (cdr strings)
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (if (zero? (string-length acc))
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (car strings)
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (string-append acc " " (car strings))))))))
\;
\ \ ,@internal-methods
\ \ ,@instance-methods
\
\ \ (define (instance-dispatcher)
\ \ \ \ (lambda (msg . args)
\ \ \ \ \ \ (cond
\ \ \ \ \ \ \ \ ((eq? msg :is-instance-of) (apply %is-instance-of args))
\ \ \ \ \ \ \ \ ((eq? msg :equals) (apply %equals args))
\ \ \ \ \ \ \ \ ((eq? msg :to-string) (%to-string))
\ \ \ \ \ \ \ \ ((eq? msg :this) (apply %this args))
\ \ \ \ \ \ \ \ ,@(map (lambda (field key-field)
\ \ \ \ \ \ \ \ \ \ \ \ `((eq? msg ,key-field)
\ \ \ \ \ \ \ \ \ \ \ \ \ \ (,class-name
\ \ \ \ \ \ \ \ \ \ \ \ \ \ ,@(map (lambda (f) (if (eq? (car f) (car field)) '(car args) (car f)))
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ fields))))
\ \ \ \ \ \ \ \ \ \ fields key-fields)
\ \ \ \ \ \ \ \ ((is-normal-function? msg)
\ \ \ \ \ \ \ \ \ \ (cond
\ \ \ \ \ \ \ \ \ \ \ \ \ \ ,@(map (lambda (method expected) `((eq? msg ,expected) (apply ,method args)))
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ instance-method-symbols instance-messages)
\ \ \ \ \ \ \ \ \ \ \ \ \ \ (else (value-error ,class-name "No such method: " msg))))
\ \ \ \ \ \ \ \
\ \ \ \ \ \ \ \ ,@(map (lambda (field) `((eq? msg ',(car field)) ,(car field))) fields)
\ \ \ \ \ \ \ \ (else (apply %apply (cons msg args))))))
\;
\ \ (set! ,this-symbol (instance-dispatcher))
\ \ ,this-symbol
) ; end of the internal typed define
\;
(if (in? msg (list ,@static-messages :is-type-of))
\ \ \ \ (apply static-dispatcher (cons msg args))
\ \ \ \ (apply ,f-make-case-class (cons msg args)))
\;
) ; end of define
) ; end of let
) ; end of define-macro
\;
</goldfish-chunk>
测试:不带用户自定义方法的样本类person
<\goldfish-chunk|tests/goldfish/liii/lang-test.scm|true|true>
(define-case-class person
\ \ ((name string? "Bob")
\ \ \ (age integer?)))
\;
(let1 bob (person :name "Bob" :age 21)
\ \ (check (bob 'name) =\<gtr\> "Bob")
\ \ (check (bob 'age) =\<gtr\> 21)
\ \ (check ((bob :name "hello") 'name) =\<gtr\> "hello")
\ \ (check-catch 'value-error (bob 'sex))
\ \ (check-catch 'value-error (bob :sex))
\ \ (check-true (bob :is-instance-of 'person))
\ \ (check (bob :to-string) =\<gtr\> "(person :name \\"Bob\\" :age 21)"))
\;
(check-catch 'type-error (person 1 21))
\;
(let ((bob (person "Bob" 21))
\ \ \ \ \ \ (get-name (lambda (x)
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (case* x
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ ((#\<less\>procedure?\<gtr\>) (x 'name))
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (else (value-error))))))
\ \ (check (get-name bob) =\<gtr\> "Bob")
\ \ (check-catch 'value-error (get-name 1)))
\;
</goldfish-chunk>
测试:带用户自定义方法的样本类jerson
<\goldfish-chunk|tests/goldfish/liii/lang-test.scm|true|true>
(define-case-class jerson
\ \ ((name string?)
\ \ \ (age integer?))
\ \
\ \ (define (%to-string)
\ \ \ \ (string-append "I am " name " " (number-\<gtr\>string age) " years old!"))
\ \ (define (%greet x)
\ \ \ \ (string-append "Hi " x ", " (%to-string)))
)
\;
(let1 bob (jerson "Bob" 21)
\ \ (check (bob :to-string) =\<gtr\> "I am Bob 21 years old!")
\ \ (check (bob :greet "Alice") =\<gtr\> "Hi Alice, I am Bob 21 years old!"))
\;
</goldfish-chunk>
测试:只有%开头的才是实例方法,其它的不应该被识别。
<\goldfish-chunk|tests/goldfish/liii/lang-test.scm|true|true>
(define-case-class test-case-class
\ \ ((name string?))
\ \
\ \ (define (@this-is-a-static-method)
\ \ \ \ (test-case-class "static"))
\ \
\ \ (define (%this-is-a-instance-method)
\ \ \ \ (test-case-class (string-append name "instance")))
)
\;
(let1 hello (test-case-class "hello ")
\ \ (check-catch 'value-error (hello :this-is-a-static-method))
\ \ (check (test-case-class :this-is-a-static-method) =\<gtr\> (test-case-class "static")))
\;
</goldfish-chunk>
测试:使用 %this 引用实例本身
<\goldfish-chunk|tests/goldfish/liii/lang-test.scm|true|true>
(let ()
\ \ (define-case-class person ((name string?) (country string?))
\ \ \ \ (define (@default)
\ \ \ \ \ \ (person "Andy" "China"))
\ \ \ \ (define (%set-country! c . xs)
\ \ \ \ \ \ (set! country c)
\ \ \ \ \ \ (apply (%this) (if (null? xs) '(:this) xs)))
\ \ \ \ (define (%set-name! n . xs)
\ \ \ \ \ \ (set! name n)
\ \ \ \ \ \ (apply (%this) (if (null? xs) '(:this) xs)))
\ \ \ \ (define (%to-string)
\ \ \ \ \ \ (format #f "Hello ~a from ~a" name country)))
\ \ (define Andy (person :default))
\ \ (check-catch 'wrong-type-arg (person :this))
\ \ (check (Andy :to-string) =\<gtr\> "Hello Andy from China")
\ \ (check (Andy :set-country! "USA" :to-string) =\<gtr\> "Hello Andy from USA")
\ \ (check (Andy :to-string) =\<gtr\> "Hello Andy from USA")
\ \ (check (Andy :set-country! "China" :set-name! "Ancker-0" :to-string) =\<gtr\> "Hello Ancker-0 from China")
\ \ (check (Andy :set-country! "China") =\<gtr\> (person "Ancker-0" "China"))
\ \ (check (Andy :this :set-country! "USA" :this :set-name! "Andy" :this :to-string) =\<gtr\> "Hello Andy from USA"))
\;
</goldfish-chunk>
测试:内部方法
<\goldfish-chunk|tests/goldfish/liii/lang-test.scm|true|true>
(let ()
\ \ (define-case-class person ((name string?) (country string?))
\ \ \ \ (chained-define (@default)
\ \ \ \ \ \ (person "Andy" "China"))
\ \ \ \ (chained-define (set-country! c)
\ \ \ \ \ \ (set! country c)
\ \ \ \ \ \ (%this))
\ \ \ \ (chained-define (set-name! n)
\ \ \ \ \ \ (set! name n)
\ \ \ \ \ \ (%this))
\ \ \ \ (chained-define (%set-both! n c)
\ \ \ \ \ \ (set-country! c)
\ \ \ \ \ \ (set-name! n)
\ \ \ \ \ \ (%this))
\ \ \ \ (chained-define (%to-string)
\ \ \ \ \ \ (rich-string (format #f "Hello ~a from ~a" name country))))
\ \ (check (person :default :to-string :get) =\<gtr\> "Hello Andy from China")
\ \ (check (person :default :set-both! "Bob" "Russia" :to-string :get) =\<gtr\> "Hello Bob from Russia")
\ \ (check-catch 'value-error (person :default :set-country! "French")))
\;
</goldfish-chunk>
<paragraph|case-class?>
case class的前两个方法必须是<scm|:is-instance-of>和<scm|:equals>。
<\goldfish-chunk|goldfish/liii/lang.scm|true|true>
(define (case-class? x)
\ \ (and-let* ((is-proc? (procedure? x))
\ \ \ \ \ \ \ \ \ \ \ \ \ (source (procedure-source x))
\ \ \ \ \ \ \ \ \ \ \ \ \ (source-at-least-3? (and (list? source) (\<gtr\>= (length source) 3)))
\ \ \ \ \ \ \ \ \ \ \ \ \ (body (source 2))
\ \ \ \ \ \ \ \ \ \ \ \ \ (body-at-least-3? (and (list? body) (\<gtr\>= (length body) 3)))