Racket School 2019: The “How to Design Languages” Track
这个内容跟Programming Languages写的有点类似。
1 Language-Oriented Programming
解释了什么是LOP。个人简单的理解,其他语言之间需要通过FFI/ABI这种偏底层的接口才能互操作。缺点是无法进行复杂的互操作,因为在这个级别,源代码中很多信息都已经被抛弃了,链接器能够做的事情非常有限。
Racket所谓Language Oritented Programming,指的是Racket能将其他语言进行源代码到源代码的编译,将其转化成Racket源代码,最终可以在源代码级别对不同语言进行整合。
似乎跟LLVM的IR有点类似哦。但Racket支持功能强大的阔集,可以方便地自定义语言。
文中总结了几大好处:
- Composition is a mere syntactic act.
- Computation is accomplished via translation into the host.
- Communication is easy because embedded programs compute host values.
也就是避免了很多链接上的麻烦。可以方便地使用racket来为主流编程语言不愿涉及的特定领域设计语言。
2 Macro Expansion
通过一个例子介绍Racket的Macro语法。
3 Language Extensions via Macros
3.1 When and Why
汇编是低层次的语言。低层次不代表功能低,而是说抽象层度低。汇编中基本上只有对栈和程序计数器的抽象。虽然可以通过出入栈的方式来实现函数调用,但是这种抽象是不完整的,比如汇编在一个函数内部可以跳转到另一个完全不相干的指令地址,从而导致对函数的抽象遭到破坏。
C语言比汇编抽象层次高一些,提供了明确的函数的抽象,并且规定一个函数内的goto语句的目标只能是在函数内部,这样就保证了函数抽象的可靠性。抽象意味着限制,限制有好有坏。避免函数内的goto语句随意跳转在绝大部分情况下是一个好的抽象。这会让编译器在优化函数调用的时候可以无须担心跳转问题。
但是制定合适的抽象并不是件容易的事情,因为既要保证抽象的一致性,又要保证抽象在大部分情况下都是好的。
文中举的其他几个抽象的例子;
- The Async Callback Pattern
- Function Abstraction
很多有用的抽象只能通过语言扩展来实现。
学到一个词,notational logorrhea,辞藻繁复。
还是介绍了不少关于macro的基础知识,举了很多例子。
5 Advanced Racket Macros
5.1 Flawed Macros
学习syntax/parse的使用。如何检测语句和语义上的错误。
5.2 Macros Robust at Run-Time
如同我们所看到的,Racket阔集是完完全全的执行诀,可以执行任意运算。(只是这些执行诀接收的是语法标的,返回的也是语法标的)。
5.3 Macros Robust at Compile-Time
介绍了error, raise-syntax-error, #:fail-unless的区别
5.4 Classy Syntax
如何利用语句类别(syntax-class)来产生更细致的错误描述。
5.5 Analysis in Syntax Classes
不仅可以用语句类别来解析语句标的,还可以用其来重组语句标的。可以用#:declare
来声明语句的类型,也可以使用其:syntax-class
的缩略形式。一点区别,#:declare
所指定语句类型处于表达式情景,可以进行运算,可以指定合约,比如#:declare seq (expr/c #'list?)
。
expr/c在运行时调用一个判断函数来确保模式变量是所需的类型。但是expr/c不能改变模式变量的绑定,所以在随后的模板中,需要使用其提供的.c
属性,对于上面的例子,也就是seq.c
。
语法类别是通过属性将解析的结果返回其使用者的。可以通过attribute构造来获取。
5.6 Defining Syntax Classes
举例说明如何自定义语句类别。
5.7 Synthesis in Syntax Classes
如何定义语句类别的属性。
5.8 What we did not cover
未涉及的部分
- 如果有多个子模式的情况下,如何决定要暴露哪些语句类型的属性。define-syntax-class允许开一个属性列表,所有子模式都要提供此属性。
- 属性不仅可以绑定语句标的,还可以绑定其他任意数据类型。
- 语句类型中的模式可以不用括号括起来吗?可以,但要使用splicing syntax classes。
7 Lexical Scope, (Un)Hygienic Macros
如何编制一个类型模式,也就是面向对象语言(比如C++或者Java)中的类型定义。
7.1 An Example Class
用作示例的类型是posn,用来表示2D平面上的一个点。这个类型主要包含以下功能:
- 两点之间的距离
- 到原点的距离
- X轴平移
- Y轴调整
示例代码:
(posn #:x 0 #:y 0)
(posn #:y 4 #:x 2)
(posn #:x 3 #:y 5)
下面的语法有点不能理解:
(this . distance-from . o)
7.2 This and That
Racket默认的作用域规则(叫做hygiene)是为了让变量的作用域可以容易预测。 讲述了hygiene的重命名规则。
7.3 Introducing Names
如何打破阔集的hygiene规则:
(define-simple-macro (define-it e)
#:with the-new-name (datum->syntax #'e 'it)
(define the-new-name e))
上述例子中,使用datum->syntax在e的作用域绑定了一个新标识符it。
你也可以传入一部分名字:
(define-simple-macro (define-like base:id e)
#:with new-name
(datum->syntax
#'base
(string->symbol
(string-append
(symbol->string (syntax->datum #'base))
"-like")))
(define new-name e))
(define-like x 5)
(+ x-like x-like)
也可以使用format-id来简化上述代码:
(define-simple-macro (define-like base:id e)
#:with new-name (format-id #'base "~a-like" #'base)
(define new-name e))
7.4 Scoped New Names
上一章介绍如何打破higiene规则,但是着实不够优雅。更好的办法是采用语句动参(syntax parameter)。
直接上例子:
(define-syntax-parameter it
(λ (stx) (raise-syntax-error #f "Illegal outside with-it" stx)))
(define-simple-macro (with-it e body ...+)
(let ([this-it e])
(syntax-parameterize ([it (make-rename-transformer #'this-it)])
body ...)))
(with-it (λ (x) (add1 x))
(it 5))
7.5 Literal Identifiers
有一些标识符只是占个位置而已,例如下面的EOF
(define-syntax (with-ignored stx)
(syntax-parse stx
#:literals (EOF)
#:track-literals
[(_ before ... EOF after ...)
#'(let () before ...)]))
(with-ignored
(define x 6)
(+ x x)
EOF ; following clauses are discarded
x y z
(/ 1 0))
使用#:literals (EOF)
的形式需要定义一个EOF:
(define-syntax EOF
(λ (stx) (raise-syntax-error #f "Illegal outside with-ignored" stx)))
如果使用(~literal EOF)
的形式则不用。
7.6 The definition of classy
逐步给出classy的定义。
7.7 What we did not cover
全功能(带继承,上级方法调用等等)的class可以参考racket/class。
(未完待续)