www

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

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).}]