Как переопределить конструктор структуры, в то же время предоставляя метаданные структуры для времени компиляции в Racket? - PullRequest
3 голосов
/ 20 марта 2019

Это может показаться похожим на вопросы типа Перегрузка конструктора структуры? или Перегрузка конструктора структуры . Но ни один из этих вопросов не решает проблему передачи перегруженного идентификатора за пределы модуля (путем его предоставления).

Например, допустим, у меня есть структура, я хочу перегрузить конструктор:

(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: идентификатор не связан с информацией о типе структуры
по адресу: рыба
в: (структурированная рыба)

Вопрос

Итак, как мы можем изменить конструктор структуры, но при этом разрешить ее предоставление и использование в качестве родителя в других структурах?

1 Ответ

3 голосов
/ 20 марта 2019

Используя структуру метаданных, которая является просто структурой, определенной во время компиляции, вы можете инкапсулировать определение структуры во время компиляции в значение, которое можно использовать для 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, благодаря пиару Алекса Кнаута, set!-transformer-procedure больше не понадобится, и вам нужно будет просто позвонить make-variable-like-transformer.

...