Add initial proof of concept
This repository contains a proof of concept for a style of scheme-level function signatures which mimics the expectations around command-line arguments. Namely:
Additionally, it takes inspiration from python by allowing the caller to provide all arguments either positionally or by name, determined at the calling site. One caller might provide every argument by position, might provide every argument by name.
It is named "REPL User Interface", or RUI for short, since it takes inspiration from traditional Command Line Interfaces (CLI for short).
(read-set! keywords #f)
(use-modules ((repl-user-interface) #:prefix rui.))
(read-set! keywords 'postfix)
(rui.def (example (normal-argument alias: #:na default: "value") (flag flag?: #t))
(list normal-argument flag))
; These evaluate to '("value #t")
(example)
(example "value" #t)
(example #t normal-argument: "value")
(example na: "value" #:flag)
; These evaluate to '("not-a-value =]" #f)
(example #:no-flag na: "not-a-value =]")
(example "not-a-value =]" #f)
Guile provides 3 styles for keyword: default (#:name), postfix (name:), and prefix (:name). In my experience the default style is the most popular. This project mixes the default and postfix styles for more meaningful syntax. If the keyword object is meant as a value itself in the context of its use then the default style is used. If it is being used to label the value that follows it then the postfix style is used. This can be seen in the example above where postfix is used when naming the value for normal-argument, but default is used when declaring the flag.
Note that keyword style is a global setting, not a per-file setting.
This is why the example above calls read-set!
before and after importing modules, so
that the imported modules are loaded in the default state but the module itself supports
the postfix style.
This is the technique I recommend to handle keyword styling in all projects.
REPL user interfaces are implemented in 3 different contexts.
When a procedure that supports the RUI is defined, the library generates code which knows
how to rewrite calls using the interface into calls that send all arguments positionally
(as is the case with procedures defined using the standard define
or lambda
mechanisms).
When a dependent module calls a procedure the arguments are scanned for keywords that name an argument, and the arguments are rewritten so that they are all positional arguments and the keyword names are removed.
Both of the above contexts happen at compile time.
Once the rewriting is done, all that remains is a direct call to a procedure that is
creating using the typical lambda
mechanism.
This is the only thing that remains once the code is compiled.
You can think about the above example as compiling to something that looks like this, although the reality is more complicated:
(read-set! keywords #f)
(use-modules ((repl-user-interface) #:prefix rui.))
(read-set! keywords 'postfix)
(define (example normal-argument flag)
(list normal-argument flag))
; All 4 examples in the first section compile to the exact same thing
(example "value" #t)
(example "value" #t)
(example "value" #t)
(example "value" #t)
; Again, both of the examples in the second section compile to the same thing
(example "not-a-value =]" #f)
(example "not-a-value =]" #f)