美文网首页
次元超越! Macro in Racket

次元超越! Macro in Racket

作者: 卷毛宿敌大小姐 | 来源:发表于2020-05-02 11:50 被阅读0次

    Intro

    最近写代码感觉非常的不顺。 之前看自己一个月以来写的代码真的是被自己恶心到了。 感觉我的Racket 还是没有能够掌握主要特性。 所以大概就花了一周左右的时间把Macro拿出来学了一下, 今年的PL课上很可惜没有时间在课上讲这个。


    如果说编程语言是魔咒学的话, 这个Macro 绝对是次元魔法, 特别是在Racket语言当中。 简单的说, Macro就是能让你在正式编译之前的,先对你的代码进行一次预编译, 在这个时候把你代码中的一些符合条件的规则,全部替换成你需要的代码,或者说去生成你想要的代码。 之前写过C语言的宏的话应该还是对这个东西不陌生的,但是在大部分的语言中,宏只能做一些非常Ad-hoc的替换,如果你的替换方式非常的复杂, 这时候就会很难写,也很容易出Bug。但是在Racket之类的Scheme系语言当中,宏系统基本上就是能让你直接在编译期运行你写的scheme/racket代码。得益于这种强大的能力你可以轻松进入高次元,在那里直接对racket这个编程语言本身进行扩展得到你想要的语法特性。Racket 是 Racket的meta language

    交换

    在编程中我们经常会需要去交换两个变量的值。这个在命令式语言中非常的简单, 可以直接用/

    temp = a;
    a = b;
    b = tmp;
    

    在racket当中我们也不难能够写出下面的code

    (let ([tmp x])
      (set! x y)
      (set! y tmp))
    

    虽然racket作为函数式语言这种命令式编程语言中的“变量”,其实在racket中非常的少用。
    写过汇编的都知道其实汇编里面描述交换的时候可以直接使用XCHG。这个语义其实非常的好, 我们希望在我们的编程语言中能够实现这一种语法结构。根据metacircular interpretor 的思想,如果我们把racket当作racket本身的meta language, 我们需要做的就是把xchg这个语法“翻译”到上面的这这段racket code。
    这种替换非常的直接, 我们可以用define-syntax-rule来直接在我们的代码中给racket添加一条语法规则

    (define-syntax-rule (xchg x y)
      (let ([tmp x])
        (set! x y)
        (set! y tmp)))
    

    当然你如果把上面的define-syntax-rule改成define代码的运行结果不会发生任何的改变。
    那和函数有什么区别?
    其实这里编译器就相当于在编译值之前先把你所有代码中的xchg 修改成了(let ...)你的代码本身发生的变化,而如果定义函数的方式来实现,这里实际上就是一个函数调用,会新建一个函数栈在运行时,但是你的代码一旦写好了,自然是不会被编译器修改的。
    这里很明显的感觉就是使用s 表达式的优势, 因为我们的代码相当于已经天然的被parse好了,所以很容易看清语法的边界, 这种代码和数据(list)的同构性,让写宏的人会更加清晰。

    使用racket编写语法谓词

    如果我们要使用racket来写一个简单的解释器,第一步需要做的就是定义目标语言的predicate, 这样我们就可以配合quasi quoting和pattern match来比较轻松的定义语言的语法了,比如简单的lambda的语法如下

    lambda-exp :: x
                |  (lambda-exp lambda-exp)
                |  (lamdba  (x ...) lambda-exp)
    
    where x is symbol
    

    使用racket predicate来描述

    (define (lambda-exp? e)
      (match e
        [(? symbol?) #t]
        [`(lambda (,x ...) ,(? lambda-exp?)) #t]
        [`(,(? lambda-exp? e1) ,(? lambda-exp?)) #t]))
    

    不过感觉这一学期下来给我的感受就是,其实大部分人对于quasi quoting pattern match的阅读能力几乎是0........很多人到了期末都没彻底搞明白。
    这个其实是一种在racket当中比较常见的做法但是确实会带来一些的阅读困难,同时这种语法结构其实并不是那么容易去扩展的, 在简单单一的一组EBNF规则中其实是看不太出这个。但是如果我希望做到类似与extend一组规则的时候这种结构其实就会显的比较乱了。 如果我们是要写一个编译器,在常见的教程和方法中都会要求有非常多级的IR,特别是如果我们使用nano-pass style, 就需要非常对级不同的IR,每级只是对中间的某一个小部分进行修改,这时候纯使用predicate就会有点难受了。

    这时候我们可以使用宏,在racket中定义一个语法结构叫define-ir , 我们可以编写类似EBNF的代码,然后macro expand之后我们就可以自动得到我们想要的predicate

    使用宏生成语法谓词

    1. syntax

    这个其实还是比较复杂的,可以一点一点来, 先看一种比较简单的情况:
    我们的宏会take 一个list,最终在编译之后把list上的所有终结符(terminate symbol) 全部替换成quasi pattern,也就是说比如
    '(symbol symbol)在 expand 之后会变成 `(,(? symbol?) ,(? symbol?))

    对于宏我们的本质就是把代码结构就像list一样进行演算。尽管我们的s表达式非常的像list,但是很明显'(symbol symbol)只是一个list data, 如何在racket表示一段代码'(symbol symbol)呢?
    其实和quote有点像我们可以使用syntax quote,#'关键字
    #'(symbol symbol)
    这样表示的就是一段syntax对象而不是一个list对象了。
    我们甚至还可以使用quasi syntax, 这样我们就可以在一个syntax object中间使用#,来混入racket代码了
    (define x #'c)
    #`(a b #,x)

    我们也可以用racket的内置函数synatx->datum \ datum->syntax来完成这种data 和syntax的互相转化

    > (require racket/syntax)
    > (syntax->datum #'symbol?)
    'symbol?
    > (syntax->datum #'(a b c))
    '(a b c)
    > (datum->syntax (current-syntax-context) 'x)
    #<syntax x>
    

    <to be continue>


    一些代码

    相关文章

      网友评论

          本文标题:次元超越! Macro in Racket

          本文链接:https://www.haomeiwen.com/subject/bywhghtx.html