在 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-case
、quasisyntax
以及宏的许多其他变体和 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!"))))))))
现在看看代码量,它并没有真正变得容易得多,所以我会对初始版本感到满意,并且如果相似行的数量进一步增加,我可能会开始考虑替代方案。现在这两条线可能会在未来的版本中发生变化并且有所不同,并且可能会失去努力。它发生的次数比我的预测对我未来的帮助要多,但我也喜欢保持干燥。
我已经使用 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-case
、quasisyntax
以及宏的许多其他变体和 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!"))))))))
现在看看代码量,它并没有真正变得容易得多,所以我会对初始版本感到满意,并且如果相似行的数量进一步增加,我可能会开始考虑替代方案。现在这两条线可能会在未来的版本中发生变化并且有所不同,并且可能会失去努力。它发生的次数比我的预测对我未来的帮助要多,但我也喜欢保持干燥。