[Feature Request] Tag Contract and Tag Type

Before posting my thought, I didn’t know much about Racket’s contract. After that, I studied the impersonator from document and tried to write a simple implementation:
(I didn't find the chaperone contract for mlist, so I replaced the original mlist example with box)

main.rkt:

#lang racket/base

(require racket/contract racket/match)
(provide tag-contract tag-contract? tag-contract-recover)


(define-values (prop:tags prop:tags? prop:tags-get)
  (make-impersonator-property 'prop:tags))

(define/contract (tag-contract? arg)
  (-> any/c boolean?)
  (and (contract? arg)
       (match (contract-name arg)
         [`((tag-contract ,_) . ,(? symbol?)) #t]
         [_ #f])))

(define/contract (tag-contract base-contract)
  (-> chaperone-contract? tag-contract?)

  (define t (gensym))
  (make-contract #:name `((tag-contract ,(contract-name base-contract)) . ,t)
                 #:projection
                 (λ (b)
                   (define base-proj (contract-projection base-contract))
                   (define base (base-proj (blame-swap b)))
                   (λ (arg)
                     (match (base arg)
                       #;[(? procedure? p)]
                       #;[(? struct? s)]
                       #;[(? vector? v)]
                       [(? box? b)
                        (chaperone-box b
                                       (λ (b o) o)
                                       (λ (b i) i)
                                       prop:tags
                                       (if (prop:tags? b)
                                           (hash-set (prop:tags-get b) t b)
                                           (hasheq t b)))]
                       #;[(? hash? h)]
                       #;[(? channel? ch)]
                       #;[(? continuation-mark-key? cmk)])))))

(define/contract (tag-contract-recover tc)
  (-> tag-contract? (-> any/c any/c))

  (match-define `((tag-contract ,_) . ,t) (contract-name tc))
  (λ (arg)
    (and (prop:tags? arg)
         (hash-has-key? (prop:tags-get arg) t)
         (hash-ref (prop:tags-get arg) t))))

test-box.rkt:

#lang racket

(require "main.rkt")


(define box-number/t  (tag-contract (box/c number?)))
(define box-boolean/t (tag-contract (box/c boolean?)))

(define box-number/r  (tag-contract-recover box-number/t))
(define box-boolean/r (tag-contract-recover box-boolean/t))

(define T?
  (first-or/c symbol? string?
              (and/c (box/c number?  #:flat? #t) box-number/t)
              (and/c (box/c boolean? #:flat? #t) box-boolean/t)))


(define/contract (g arg)
  (-> (box/c none/c any/c) number?)

  (cond [(box-boolean/r arg)
         => (λ (arg)
              (set-box! arg (not (unbox arg)))
              (if (unbox arg) 1 -1))]
        [(box-number/r arg)
         => (λ (arg)
              (set-box! arg (- (unbox arg)))
              (unbox arg))]
        [else -123]))

(define/contract (f arg)
  (-> T? number?)

  (cond [(string? arg) (string-length arg)]
        [(symbol? arg) (string-length (symbol->string arg))]
        [else (g arg)]))


(f (box 10))    ; -10
(f (box #t))    ; -1
(f (box #f))    ;  1

This is the code I imagined in Typed Racket:

#lang typed/racket


(define-type Box-Number  (Tag (Boxof Number)))
(define-type Box-Boolean (Tag (Boxof Boolean)))

(define-recover box-number/r  Box-Number)
(define-recover box-boolean/r Box-Boolean)

(define-type T (U Symbol String Box-Number Box-Boolean))


(: g [-> (Boxof Nothing Any) Number])
(define (g arg)
  (cond [(box-boolean/r arg)
         => (λ (arg)
              (set-box! arg (not (unbox arg)))
              (if (unbox arg) 1 -1))]
        [(box-number/r arg)
         => (λ (arg)
              (set-box! arg (- (unbox arg)))
              (unbox arg))]
        [else -123]))

(: f [-> T Number])
(define (f arg)
  (cond [(string? arg) (string-length arg)]
        [(symbol? arg) (string-length (symbol->string arg))]
        [else (g arg)]))


(f (box 10))    ; -10
(f (box #t))    ; -1
(f (box #f))    ;  1