(define-data)
之前设计了 (define-data) 之后:
(define-data (my-list-t E)
(nil)
(li (head E) (tail (my-list-t E))))
;; nil
;; nil?
;; ll
;; li?
;; li-head
;; li-put-head!
;; li-tail
;; li-put-tail!我一直想不通只带有单个 data constructor 的情况应该如何处理。
(define-data (pair-t A B)
(cons-pair (first A) (second B)))
;; cons-pair?
;; cons-pair-first
;; cons-pair-second
;; cons-pair-put-first!
;; cons-pair-put-second!
(define make-pair cons-pair)
(define pair? cons-pair?)
(define pair-first cons-pair-first)
(define pair-second cons-pair-second)
(define pair-put-first! cons-pair-put-first!)
(define pair-put-second! cons-pair-put-second!)这显然太啰嗦了。
(define-interface)
为此我才想到用作为 record type 的 interface 来实现 pair-t:
(define-interface (pair-t A B)
:first A
:second B)
(define (make-pair first second)
{:first first :second second})
(define (pair-first pair) (:first pair))
(define (pair-second pair) (:second pair))
(define (pair-put-first first pair) (update pair :first first))
(define (pair-put-second second pair) (update pair :second second))
(define (pair-put-first! first pair) (update! pair :first first))
(define (pair-put-second! second pair) (update! pair :second second))其实这还是啰嗦,但是对于 record type, 可以用一般的 accessor 和 modifier 语法。
(define-record-type)
其实 scheme 经典的 (define-record-type) 就是解决方案。 但是只不过 (define-record-type) 是对动态型语言定义的, 我没马上看到增加类型的方案。
(define-record-type) 的结构是:
(define-record-type <type-name>
(<constructor-name> <field-name> ...)
<predicate-name>
(<field-name> <accessor-name> <modifier-name>)
...)比如:
(define-record-type point-t
(make-point x y)
point?
(x point-x point-put-x!)
(y point-y point-put-y!))看似复杂,其实 <field-name> 是不暴露给用户的, 类似 parameter,但是这里的设计使得它的作用也类似 entity id, 使得后续可以定义对应的 field 的 accessor 和 modifier。
其实增加类型的方案很简单,只要给 constructor 加上类型就好了。 和 (define-data) 中 data constructor 的 typed field 定义一样。
(define-record-type point-t
(make-point (x float-t) (y float-t))
point?
(x point-x point-put-x!)
(y point-y point-put-y!))比如上面的 pair-t 的定义问题:
(define-record-type (pair-t A B)
(make-pair (first A) (second B))
pair?
(first pair-first pair-put-first!)
(second pair-second pair-put-second!))(define-algebraic-type)
这样看来,显然在 (define-data) 之外,
(define-data (my-list-t E)
(nil)
(li (head E) (tail (my-list-t E))))应该有一个类似 (define-record-type) 的, 所生成的名字更 explicit 的语法:
(define-algebraic-type (my-list-t E)
((nil)
nil?)
((li (head E) (tail (my-list-t E)))
li?
(head li-head li-put-head!)
(tail li-tail li-put-tail!)))显然可以省略 predicate、accessor 和 modifier,来使用默认的生成名字的方式。 并且把 (define-data) 作为 (define-algebraic-type) 的 sugar。
keyword argument
关于 keyword argument 的语法设计, 也可以跳出 :key value 语法的设计思路, 模仿 (define-record-type)。
(define-keyword (<function-name> (<parameter> <default-arg>) ...) ...)
(call/keyword <function-name> (<keyword> <value>) ...)在 define-keyword 中 <parameter> 起到 keyword 的作用, parameter 名字是暴露给用户的 API 的一部分。
但是还需要让 (->) 支持 keyword。 假设有 arrow/keyword 语法关键词,缩写为 ->/kw。 模仿 (let) 的语法:
(->/kw ((depth int-t)
(width int-t)
(bg string-t))
window-t)这也许启示我们应该用 : 做 module prefix 的分隔符, 把 / 保留为可用的 symbol。
(define-record-type) is not acceptable
尝试使用了一下 (define-record-type)。
如果要把:
(define-interface source-position-t
:index int-t
:row int-t
:column int-t)写成:
(define-record-type source-position-t
(make-source-position
(index int-t)
(row int-t)
(column int-t))
source-position?
(index source-position-index)
(row source-position-row)
(column source-position-column))或者把:
(define-interface mod-t
:name symbol-t
:stmts (list-t stmt-t)
:admitted (set-t symbol-t)
:definitions (hash-t symbol-t definition-t)
:claimed (hash-t symbol-t claimed-entry-t)
:inferred-types (hash-t symbol-t value-t)
:data-constructors (hash-t symbol-t data-constructor-t)
:project project-t
:is-error-module bool-t)写成:
(define-record-type mod-t
(cons-mod
(name symbol-t)
(stmts (list-t stmt-t))
(admitted (set-t symbol-t))
(definitions (hash-t symbol-t definition-t))
(claimed (hash-t symbol-t claimed-entry-t))
(inferred-types (hash-t symbol-t value-t))
(data-constructors (hash-t symbol-t data-constructor-t))
(project project-t)
(is-error-module bool-t))
mod?
(name mod-name)
(stmts mod-stmts)
(admitted mod-admitted)
(definitions mod-definitions)
(claimed mod-claimed)
(inferred-types mod-inferred-types)
(data-constructors mod-data-constructors)
(project mod-project)
(is-error-module mod-is-error-module))还是有点接受不了。 因为开始设计 meta-lisp 的动机之一, 就是没法接受在 typescript 中定义 algebraic data type 的时候, 一个名字要出现多次,而现在一个名字又出现了三次。
一种方案是用:
(define-interface mod-t
(name symbol-t)
(stmts (list-t stmt-t))
(admitted (set-t symbol-t))
(definitions (hash-t symbol-t definition-t))
(claimed (hash-t symbol-t claimed-entry-t))
(inferred-types (hash-t symbol-t value-t))
(data-constructors (hash-t symbol-t data-constructor-t))
(project project-t)
(is-error-module bool-t))然后根据 type 的名字,去掉 -t 后缀, 来生成对应的 define-record-type。
这样也许就不能叫 define-interface 了, 因为如此定义的 type 之间没有子类关系。
方案 A:
define-record-type改名为define-product-type, 避免与 python 的 dict 和 javascript 的 object 混淆。define-struct作为define-product-type的 sugar, 避免一个名字重复多次。
方案 B:
- 在
define-record-type之外增加define-algebraic-type, 二者都是 sugar 版本。 - 另外设置
define-detailed-algebraic-type和define-detailed-record-type作为 desugar 的版本。 detailed 版本甚至可以完全不要缩写,使用(constructor)(predicate)和(fields)来标记。 - 另外有
define-type-alias,用来做简单的 alias。
方案 C:
define-algebraic-type模仿带有类型信息的define-record-type。define-struct和define-enumdesugar 到define-algebraic-type。
最终选择方案 C。
(define-struct)
与 (define-enum) 不同的是,(define-struct) 只有一个 constructor。 不是根据 constructor 的名字,而是根据 <type-name> 的名字去掉后缀 -t, 来生成 <predicate-name> <accessor-name> <modifier-name>。
比如:
(define-struct point-t
(x float-t)
(y float-t))展开为:
(define-algebraic-type point-t
(make-point
(x float-t)
(y float-t))
point?
(x point-x point-put-x!)
(y point-y point-put-y!))又比如:
(define-struct (pair-t A B)
(first A)
(second B))展开为:
(define-algebraic-type (pair-t A B)
(make-pair
(first A)
(second B))
pair?
(first pair-first pair-put-first!)
(second point-second point-put-second!))给 (define-struct) 一个变体 (define-struct*):
(define-struct*)-- 用户提供 constructor-name。(define-struct)-- 根据 type-name 生成 constructor-name -- 用 make- 前缀。