polysemy.scrbl (13865B)
1 #lang scribble/manual 2 @(require scribble/example 3 (for-label racket/base 4 racket/contract/base 5 racket/match 6 syntax/parse 7 polysemy)) 8 9 @title{Polysemy: support for polysemic identifiers} 10 @author[@author+email["Suzanne Soy" "racket@suzanne.soy"]] 11 12 @defmodule[polysemy] 13 14 This is an experimental proof of concept, and is not intended to be used in 15 production until the potential issues of doing so have been discussed with 16 other racketeers. 17 18 The bindings described here may be changed in future versions without notice. 19 20 @section{Examples} 21 22 This first example shows four short modules which all define the identifier 23 @racketid[^], with four different meanings: the first uses it as a special 24 token (similarly to the use of @racket[:] to separate fields from their type 25 in Typed Racket, among other things); the second defines it as a exclusive-or 26 match expander; the third defines it as the exponentiation function; the 27 fourth defines it as the two-variable logical xor function (which, thankfully, 28 does not need any short-circuiting behaviour). 29 30 @examples[#:escape UNSYNTAX 31 (module m-one racket 32 (require polysemy (for-syntax syntax/parse racket/list)) 33 (provide (poly-out [my-macro normal-macro] 34 [^ my-macro-repeat-n-times])) 35 (define-poly-literal ^ my-macro-repeat-n-times hat-stxclass) 36 (define-poly my-macro normal-macro 37 (syntax-parser 38 [(_ v :hat-stxclass n) 39 #`(list . #,(for/list ([i (in-range (syntax-e #'n))]) #'v))]))) 40 (module m-two racket 41 (require polysemy (for-syntax syntax/parse)) 42 (provide (poly-out [[xor ^] match-expander])) 43 (define-poly xor match-expander 44 (syntax-parser 45 [(_ a b) #'(and (or a b) (not (and a b)))]))) 46 (module m-three racket 47 (require polysemy) 48 (provide (all-defined-out)) 49 (code:comment "Multi-argument functions are not supported yet…") 50 (define-poly-case (^ [x number?]) (λ (y) (expt x y)))) 51 (module m-four racket 52 (require polysemy) 53 (provide (all-defined-out)) 54 (define-poly-case (^ [x boolean?]) 55 (λ (y) 56 (and (or x y) (not (and x y)))))) 57 (code:comment "Seamlessly require the two versions of ^") 58 (require 'm-one 'm-two 'm-three 'm-four racket/match) 59 60 (my-macro 'foo ^ 3) 61 (match "abc" 62 [(^ (regexp #px"a") (regexp #px"b")) "a xor b but not both"] 63 [_ "a and b, or neither"]) 64 ((^ 2) 3) 65 ((^ #t) #f)] 66 67 Thanks to the use of @racketmodname[polysemy], all four uses are compatible, 68 and it is possible to require the four modules without any special incantation 69 at the require site. The providing modules themselves have to use special 70 incantations, though: @racket[define-poly-literal], @racket[define-poly] and 71 @racket[define-poly-case]. Furthermore, a simple @racket[rename-out] does not 72 cut it anymore, and it is necessary to use @racket[poly-out] to rename 73 provided polysemic identifiers. Note that a static check is performed, to make 74 sure that the cases handled by @racketid[^] from @racketid[m-three] do not 75 overlap the cases handled by @racketid[^] from @racketid[m-four]. The function 76 overloads are, in this sense, safe. 77 78 The following example shows of the renaming capabilities of 79 @racketmodname[polysemy]: three meanings for the @racket[foo] identifier are 80 defined in two separate modules (two meanings in the first, one in the 81 second). The meanings of @racketid[foo] from the first module are split apart 82 into the identifiers @racketid[baz] and @racketid[quux], and the meaning from 83 the second module is attached to @racketid[baz]. The identifier @racketid[baz] 84 is therefore a chimera, built with half of the @racketid[foo] from the first 85 module, and the @racketid[foo] from the second module. 86 87 @examples[(module ma racket 88 (require polysemy) 89 (provide (all-defined-out)) 90 (define-poly foo match-expander (λ (stx) #'(list _ "foo" "match"))) 91 (define-poly-case (foo [x integer?]) (add1 x))) 92 (module mb racket 93 (require polysemy) 94 (provide (all-defined-out)) 95 (define-poly-case (foo [x list?]) (length x))) 96 97 (code:comment "baz is a hybrid of the foo match expander from ma,") 98 (code:comment "and of the foo function on lists from mb.") 99 (code:comment "ma's foo function is separately renamed to quux.") 100 (require polysemy 101 racket/match 102 (poly-rename-in 'ma 103 [[foo baz] match-expander] 104 [[foo quux] (case-function integer?)]) 105 (poly-rename-in 'mb 106 [[foo baz] (case-function list?)])) 107 108 (code:comment "baz now is a match expander and function on lists:") 109 (match '(_ "foo" "match") [(baz) 'yes]) 110 (baz '(a b c d)) 111 112 (code:comment "The baz function does not accept integers") 113 (code:comment "(the integer-function part from ma was split off)") 114 (eval:error (baz 42)) 115 116 (code:comment "The quux function works on integers…") 117 (quux 42) 118 (code:comment "… but not on lists, and it is not a match expander") 119 (eval:error (quux '(a b c d))) 120 (eval:error (match '(_ "foo" "match") [(quux) 'yes] [_ 'no]))] 121 122 @section{Introduction} 123 124 This module allows defining polysemic identifiers which can act as a 125 @tech[#:doc '(lib "scribblings/reference/reference.scrbl")]{match expander}, 126 as a @tech[#:doc '(lib "scribblings/guide/guide.scrbl")]{macro}, as an 127 @tech[#:doc '(lib "scribblings/guide/guide.scrbl")]{identifier macro}, as a 128 @racket[set!] subform, and as a collection of function overloads. 129 130 The following meanings are special: 131 132 @itemlist[ 133 @item{The value for the @racket[normal-macro] meaning is used when the 134 identifier appears as the first element of a macro application (i.e. when it 135 is used as a as a @tech[#:doc '(lib "scribblings/guide/guide.scrbl")]{ 136 macro}).} 137 @item{The value for the @racket[identifier-macro] meaning is used when the 138 identifier appears on its own as an expression (i.e. when it is used as an 139 @tech[#:doc '(lib "scribblings/guide/guide.scrbl")]{identifier macro}).} 140 @item{The value for the @racket[match-expander] meaning is used when the 141 identifier is used as a match template} 142 @item{The value for the @racket['set!-macro] meaning is used when the 143 identifier is appears as the second element of a @racket[set!] form.} 144 @item{Other "core" meanings may be added later, and third-party libraries can 145 define their own meanings.}] 146 147 @section{Bindings provided by @racketmodname[polysemy]} 148 149 In all the forms below, the @racket[_meaning] should be a simple identifier. 150 Note that is lexical context is not taken into account (i.e. the comparison is 151 based on the equality of symbols, not based on @racket[free-identifier=?]), 152 and therefore every @racket[_meaning] should be globally unique. Later 153 versions may add a notion of hygiene to meanings (allowing these meanings 154 themselves to be renamed, to circumvent conflicts). 155 156 @defform[#:kind "require transformer" 157 (poly-only-in module [maybe-rename meaning ...] ...) 158 #:grammar [(maybe-rename old-id 159 [old-id new-id])]]{ 160 Requires each given @racket[meaning] of the corresponding @racket[old-id]. If 161 @racket[new-id] is supplied, then the meanings are attached to 162 @racket[new-id], otherwise they are attached to @racket[old-id].} 163 164 165 @defform[#:kind "require transformer" 166 (poly-rename-in module [maybe-rename meaning] ...) 167 #:grammar [(maybe-rename old-id 168 [old-id new-id])]]{ 169 170 Similar to @racket[poly-only-in], but all identifiers and meanings which are 171 unaffected are also implicitly required. Note that if some (but not all) 172 meanings of an identifier are renamed, then the old name is not required 173 automatically anymore, and needs to be explicitly required.} 174 175 @defform[#:kind "provide transformer" 176 (poly-out module [maybe-rename meaning]) 177 #:grammar [(maybe-rename old-id 178 [old-id new-id])]]{ 179 Provides the given meanings for @racket[id]. It is necessary to provide all 180 the desired meanings explicitly, or use @racket[(provide (all-defined-out))]. 181 Simply using @racket[(provide id)] will only provide the base identifier, 182 without any meanings attached to it. 183 184 If @racket[old-id] and @racket[new-id] are supplied, each given 185 @racket[meaning], which must be attached to @racket[old-id], will be 186 re-attached to @racket[new-id].} 187 188 @defform*[{(define-poly id) 189 (define-poly id meaning value)}]{ 190 The first form declares that @racket[id] is a polysemic identifier, with 191 no special meaning attached to it. 192 193 The second form attaches the phase 1 @racket[value] (i.e. it is a transformer 194 value) to the given @racket[meaning] of the @racket[id].} 195 196 @defform[#:kind "pattern expander" 197 (~poly pvar meaning)]{ 198 Pattern epander for @racketmodname[syntax/parse], can be used to match against 199 polysemic identifiers, extracting the desired @racket[meaning]. 200 201 The transformer value for the requested meaning is stored in the 202 @racket[value] attribute.} 203 204 @defform[(define-poly-literal id meaning syntax-class)]{ 205 Defines @racket[id] as a literal with the given @racket[meaning]. The 206 @racket[syntax-class] is automatically defined to recognise the given 207 @racket[meaning] of @racket[id], even if @racket[id] was renamed and its 208 different meanings split out and recombined into different identifiers. 209 210 This can be used to define "tokens" for macros, which bear a special meaning 211 for some macros, but might have a different meaning for another third-party 212 macro. If both rely on @racketmodname[polysemy], then they can use the same 213 default name, without the risk of the identifiers conflicting. Furthermore, it 214 is possible to rename the two meanings separately.} 215 216 @defform[(define-poly-case (name [arg₀ pred?]) . body)]{ 217 Note that the syntax for this form will be changed in the future when support 218 for multiple-argument dispatch is added (remember, this package is still in an 219 experimental state). 220 221 Defines an overload for the @racket[name] function, based on the type of its 222 first argument. For now, only a few contracts are allowed: 223 224 @itemlist[ 225 @item[@racket[any/c]] 226 @item[@racket[string?]] 227 @item[@racket[exact-positive-integer?]] 228 @item[@racket[exact-integer]] 229 @item[@racket[integer?]] 230 @item[@racket[exact?]] 231 @item[@racket[number?]] 232 @item[@racket[zero?]] 233 @item[@racket[list?]]] 234 235 When any polysemic identifier which is contains a poly-case is called as a 236 function, a check is performed to make sure that none of its cases overlap. If 237 some cases overlap, then an error is raised. 238 239 Note that an identifier cannot have both a meaning as a function case, and a 240 @racket[normal-macro] or @racket[identifier-macro] meanings.} 241 242 @defform[#:kind "poly-meaning-expander" 243 (case-function pred?)]{ 244 When used in place of a meaning in a @racket[poly-rename-in], 245 @racket[poly-only-in] or @racket[poly-out] form, expands to the meaning symbol 246 for a function overload accepting the given argument type. The 247 @racket[normal-macro] and @racket[identifier-macro] meanings (which would 248 normally be associated with @racketmodname[polysemy]'s dynamic dispatch 249 macro) are also included in the expansion.} 250 251 @defidform[#:kind "meaning" 252 poly-meaning-expander]{ 253 254 When used as 255 @racket[(define-poly _some-id poly-meaning-expander (λ (stx) . body))], 256 defines an expander for the @racket[poly-rename-in], @racket[poly-only-in] and 257 @racket[poly-out] forms. For example, the @racket[case-function] expander 258 described above is defined in that way. 259 260 } 261 262 @section{Limitations} 263 264 There are currently many limitations. Here are a few: 265 266 @itemlist[ 267 @item{Meanings themselves cannot be renamed, and must therefore be globally 268 unique. A later version could solve this by generating the actual meaning 269 symbol using @racket[gensym], and by attaching it to a user-friendly name by 270 means of a @racket[poly-meaning-expander].} 271 @item{It should be possible to specify multiple macro cases, as long as they 272 do not overlap.} 273 @item{Function overloads currently only allow a single argument. Adding 274 multiple dispatch and multiple non-dispatch arguments would be nice.} 275 @item{Only a few contracts are supported by function overloads. For simple 276 contracts, it is only a matter of extending the inheritance table in 277 @filepath{ids.rkt}. More complex contract combinators will require a bit more 278 work.} 279 @item{The generated functions are not compatible with Typed Racket. Deriving 280 types from the small set of contracts that we support should not be difficult, 281 and would allow function overloads in Typed Racket (as long as the 282 user-defined functions are typed, of course).} 283 @item{The whole contraption relies on marshalling names. Since 284 @racket[require] and @racket[provide] only care about plain names, and do not 285 have a notion of scopes (which could be used to hide some of these names), I 286 do not see any way to avoid this problem, while still making simple imports 287 (i.e. without renaming) work seamlessly with the stock implementation of 288 @racket[require].} 289 @item{There is no support for polysemic identifiers in Scribble: identifiers 290 will not get highlighted, and @tt{raco doc-coverage} will complain that some 291 internal identifiers are not documented (using @tt{raco doc-coverage -s '^ ' 292 module-path} is a quick workaround for this second issue).}]