在Typed Racket的 for*/list中,表达式本应只产生一个值,但实际产生的值数量不确定

编程语言 2026-07-10

我一直在尝试用Typed Racket写一个可扩展的词法分析器(lexer):它把一个字符串(String)逐字符地发送给一组函数,函数按顺序返回#f或 Indexed-String;如果某个函数决定不返回,则返回#f;否则返回Indexed-String,由 define-type 收集。关键在于 r,它是每个函数在循环中产生并更新的值。当它是#f时,就不会被收集(我认为这不是最好的做法,但我想不出更好的办法)

我可以通过在函数列表中简单地添加函数来扩展这个词法分析器的功能。

这是代码:

#lang typed/racket

(define-type Indexed-String (Pairof Index String))

(: lex (-> String
           (Listof (-> Char Index (Option Indexed-String)))
           (Listof Indexed-String)))
(define (lex text lf)
  (for*/list ([(c i) (in-indexed text)]
              [f lf]
              [r (in-value (f c (cast i Index)))]
              #:when (not (false? r)))
    r))

但类型检查器(毫不意外)给我报错:

Type Checker: Expression should produce 1 values, but produces an unknown number of values in: (for*/list (((c i) (in-indexed text)) (f lf) (r (in-value (f c (cast i Index)))) #:when (not (false? r))) r)

经过两个小时我什么也没想到,这也是我再次在Stack Overflow上问这个我又爱又恨的语言的原因:P

解决方案

要修复这个问题,你只需要在 for*/list 形式中包含一个返回类型:

(define (lex text lf)
  (for*/list : (Listof Indexed-String)
    ([(c i) (in-indexed text)]
     [f lf]
     [r (in-value (f c (cast i Index)))]
     #:when (not (false? r)))
    r))

其他改进:in-value 可以被 #:do 替代,(not (false? r)) 也可以直接用 r 代替。此外,优先使用带有恰当谓词的 assert,而不是 cast;后者会用契约包装值,可能像在有类型/无类型代码之间调用一样影响性能:

(define (lex text lf)
  (for*/list : (Listof Indexed-String)
    ([(c i) (in-indexed text)]
     [f lf]
     #:do [(define r (f c (assert i index?)))]
     #:when r)
    r))

有很多诱惑,会在Typed Racket中对索引使用 Index,但我发现这往往很麻烦——对 Index 的任何数学运算都会得到 Integer,例如,会产生大量需要重新强制类型的额外 assert。我发现的最佳实践基本上是让你自己的函数做几乎和标准字符串函数一样的事——返回 Index 值,但接受 Integer 参数。用这种方式对抗类型系统的冲突会少得多。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章