在 Scheme 中生成条件(测试表达式...)

Generating cond's (test expression ...) in Scheme

我已经使用 Guile 大约一年了,但在 Scheme 中使用宏方面还很缺乏经验。虽然我有一些更复杂的例子可以令人满意地工作,但我仍然坚持(对我来说)感觉像一个非常简单的用例,类似于简单替换,类似于用 C 中的 #define 可以实现的。

我有一个函数使用 cond 来测试几个条件,其中一些具有一般形式。例如:

(define (file->list filename)
  "Read input and split into list of 
   coordinates and folds."
  (let ((lst (call-with-input-file filename
           (λ (p)
         (list-ec (:port line p read-line)
              (cond
               ((string-any (cut eqv? <> #\,) line)
                (string-split line #\,))
               ((string-null? line) #f) ;; blank line
               ((string= line "fold along x=" 1 13 1 13)
                `(x ,(last (string-split line #\=))))
               ((string= line "fold along y=" 1 13 1 13)
                `(y ,(last (string-split line #\=))))
               (else (error "bad input!"))))))))
    (parse-input lst)))

我想摆脱围绕以下表格条件的重复:

((string= line "fold along x=" 1 13 1 13)
`(x ,(last (string-split line #\=))))

我觉得这就像一个宏,因为这个样板文件可以在编译时使用模式匹配生成——我曾天真地尝试过这样的事情:

(define-syntax-rule (fold-parse str x-or-y)
   `((string= ,str
         ,(string-append "fold along " (symbol->string x-or-y) "=")
         1 13 1 13)
      (x-or-y ,(string->number (last (string-split str #\=))))))

这确实在 REPL 中重现了(测试表达式)s 表达式:

scheme@(guile-user)> (fold-parse "fold along x=3" 'x)
 = ((string= "fold along x=3" "fold along x=" 1 13 1 13) ((quote x) 3))
scheme@(guile-user)> 

但是当我尝试将宏插入我的 cond 时,出现以下错误:

;;; WARNING: compilation of /home/foo/dev/aoc_2021/13/./13.scm failed:
;;; Syntax error:
;;; /home/foo/dev/aoc_2021/13/./13.scm:53:28: source expression failed to match any pattern in form fold-parse
ice-9/psyntax.scm:2794:12: In procedure syntax-violation:
Syntax error:
unknown location: source expression failed to match any pattern in form fold-parse

我天真地添加了它,就像下面一样 - 我已经在 cond 中注释掉了围绕“fold along x=”样板的样板,它意味着要替换:

(define (file->list filename)
  "Read input and split into list of 
   coordinates and folds."
  (let ((lst (call-with-input-file filename
           (λ (p)
         (list-ec (:port line p read-line)
              (cond
               ((string-any (cut eqv? <> #\,) line)
                (string-split line #\,))
               ((string-null? line) #f) ;; blank line
               (fold-parse line 'x)
               ;;((string= line "fold along x=" 1 13 1 13)
               ;; `(x ,(last (string-split line #\=))))
               ((string= line "fold along y=" 1 13 1 13)
                `(y ,(last (string-split line #\=))))
               (else (error "bad input!"))))))))
    (parse-input lst)))

自从这次尝试以来,我一直在探索 syntax-casequasisyntax 以及宏的许多其他变体和 cond 来尝试制作它有效。

但是,对于宏可以“直接替换”片段或表达式的一部分的方式,我显然没有得到一些根本上重要的东西。

谁能帮我看看我方法的错误?

如何编写可以生成要在 cond 子句中使用的测试和表达式的宏?而且 - 这是 reasonable/sensible 要做的事吗?

cond 将在您的宏之前展开。于是。

(cond 
  ...
  (fold-parse line 'x)
  ...)

首先会变成:

(if ...
    (if fold-parse
        (begin line 'x)
        ...)

因此您可能会遇到未绑定变量错误或者猪会飞。无论如何,cond 的工作原理是,如果一个条件项只有一个测试,那么真实值将是 cond 的结果,因此你可以这样做:

(define (handle-fold line var)
  (let ((str (string-append "fold along " (symbol->string var) "=")))
    (and (string= line str 1 13 1 13)
         (list var (last (string-split line #\=))))))

在你身上 cond:

(cond
  ((string-any (cut eqv? <> #\,) line)
   (string-split line #\,))
  ((string-null? line) #f) ;; blank line
  ((handle-fold line 'x))  ;; NB: The truthy return is the result
  ((handle-fold line 'y))  ;; NB: The truthy return is the result
  (else (error "bad input!"))))))))

现在看看代码量,它并没有真正变得容易得多,所以我会对初始版本感到满意,并且如果相似行的数量进一步增加,我可能会开始考虑替代方案。现在这两条线可能会在未来的版本中发生变化并且有所不同,并且可能会失去努力。它发生的次数比我的预测对我未来的帮助要多,但我也喜欢保持干燥。