Typed Racket中对可能为空的列表使用apply的问题
无类型Racket中的正常运行代码
以下代码在无类型Racket中可以正常执行,生成包含三个不同颜色正方形的pict对象:
(apply hc-append (for/list ([colour '("red" "green" "blue")]) (colorize (filled-rectangle 10 10) colour)))
Typed Racket中的初次尝试与错误
在Typed Racket中,for/list必须添加类型注解,但直接添加注解后仍会触发类型检查错误:
(apply hc-append (for/list : (Listof pict) ([colour '("red" "green" "blue")]) (colorize (filled-rectangle 10 10) colour)))
类型检查器报错:
; […] Type Checker: Bad arguments to function in `apply': ; Domains: pict pict * ; Real pict pict * ; Arguments: (Listof pict)
原因很明确:(Listof pict)类型的列表可能为空,而hc-append的类型签名要求至少传入一个pict参数(即pict pict *,表示一个pict加任意多个pict)。
map版本的可行方案
以下代码在无类型和Typed Racket中都能正常运行:
(apply hc-append (map (curry colorize (filled-rectangle 10 10)) '("red" "green" "blue")))
这是因为map作用于非空的输入列表时,返回值的类型是(Pairof pict (Listof pict))——明确为非空列表,完全符合hc-append对参数的类型要求。但这个非空列表类型无法直接作为for/list的类型注解,类型检查器会直接拒绝。
尝试拆分类型处理仍失败
尝试通过cond分支区分空列表和非空列表,并给非空分支的列表指定(Pairof pict (Listof pict))类型,仍然报错:
(let ([potentially-empty-list (for/list : (Listof pict) ([c '("red" "green" "blue")]) (colorize (filled-rectangle 10 10) c))]) (cond [(null? potentially-empty-list) (blank)] [else (apply hc-append #{(list (first potentially-empty-list) (rest potentially-empty-list)) : (Pairof pict (Listof pict))})]))
错误信息如下:
; […] Type Checker: Bad arguments to function in `apply': ; Domains: pict pict * ; Real pict pict * ; Arguments: (List pict (Listof pict))
核心困惑点
- 明明指定了
(Pairof pict (Listof pict))类型,类型检查器却将其识别为(List pict (Listof pict)),两者结构看似相似,但实际类型语义完全不同。 map返回的(Pairof pict (Listof pict))列表可以正常用apply hc-append,为何同样类型注解的列表却不行?
问题根源与解决方法
类型语义差异
(Pairof pict (Listof pict))表示的是一个pict元素后跟任意数量pict元素组成的非空列表(即cons结构:(cons pict (cons pict ... null))),而(list (first lst) (rest lst))生成的是包含两个元素的列表:第一个元素是pict,第二个元素是(Listof pict)类型的列表,这和hc-append需要的参数类型完全不匹配——hc-append需要的是多个pict,而不是一个pict加一个pict列表。
正确的处理方式
在else分支中,不需要重新构造列表,直接给原列表添加非空类型注解即可,因为此时已经通过null?判定列表非空:
(let ([potentially-empty-list (for/list : (Listof pict) ([c '("red" "green" "blue")]) (colorize (filled-rectangle 10 10) c))]) (cond [(null? potentially-empty-list) (blank)] [else (apply hc-append (ann potentially-empty-list (Pairof pict (Listof pict))))]))
或者直接使用cons拼接first和rest,生成正确的非空列表类型:
(let ([potentially-empty-list (for/list : (Listof pict) ([c '("red" "green" "blue")]) (colorize (filled-rectangle 10 10) c))]) (cond [(null? potentially-empty-list) (blank)] [else (apply hc-append (cons (first potentially-empty-list) (rest potentially-empty-list)))]))
这两种写法都能通过Typed Racket的类型检查,正确生成预期的pict对象。
内容的提问来源于stack exchange,提问作者Aleksandar Dimitrov

