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。

(未完待续)