Polysemy:   support for polysemic identifiers
1 Examples
2 Introduction
3 Bindings provided by polysemy
poly-only-in
poly-rename-in
poly-out
define-poly
~poly
define-poly-literal
define-poly-case
case-function
poly-meaning-expander
4 Limitations
8.12

Polysemy: support for polysemic identifiers🔗ℹ

Suzanne Soy <racket@suzanne.soy>

 (require polysemy) package: polysemy

This is an experimental proof of concept, and is not intended to be used in production until the potential issues of doing so have been discussed with other racketeers.

The bindings described here may be changed in future versions without notice.

1 Examples🔗ℹ

This first example shows four short modules which all define the identifier ^, with four different meanings: the first uses it as a special token (similarly to the use of : to separate fields from their type in Typed Racket, among other things); the second defines it as a exclusive-or match expander; the third defines it as the exponentiation function; the fourth defines it as the two-variable logical xor function (which, thankfully, does not need any short-circuiting behaviour).

Examples:
> (module m-one racket
    (require polysemy (for-syntax syntax/parse racket/list))
    (provide (poly-out [my-macro normal-macro]
                       [^ my-macro-repeat-n-times]))
    (define-poly-literal ^ my-macro-repeat-n-times hat-stxclass)
    (define-poly my-macro normal-macro
      (syntax-parser
        [(_ v :hat-stxclass n)
         #`(list . #,(for/list ([i (in-range (syntax-e #'n))]) #'v))])))
> (module m-two racket
    (require polysemy (for-syntax syntax/parse))
    (provide (poly-out [[xor ^] match-expander]))
    (define-poly xor match-expander
      (syntax-parser
        [(_ a b) #'(and (or a b) (not (and a b)))])))
> (module m-three racket
    (require polysemy)
    (provide (all-defined-out))
    ; Multi-argument functions are not supported yet…
    (define-poly-case (^ [x number?]) (λ (y) (expt x y))))
> (module m-four racket
    (require polysemy)
    (provide (all-defined-out))
    (define-poly-case (^ [x boolean?])
      (λ (y)
        (and (or x y) (not (and x y))))))
; Seamlessly require the two versions of ^
> (require 'm-one 'm-two 'm-three 'm-four racket/match)
> (my-macro 'foo ^ 3)

'(foo foo foo)

> (match "abc"
    [(^ (regexp #px"a") (regexp #px"b")) "a xor b but not both"]
    [_ "a and b, or neither"])

"a and b, or neither"

> ((^ 2) 3)

8

> ((^ #t) #f)

#t

Thanks to the use of polysemy, all four uses are compatible, and it is possible to require the four modules without any special incantation at the require site. The providing modules themselves have to use special incantations, though: define-poly-literal, define-poly and define-poly-case. Furthermore, a simple rename-out does not cut it anymore, and it is necessary to use poly-out to rename provided polysemic identifiers. Note that a static check is performed, to make sure that the cases handled by ^ from m-three do not overlap the cases handled by ^ from m-four. The function overloads are, in this sense, safe.

The following example shows of the renaming capabilities of polysemy: three meanings for the foo identifier are defined in two separate modules (two meanings in the first, one in the second). The meanings of foo from the first module are split apart into the identifiers baz and quux, and the meaning from the second module is attached to baz. The identifier baz is therefore a chimera, built with half of the foo from the first module, and the foo from the second module.

Examples:
> (module ma racket
    (require polysemy)
    (provide (all-defined-out))
    (define-poly foo match-expander (λ (stx) #'(list _ "foo" "match")))
    (define-poly-case (foo [x integer?]) (add1 x)))
> (module mb racket
    (require polysemy)
    (provide (all-defined-out))
    (define-poly-case (foo [x list?]) (length x)))
; baz is a hybrid of the foo match expander from ma,
; and of the foo function on lists from mb.
; ma's foo function is separately renamed to quux.
> (require polysemy
           racket/match
           (poly-rename-in 'ma
                           [[foo baz] match-expander]
                           [[foo quux] (case-function integer?)])
           (poly-rename-in 'mb
                           [[foo baz] (case-function list?)]))
; baz now is a match expander and function on lists:
> (match '(_ "foo" "match") [(baz) 'yes])

'yes

> (baz '(a b c d))

4

; The baz function does not accept integers
; (the integer-function part from ma was split off)
> (baz 42)

baz: contract violation

  expected: list?

  given: 42

  in: the 1st argument of

      (-> (listof any/c) any)

  contract from: (function baz)

  blaming: top-level

   (assuming the contract is correct)

  at: eval:6:0

; The quux function works on integers…
> (quux 42)

43

; … but not on lists, and it is not a match expander
> (quux '(a b c d))

quux: contract violation

  expected: integer?

  given: '(a b c d)

  in: the 1st argument of

      (-> integer? any)

  contract from: (function quux)

  blaming: top-level

   (assuming the contract is correct)

  at: eval:8:0

> (match '(_ "foo" "match") [(quux) 'yes] [_ 'no])

eval:9:0: match: syntax error in pattern

  in: (quux)

2 Introduction🔗ℹ

This module allows defining polysemic identifiers which can act as a match expander, as a macro, as an identifier macro, as a set! subform, and as a collection of function overloads.

The following meanings are special:

3 Bindings provided by polysemy🔗ℹ

In all the forms below, the meaning should be a simple identifier. Note that is lexical context is not taken into account (i.e. the comparison is based on the equality of symbols, not based on free-identifier=?), and therefore every meaning should be globally unique. Later versions may add a notion of hygiene to meanings (allowing these meanings themselves to be renamed, to circumvent conflicts).

require transformer

(poly-only-in module [maybe-rename meaning ...] ...)

 
maybe-rename = old-id
  | [old-id new-id]
Requires each given meaning of the corresponding old-id. If new-id is supplied, then the meanings are attached to new-id, otherwise they are attached to old-id.

require transformer

(poly-rename-in module [maybe-rename meaning] ...)

 
maybe-rename = old-id
  | [old-id new-id]
Similar to poly-only-in, but all identifiers and meanings which are unaffected are also implicitly required. Note that if some (but not all) meanings of an identifier are renamed, then the old name is not required automatically anymore, and needs to be explicitly required.

provide transformer

(poly-out module [maybe-rename meaning])

 
maybe-rename = old-id
  | [old-id new-id]
Provides the given meanings for id. It is necessary to provide all the desired meanings explicitly, or use (provide (all-defined-out)). Simply using (provide id) will only provide the base identifier, without any meanings attached to it.

If old-id and new-id are supplied, each given meaning, which must be attached to old-id, will be re-attached to new-id.

syntax

(define-poly id)

(define-poly id meaning value)
The first form declares that id is a polysemic identifier, with no special meaning attached to it.

The second form attaches the phase 1 value (i.e. it is a transformer value) to the given meaning of the id.

pattern expander

(~poly pvar meaning)

Pattern epander for syntax/parse, can be used to match against polysemic identifiers, extracting the desired meaning.

The transformer value for the requested meaning is stored in the value attribute.

syntax

(define-poly-literal id meaning syntax-class)

Defines id as a literal with the given meaning. The syntax-class is automatically defined to recognise the given meaning of id, even if id was renamed and its different meanings split out and recombined into different identifiers.

This can be used to define "tokens" for macros, which bear a special meaning for some macros, but might have a different meaning for another third-party macro. If both rely on polysemy, then they can use the same default name, without the risk of the identifiers conflicting. Furthermore, it is possible to rename the two meanings separately.

syntax

(define-poly-case (name [arg₀ pred?]) . body)

Note that the syntax for this form will be changed in the future when support for multiple-argument dispatch is added (remember, this package is still in an experimental state).

Defines an overload for the name function, based on the type of its first argument. For now, only a few contracts are allowed:

When any polysemic identifier which is contains a poly-case is called as a function, a check is performed to make sure that none of its cases overlap. If some cases overlap, then an error is raised.

Note that an identifier cannot have both a meaning as a function case, and a normal-macro or identifier-macro meanings.

poly-meaning-expander

(case-function pred?)

When used in place of a meaning in a poly-rename-in, poly-only-in or poly-out form, expands to the meaning symbol for a function overload accepting the given argument type. The normal-macro and identifier-macro meanings (which would normally be associated with polysemy’s dynamic dispatch macro) are also included in the expansion.

meaning

poly-meaning-expander

When used as (define-poly some-id poly-meaning-expander (λ (stx) . body)), defines an expander for the poly-rename-in, poly-only-in and poly-out forms. For example, the case-function expander described above is defined in that way.

4 Limitations🔗ℹ

There are currently many limitations. Here are a few: