Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overloading a struct constructor?

Tags:

racket

My question is the same as the one asked here, but the answer given there doesn't actually work.

I have a bunch of structs inheriting from a parent struct A, which has two fields that I want to make optional for all its descendants. Previously I was using #:auto, but that turns out to really not be what I want because it breaks methods like struct-copy, and also I do want to be able to optionally supply values for those fields on struct creation.

I've found a few other questions about optional arguments for structs, but the answers have all suggested defining a custom constructor and using that instead. Unfortunately I already have a lot of code using the regular constructors, so what I really want is for this custom constructor to have the same name as the struct itself. The answer to the question I linked was what I was looking for, but unfortunately it only works in the REPL because there duplicate definitions are allowed. Outside of the REPL I get an error like module: duplicate definition for identifier in: exn:my-app, for example (when running the answer to the linked question).

edit: I know the duplicate definition problem is because the struct id is also bound to a transformer. I don't want to stop that definition from happening; I want a name bound to both a constructor and a transformer for that struct, where the constructor is not the default one.

Is there a way to do this that will play nicely with existing code?

like image 578
Anonymous Avatar asked Mar 12 '23 20:03

Anonymous


2 Answers

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.

like image 106
Anonymous Avatar answered Apr 30 '23 10:04

Anonymous


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"
like image 45
soegaard Avatar answered Apr 30 '23 09:04

soegaard