Overloading a struct constructor?

二次信任 提交于 2019-12-01 18:40:33

Looking further into the new struct keywords that soegaard mentioned, I think I've come up with a solution that's a bit cleaner and, more importantly, much easier to abstract into a macro (those modules and requires were giving me a really hard time). I thought I'd share it for future reference. Again, this requires one of the nightly Racket builds. I'm using 6.5.0.7.

(Note: the use of case-lambda over defining keyword arguments here is just my preference.)

#lang racket

(require (for-syntax syntax/transformer))

(struct horse (color)
  #:name Horse
  #:constructor-name Horse
  #:transparent)

(define make-horse
  (case-lambda
    [() (Horse "black")]
    [(color) (Horse color)]))

(define-match-expander horse
  ; match expander
  (λ (pat)
    (syntax-case pat ()
      [(_ more ...) #'(Horse more ...)]))

  ; constructor
  ; edit: changing this to use make-variable-like-transformer,
  ;  as suggested in the comments.
  #;(syntax-id-rules ()
      [(_ args ...) (make-horse args ...)]
      [horse Horse])
  (make-variable-like-transformer #'make-horse))

Sample usage:

> (define black-beauty (horse))
> black-beauty
(horse "black")

> (define ginger (horse "red"))
> ginger
(horse "red")

> (match black-beauty
    [(horse color) color])
"black"

> (match ginger
    [(horse color) color])
"red"

> (horse-color black-beauty)
"black"

> (horse-color ginger)
"red"

In short: instantiation, matching, and accessing fields seem to work as you would expect if you'd just used struct. The struct id can also be used as an identifier without any syntax issues. I don't really think that part has much practical use, but I thought it was nice.

The struct construct will define two entities with the name of the struct. A constructor and a transformer binding that has information on the struct.

In order to avoid the "duplicate identifier" error you can use #:omit-define-syntaxes.

An alternative is to define the struct in a submodule and export only the things you need (and possibly renaming some of the identifiers).

#lang racket
(struct horse (color)
  #:constructor-name make-horse
  #:omit-define-syntaxes
  #:transparent)

(define (horse #:color [color "black"])
  (make-horse color))

(horse)
(horse #:color "red")

Output:

(horse "black")
(horse "red")

EDIT

A solution that works with match is possible with the help of a match expander. Note: You need at least version 6.5 for this to work due the the use of #:extra-name.

#lang racket

(module horse racket
  (provide (struct-out Horse)
           make-horse)
  (struct horse (color)
    #:extra-name Horse
    #:extra-constructor-name make-horse
    #:transparent))

(require 'horse)

; the custom horse constructor
(define (my-make-horse #:color [color "black"])
  (make-horse color))

(define-match-expander horse
  ; match expander
  (λ (pat)
    (syntax-case pat ()
      [(_horse more ...)
       #'(Horse more ...)]))
  ; constructor
  (λ (stx)
    (syntax-case stx ()
      [(_horse arg ...)
       (syntax/loc stx
         (my-make-horse arg ...))])))

(horse)
(horse #:color "red")

(match (horse #:color "blue")
  [(horse color) color])

Output:

(horse "black")
(horse "red")
"blue"
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!