0

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

我有一个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)
$33 = ((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-case尝试使其工作。quasisyntaxcond

但是,我显然没有得到关于宏可以“插入替换”片段或表达式的一部分来代替它们的方式的根本重要的东西。

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

如何编写一个可以生成要在 - 子句中使用的测试和表达式的宏cond?而且 - 这是一件合理/明智的事情吗?

4

1 回答 1

2

cond在您的宏之前展开。因此。

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

首先会变成:

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

因此,您可能会遇到未绑定的变量错误,或者猪会飞。无论如何,cond如果 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!"))))))))

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

于 2021-12-31T19:18:30.893 回答