How to implement generic method implementation separate from struct definition

I'm wondering if it is possible to do the following:

Define a struct in 1 file:

---[ data.rkt ]---

(struct person
  (name age))

Define an interface in a separate file:

---[ greetable.rkt ]---

(define-generics greetable
  (greet greetable))

And in a 3rd file provide the implementation of the greetable interface for the person struct:

---[ person-greetable.rkt ]---

(require 
  "data.rkt"
  "greetable.rkt")

; Somehow provide the implementation maybe using something like: 
; (make-generic-struct-type-property)

Hi, @rvdalen.

I haven't used generics at all, so please don't consider my reply an "answer", it is as much for my own edification as anything.

Is this sort of what you have in mind?

#lang racket

(module data racket
  (provide (struct-out person))
  
  (struct person [name age]
    #:transparent))

(module iface racket
  (require racket/generic)

  (provide greet gen:greetable)
  
  (define-generics greetable
    (greet greetable)))

(require 'data
         'iface
         racket/generic)

(define prop:greets
  (make-generic-struct-type-property
   gen:greetable
   (define (greet person)
     (format "Hi there, ~a!" (person-name person)))))

(struct person* person []
  #:transparent
  #:property
  prop:greets #false)
  
(greet (person* "john" 31))

Yes something like that, but without the derived person* struct, the generic interface must be implemented for the person struct:)

Struct cannot be changed in another module. But you may use external generics. For example, from gls package.

#lang racket

(module data racket
  (provide (struct-out person))
  
  (struct person [name age]
    #:transparent))

(module iface racket
  (require gls)

  (provide greet)
  
  (defgeneric greet))

(require 'data
         'iface
         gls)

(add-method greet
  (method ([person person?]) (format "Hi there, ~a!" (person-name person))))
  
(greet (person "john" 31))
1 Like

Ah, thanks so much for mentioning GLS. I will look into GLS. It seems as you mentioned there is no built in way to do this with base Racket.

Apologies for resurrecting this thread, but this just struck me while encountering exactly this question in my own code: What about a parameter?

Obviously, it depends on why you (were) trying to do this, but if it is merely a behavioral thing, then maybe this could work. Interesting, nonetheless.

#lang racket

(require racket/generic)
  
(define-generics greetable
  (greet greetable))

(define greetable-param (make-parameter #false))

(define prop:greets
  (make-generic-struct-type-property
   gen:greetable
   (define (greet person)
     ({greetable-param} person))))

(struct person [name age] #:transparent
  #:property prop:greets #false)

(struct persona-non-grata person [])

(define jil (person            "Jil" 32))
(define bob (persona-non-grata "Bob" 28))

(parameterize ([greetable-param
                (lambda (person)
                  (format "Hi there, ~a!" (person-name person)))])
  (list (greet bob)
        (greet jil)))

(parameterize ([greetable-param
                (lambda (person)
                  (if (persona-non-grata? person)
                      (format "Go away, ~a!"  (person-name person))
                      (format "Hi there, ~a!" (person-name person))))])
  (list (greet bob)
        (greet jil)))

;=> '("Hi there, Bob!" "Hi there, Jil!")
;=> '("Go away, Bob!" "Hi there, Jil!")

Edit:
So, it won't work on person, but it will work on its children with that property.

Kind of stupid I now realize. Sorry.


Edit:
But anyways, this approach solves my problem, although I was too hasty in posting this.

:slight_smile: Ha ha, no worries. It is good to try stuff. Thanks for responding though. Even if something doesn't work for me we can still learn from your example.

1 Like