首页 > 解决方案 > 如何在 Racket 中为编译时提供结构元数据的同时覆盖结构构造函数?

问题描述

这似乎类似于重载结构构造函数之类的问题?重载结构构造函数。但是这些问题都没有解决将重载标识符传递出模块边界的问题(通过提供它)。

例如,假设我有一个要重载构造函数的结构:

(struct fish (weight scales))
(define (make-fish [weight 5] [scales 'blue])
  (fish weight scales))

现在我想提供新的构造函数,使其具有结构的名称,以使其使用完全透明:

(provide
  (except-out (struct-out fish) fish)
  (rename-out (make-fish fish)))

这将在大多数情况下起作用。但是可能会出现一些细微的小错误。

继承结构不再可能,也不能使用match

(require animals/fish)

(struct shark fish (teeth)) ;; ERROR: parent struct type not defined

(define (describe-animal animal)
  (match animal
    [(fish weight scales) ;; ERROR: syntax error in pattern
     (format "A ~a pounds fish with ~a scales" weight scales)]
    [_ "Not a fish"]))

失败:使用匹配扩展器

创建匹配扩展器(链接问题中接受的解决方案)。
它不起作用,因为您无法将匹配扩展器导出为结构。

#lang racket/base

(require
  (for-syntax
    racket/base
    syntax/transformer)
  racket/match)

(provide
  (except-out (struct-out fish) fish)
  (rename-out (make-fish fish)))

(struct fish (weight scales)
  #:name private-fish
  #:constructor-name private-fish)

(define (make-fish [weight 5] [scales 'blue])
  (private-fish weight scales))

(define-match-expander fish
  (lambda (stx)
    (syntax-case stx ()
      [(_ field ...) #'(private-fish field ...)]))
  (make-variable-like-transformer #'private-fish))

你得到错误:

struct-out: 标识符未绑定到结构类型信息
在:fish
in: (struct-out fish)

问题

那么我们如何改变一个结构的构造函数,但仍然允许它被提供并用作其他结构的父级呢?

标签: structracket

解决方案


使用元数据结构,它只是在编译时定义的结构,您可以在编译时将结构定义封装成可用于match和继承的值。

#lang racket/base

(require
  (for-syntax
    racket/base
    racket/struct-info
    syntax/transformer)
  racket/match)

(provide
  (struct-out fish))

(struct fish (weight scales)
  #:name private-fish
  #:constructor-name private-fish)

(define (make-fish [weight 5] [scales 'blue])
  (private-fish weight scales))

(begin-for-syntax
  ;; we define a struct that will only exist at compile time
  ;; and can encapsulate an identifier
  (struct metadata (ctor struct-info)
    #:property prop:procedure (struct-field-index ctor)
    #:property prop:struct-info (lambda (self) (metadata-struct-info self))))

(define-syntax fish ;; this variable can be used like the initial struct when compiling
  (metadata
    (set!-transformer-procedure
      (make-variable-like-transformer #'make-fish))
    (extract-struct-info (syntax-local-value #'private-fish))))

该结构必须具有特定的属性:prop:procedure,以便它仍然可以作为构造函数工作,并且prop:struct-info,以便match并且struct可以在编译时获取结构信息。

笔记

请注意,在下一个版本的 Racket 中,感谢 Alex Knauth 的 PR,set!-transformer-procedure将不再需要,您只需调用make-variable-like-transformer.


推荐阅读