如何扩展Racket的阅读器以获得更好的路径处理?

时间:2014-06-28 01:58:24

标签: racket

我一直致力于将我用Python编写的静态网站生成器转换为Racket。这主要是为了更好地了解Racket的学习练习。我现在已经使用了Racket版本,但有一件事我更喜欢Python版本:pathlib.Path对象。生成器有很多路径处理,在Python代码中看起来像这样:

render_template(root / "templates" / "index.jinja")

而Racket代码看起来更像是这样:

(render-template (build-path (root) "templates" "index.jinja"))

我发现在Python代码中能够更清晰地读取路径,因此我想修改Racket阅读器以支持以下内容:

(render-template (root) / "templates" / "index.jinja")

与读者一起玩,我想出了如何让这种表达起作用:

(render-template  / foo / templates / index.jinja /)

我对语法没问题,但我无法弄清楚如何将路径元素评估为Racket表达式而不仅仅是字符串。即使我确实想到了这一点,我仍然有一个问题,即天真的字符串处理会导致类似的问题:

(render-template / (root) / (string-join 
                             (list "templates" "subdir") "/") 
                 / "index.jinja" /)

那么,关于我在这里可以做什么的任何建议/意见? :)

1 个答案:

答案 0 :(得分:3)

如果我理解正确,原来你有这样的事情:

(define (root)
  "/")

(define (render-template path)
  (displayln path))

(render-template (build-path (root) "templates" "index.jinja"))
;; => /templates/index.jinja

我建议只是改变render-template以便它可能 用多个路径调用"部件" - 它处理呼叫 build-path为你:

(define (render-template . path-parts)
  (define path (apply build-path path-parts))
  (displayln path))

现在你可以这样称呼它:

(render-template (root) "templates" "index.jinja")
;; => /templates/index.jinja

顺便说一下,以原始方式调用它仍然有效,因为 在这种情况下,build-path将充当身份:

(render-template (build-path (root) "templates" "index.jinja"))
;; => /templates/index.jinja

我认为这是最" Rackety"办法。一件好事 s表达式是你不必输入"分隔符"例如,/。 空间就足够了。而且我认为您阅读和编写的球拍代码越多, 你越有这种感觉。

当然,也许最安全的东西就是制作能力 你自己的小(或大)语言。如果你想要一个" DSL"来写 主要由路径组成的文件,可能是一回事。但在 在这种情况下,我不确定我是否看到了大胜。


如果有的话,也许你只想要一个宏 这使/在此上下文中充当空格。即到 make /表示"没有"它最终需要在扩展代码中。

例如:

#lang racket/base

(require (for-syntax racket/base syntax/parse))

(define-syntax (render-template stx)
  (define-splicing-syntax-class pp
    (pattern (~seq part (~optional (~literal /)))))
  (syntax-parse stx
    [(_ p:pp ...) #'(do-render-template p.part ...)]))

(define (do-render-template . path-parts)
  (define path (apply build-path path-parts))
  (displayln path))

(render-template (root) / "templates" / "index.jinja")
;; => /templates/index.jinja
(render-template (root) "templates" "index.jinja")
;; => /templates/index.jinja

请注意,此处/是完全可选的。他们被视为 空格。

另请注意,实际工作是在函数中完成的,现在重命名为do-render-template。宏只是一个包装器。通常情况下,宏必须尽可能少地完成工作,并且可以成为函数的函数。

但是,再一次,我个人不会为这个宏而烦恼,我会采用上面建议的方法。


更新:作为p.s.,如果我理解正确,Python PathLib.Path/定义为运算符?那么,Racket并没有真正拥有"运营商"。它有功能。像/这样的数学函数接受任意数量的参数。因此,我们写10 / 5 / 2代替(/ 10 5 2)。实际上,我们将整圈带回build-path:一个可以接收任意数量路径部分的函数。

我想您可以有效地将build-path重命名为/

(require (rename-in (except-in racket /)
                    [build-path /]))
(/ (root) "templates" "index.jinja")

但这并不是真正的运算符重载,因为这些是普通函数而不是方法。并且......我不会这样做。 :)