# Compile-time dispatch based on 'type'

**URL:** <https://racket.discourse.group/t/compile-time-dispatch-based-on-type/3405>\
**Category:** Questions & Answers\
**Created:** [December 9, 2024, 11:43am UTC](https://racket.discourse.group/t/compile-time-dispatch-based-on-type/3405 "2024-12-09T11:43:25Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![Laurent.O](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/laurent.o/32/18_2.png) [@Laurent.O](https://racket.discourse.group/u/Laurent.O)\
**Post date:** [December 9, 2024, 11:43am UTC](https://racket.discourse.group/t/compile-time-dispatch-based-on-type/3405/1 "2024-12-09T11:43:25Z")

</div>

I'm trying to move a runtime dispatch to compile time for efficiency reasons.

In the runtime version, I have two 'types', `A` and `B`, and a procedure `ref-proc` that behaves differently depending on the 'type':

```scheme
#lang racket

(struct A (a))
(struct B (b))

(define (ref-proc x)
  (cond [(A? x) (A-a x)]
         [(B? x) (B-b x)]
         [else (error "not implemented")]))

;; Examples:
(let ([x (A 'aaa)])
  (ref-proc x)) ; 'aaa

(let ([x (B 'bbb)])
  (ref-proc x)) ; 'bbb

```

Easy enough. Now, in the compile-time version, I'm happy to annotate `x` somehow such that `ref` can use this information to know how to dispatch. Perhaps something that would look like this:

```scheme
(let ([x (A 'aaa)])
  (with-AorB [x A] ; annotate `x` being of 'type' `A`
    (ref x))) ; compile-time dispatch to `A-a`

(let ([x (B 'bbb)])
  (with-AorB [x B] ; annotate `x` being of 'type' `B`
    (ref x))) ; compile-time dispatch to `B-b`

```

I've tried to use `syntax-property`, but I don't know what I'm doing:

```scheme
(define-syntax (with-AorB stx)
  (syntax-parse stx
    [(_ [x:id aorb:id] body ...)
     (with-syntax ([x (syntax-property #'x 'AorB (syntax-e #'aorb))])
       #'(begin body ...))]))

(define-syntax (ref stx)
  (syntax-parse stx
    [(_ x)
     (begin
       (define prop (syntax-property #'x 'AorB))
       (writeln prop)
       (case prop
         [(A) #'(A-a x)]
         [(B) #'(B-b x)]
         [else (raise-syntax-error 'ref (format "incorrect type: ~a" prop) #'x)]))]))

(let ([x (A 'aaa)])
  (with-AorB [x A]
    (ref x))) ; compile-time error "ref: incorrect type: #f in: x"

```

Not sure if this is the right approach. Any clue?

---

<div class="post-metadata">

**Author:** ![bakgatviooldoos](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/bakgatviooldoos/32/1381_2.png) [@bakgatviooldoos](https://racket.discourse.group/u/bakgatviooldoos)\
**Post date:** [December 9, 2024, 12:37pm UTC](https://racket.discourse.group/t/compile-time-dispatch-based-on-type/3405/2 "2024-12-09T12:37:59Z")

</div>

Hi, @Laurent.O.

How localized do the `ref`s need to be? If we're using `syntax-parse`, perhaps one might use a `syntax-class` like so?

```scheme
#lang racket

(require
  (for-syntax
   syntax/parse))

(struct A (a))
(struct B (b))

(begin-for-syntax
  (define-syntax-class A/B
    (pattern [x (~literal A)]
      #:with ref #'(A-a x))

    (pattern [x (~literal B)]
      #:with ref #'(B-b x))
    
    (pattern else
      #:with ref #false
      #:fail-when #true
      (format "incorrect type syntax: ~a" (syntax->datum #'else)))))

(define-syntax (ref stx)
  (syntax-parse stx
    [(_ x:A/B) #'x.ref]))

(let ([x (A 'aaa)]) (ref [x A]))
(let ([x (B 'bbb)]) (ref [x B]))

;(let ([x (A 'aaa)]) (ref oops))
; =>
#|
ref: incorrect type syntax: oops
  parsing context: 
   while parsing A/B in: oops
|#

```

However, I am not sure what this adds apart from different syntax to the accessor (i.e. I don't understand the interplay with the dispatch, per se).

---

<div class="post-metadata">

**Author:** ![Laurent.O](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/laurent.o/32/18_2.png) [@Laurent.O](https://racket.discourse.group/u/Laurent.O)\
**Post date:** [December 9, 2024, 12:49pm UTC](https://racket.discourse.group/t/compile-time-dispatch-based-on-type/3405/3 "2024-12-09T12:49:36Z")

</div>

Thanks, that's an interesting idea, but that means I need to annotate at every use of `ref`, which kinda defeats the purpose.  
I would like to annotate once, then potentially have many `ref` (and perhaps other accessors to come) in the body of the annotated block.

---

<div class="post-metadata">

**Author:** ![bakgatviooldoos](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/bakgatviooldoos/32/1381_2.png) [@bakgatviooldoos](https://racket.discourse.group/u/bakgatviooldoos)\
**Post date:** [December 9, 2024, 1:51pm UTC](https://racket.discourse.group/t/compile-time-dispatch-based-on-type/3405/4 "2024-12-09T13:51:28Z")

</div>

Ah, yes, hence my question about the locality.

This is ugly, but the idea might be fruitful, i.e., using a `syntax-parameter` or similar contrivance to simulate the annotations.

Which is not to say that attributes might not work, but my brain isn't braining that way, right now.

```scheme
#lang racket

(require
  racket/stxparam
  (for-syntax
   syntax/parse syntax/parse/class/struct-id racket/hash))

(struct A (a b))
(struct B (b))

(define-syntax-parameter ref-types (hash))

(begin-for-syntax
  (define (the-types) (syntax-parameter-value #'ref-types))
  (define (known? id) (hash-has-key? (the-types) id))
  
  (define-syntax-class ref-expr
    (pattern [x type]
      #:do [(define v (syntax->datum #'x))]
      
      #:fail-when (not (known? v))
      (format "incorrect type syntax, unexpected id: ~a" v)
      
      #:do [(define T (syntax->datum #'type))
            (define ref-pattern (hash-ref (the-types) v))
            (define the-type (car ref-pattern))
            (define the-ref (cadr ref-pattern))]
      
      #:fail-when (not (eq? the-type T))
      (format "incorrect type syntax, expected type: ~a" T)
      
      #:with ref #`(#,the-ref x))))

(define-syntax (with-refs stx)
  (syntax-parse stx
    [(_ ([which (type:struct-id get)] ...)
        body ...)
     #:do [(define instances (syntax->datum #'(get ...)))
           (define accessors (syntax->datum #'((type.accessor-id ...) ...)))
           (define valid-get? (andmap (lambda (g G) (member g G)) instances accessors))]
     #:when valid-get?
     #`(syntax-parameterize ([ref-types (#:combine/key (lambda (k a b) b)
                                         . hash-union .
                                         #,(the-types) (hash (~@ 'which '(type get)) ...))])
         body ...)]))

(define-syntax (ref stx)
  (syntax-parse stx
    [(_ x:ref-expr) #'x.ref]))

(with-refs ([x (A A-a)]
            [y (A A-b)])
  (list
   (let ([x (A 'i 'j)])
     (list
      (ref [x A])
      (with-refs ([x (B B-b)])
        (let ([x (B 'bb)]) (ref [x B])))))

   (let ([y (A 'm 'n)]) (ref [y A]))))

;=> '((i bb) n)

```

Thanks to @shawnw for showing me the `struct-id` syntax class.

Maybe have a look at his [`with-slots`](https://racket.discourse.group/t/what-kind-of-with-s-have-you-come-up-with/3360/8), now that I think about it...

* * *

Edit: this way, you can have shadowing of ref patterns, but I am just messing around.

---

<div class="post-metadata">

**Author:** ![Laurent.O](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/laurent.o/32/18_2.png) [@Laurent.O](https://racket.discourse.group/u/Laurent.O)\
**Post date:** [December 9, 2024, 2:28pm UTC](https://racket.discourse.group/t/compile-time-dispatch-based-on-type/3405/5 "2024-12-09T14:28:48Z")

</div>

You still need to provide the 'type' to `ref` though, it seems?

---

<div class="post-metadata">

**Author:** ![bakgatviooldoos](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/bakgatviooldoos/32/1381_2.png) [@bakgatviooldoos](https://racket.discourse.group/u/bakgatviooldoos)\
**Post date:** [December 9, 2024, 3:08pm UTC](https://racket.discourse.group/t/compile-time-dispatch-based-on-type/3405/6 "2024-12-09T15:08:34Z")

</div>

Indeed.

I guess you could just throw that bit away? But I am starting to suspect I don't really understand the assignment.

```scheme
#lang racket

(require
  racket/stxparam
  (for-syntax
   syntax/parse syntax/parse/class/struct-id racket/hash))

(struct A (a b))
(struct B (b))

(define-syntax-parameter ref-map (hash))

(begin-for-syntax
  (define (the-refs) (syntax-parameter-value #'ref-map))
  (define (known? id) (hash-has-key? (the-refs) id))
  
  (define-syntax-class ref-expr
    (pattern x:id
      #:do [(define v (syntax->datum #'x))]
      
      #:fail-when (not (known? v))
      (format "incorrect type syntax, unexpected id: ~a" v)
      
      #:with ref #`(#,(hash-ref (the-refs) v) x))))

(define-syntax (with-refs stx)
  (syntax-parse stx
    [(_ ([which (type:struct-id get)] ...)
        body ...)
     #:do [(define accessor* (syntax->datum #'(get ...)))
           (define accessors (syntax->datum #'((type.accessor-id ...) ...)))]
     #:when (andmap member accessor* accessors)
     #`(syntax-parameterize ([ref-map (#:combine/key (lambda (k a b) b)
                                       . hash-union .
                                       #,(the-refs) (hash (~@ 'which 'get) ...))])
         body ...)]))

(define-syntax (ref stx)
  (syntax-parse stx
    [(_ ex:ref-expr) #'ex.ref]))

(with-refs ([x (A A-a)] [y (A A-b)])
  (list
   (let ([x (A 'i 'j)])
     (list
      (ref x)
      (with-refs ([x (B B-b)])
        (let ([x (B 'bb)])
          (ref x)))))
   
   (let ([y (A 'm 'n)])
     (ref y))))

;=> '((i bb) n)

```

* * *

Edit: my OCD was killing me about that `lambda` around the `member` 😅.

---

<div class="post-metadata">

**Author:** ![Laurent.O](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/laurent.o/32/18_2.png) [@Laurent.O](https://racket.discourse.group/u/Laurent.O)\
**Post date:** [December 9, 2024, 3:54pm UTC](https://racket.discourse.group/t/compile-time-dispatch-based-on-type/3405/7 "2024-12-09T15:54:01Z")

</div>

That's clearer now 🙂 It's actually a pretty neat solution, thanks!

(FTR: requires Lexi Lambda's `syntax-classes` packages for the `struct-id` class)

---

<div class="post-metadata">

**Author:** ![Laurent.O](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/laurent.o/32/18_2.png) [@Laurent.O](https://racket.discourse.group/u/Laurent.O)\
**Post date:** [December 9, 2024, 6:50pm UTC](https://racket.discourse.group/t/compile-time-dispatch-based-on-type/3405/8 "2024-12-09T18:50:15Z")

</div>

Still for the record, I suppose a better version would use the [syntax-id/tables](https://docs.racket-lang.org/syntax/syntax-helpers.html#%28mod-path._syntax%2Fid-table%29).

---

<div class="post-metadata">

**Author:** ![bakgatviooldoos](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/bakgatviooldoos/32/1381_2.png) [@bakgatviooldoos](https://racket.discourse.group/u/bakgatviooldoos)\
**Post date:** [December 9, 2024, 8:29pm UTC](https://racket.discourse.group/t/compile-time-dispatch-based-on-type/3405/9 "2024-12-09T20:29:46Z")

</div>

Hmm, I am inclined to agree, but I am not sure how to make it work.

I tried using a `bound-id-table` with the assumption that this would do the trick, but I obviously don't understand what it is doing, because it does not seem to match the identifiers as I would expect.

I shall try some more. In the meantime, this little extension allows one to elide the explicit type of the struct, and only refer to the accessor:

```scheme
#lang racket

(require
  racket/stxparam
  (for-syntax
   racket/syntax syntax/parse syntax/parse/class/struct-id
   racket/hash))

(struct A (a b-max))
(struct B (b))

(define-syntax-parameter ref-map (hash))

(begin-for-syntax
  (define (the-refs) (syntax-parameter-value #'ref-map))
  (define (known? id) (hash-has-key? (the-refs) id))
  
  (define-syntax-class ref-expr
    (pattern x:id
      #:do [(define u (syntax->datum #'x))]
      
      #:fail-when (not (known? u))
      (format "incorrect ref-expr syntax, unexpected id: ~a" u)
      
      #:with ref #`(#,(hash-ref (the-refs) u) x)))

  (define-syntax-class ref-decl
    (pattern (type:struct-id get)
      #:with gets (format-id #'type "~a-~a" #'type #'get)
      #:do [(define accessor* (syntax->datum #'gets))
            (define accessors (syntax->datum #'(type.accessor-id ...)))]
      #:fail-when
      (not (member accessor* accessors))
      ("unexpected accessor declaration in: ~a\nexpected one of: ~a"
       . format . accessor* accessors)))

  (define (union-refs new-refs)
    (#:combine/key (lambda (k a b) b)
     . hash-union .
     (the-refs) new-refs)))

(define-syntax (with-refs stx)
  (syntax-parse stx
    [(_ ([which ex:ref-decl] ...)
        body ...)
     #'(syntax-parameterize ([ref-map (union-refs (hash (~@ 'which 'ex.gets) ...))])
         body ...)]))

(define-syntax (ref stx)
  (syntax-parse stx
    [(_ ex:ref-expr) #'ex.ref]))

(with-refs ([x (A a)] [y (A b-max)])
  (list
   (let ([x (A 'i 'j)])
     (list
      (ref x)
      (with-refs ([x (B b)])
        (let ([x (B 'k)])
          (ref x)))))
   
   (let ([y (A 'm 'n)])
     (ref y))))

;=> '((i k) n)

```

P.S. this was a fun diversion for the day. Kudos.

Edit: use regex, and then you have `.+` problems 🙃
