Function and macro to define unique values

sschwarzer
Sat, 25 Jun 2022 12:34:54 +0000
Show & Tell

I've seen the idiom

(struct unique-value ())

to create unique values, for example as default arguments in a function to check with (eq? argument unique-value) if anything was passed for the argument.

Since I have only little experience with Racket macros so far, I took this as a motivation to define a macro around this. (But in the end, because of the helper function, the macro itself turned out much simpler than I had expected.) To be useful to as many readers as possible I added comments more extensively than I usually do, even though I certainly learned a lot myself and therefore had added some of the comments anyway.

#lang racket/base

(require
  racket/format
  racket/function)

; Define a helper function `make-unique-value` to generate
; a value of a struct (that only exists locally).
;
; Note that we must return a value of the struct, not
; the struct (`unique-value`) itself because otherwise the
; custom write method isn't applied.
(define (make-unique-value name)
  (struct unique-value ()
    ; See https://docs.racket-lang.org/reference/define-struct.html#%28form._%28%28lib._racket%2Fprivate%2Fbase..rkt%29._struct%29%29
    ; and https://docs.racket-lang.org/reference/Printer_Extension.html#%28def._%28%28lib._racket%2Fprivate%2Fbase..rkt%29._gen~3acustom-write%29%29
    #:methods gen:custom-write
      ; Pre-set the name as the rightmost argument because
      ; the function for `write-proc` must use three arguments,
      ; but we need to get the name somehow into `write-proc`.
      [(define write-proc (curryr unique-value-write name))])
  (unique-value))

(define (unique-value-write unique-value port mode name)
  (write-string (~a "#<unique:" name ">") port))

; Test the helper function.
(define foo (make-unique-value "foo"))
; #<unique:foo>
(displayln foo)

; Macro that wraps the helper function and allows us to use
; the syntax `(define-unique name)`.
;
; To convert the new identifier name to something that can be
; processed in the helper function, use `quote` to convert the
; name to a symbol, see
; https://rmculpepper.github.io/malr/basic.html .
(define-syntax-rule (define-unique name)
  ; Strictly speaking, we don't need `symbol->string` here
  ; because the custom write function formats 'name exactly
  ; like "name", but let's be clear on the intended types.
  (define name (make-unique-value (symbol->string (quote name)))))

(define-unique bar)
; #<unique:bar>
(displayln bar)

I wouldn't be surprised if this existed already in a package or even the standard library, but I wanted to experiment with this.

10 posts - 4 participants