[swift-evolution] Idea for enabling DSLs: bind to self in closures
david at alkaline-solutions.com
Fri Dec 4 15:44:30 CST 2015
> On Dec 4, 2015, at 10:36 AM, Joe Groff <jgroff at apple.com> wrote:
> Another way to do this would be to support scoped imports, to make a set of top-level functions locally available without polluting the global namespace:
> import func QuickSpecBuilder.expect
All of the matching methods (such as equal, contains, raiseError, raiseException) are also module functions, so you would likely just pull them all in. The Hamcrest Java project is very similar to Nimble, and they have a utility class generator (static methods calling other class static methods) specifically for reducing the number of static imports you have to put at the top of your test files.
The most common use of DSLs is to give an expressive grammar to builder patterns. The Ruby on Rails router has perhaps had more man hours put into it than any other DSL on the platform, and its primary purpose is to build
- a dispatch table to match and parse parameters out of incoming URLs, and stuff them into methods on controller instances
- a method (actually methods) for the reverse behavior of taking parameters and building the appropriate path, e.g. user_path(current_user)
These are often hierarchal via closures. The use of closures both allows specific context to be made available based on the outer configuration, and for the actual use of a function to be deferred until it is needed.
Another example would be a DSL providing inversion of control. This might define methods to resolve objects by name, with the closures defining the construction behavior. The closure would get as a parameter the context of the DSL lookup, to allow it to lazily retrieve its own dependencies. However, this can’t be the global reference to the DSL object, because you want to fail on circular dependencies. Instead, you get a state object representing the particular request which was made of the system.
Yet another example is Quick itself - the ‘it’ and ‘expect’ functions are inheriting the ‘describe’ to aid in better error reporting, as well as any per-test invocation behavior (such as clearing out an in-memory database). This state can be passed in as an object, captured in lexical scope by the nested closure, or (in these cases) set and modified globally as needed. My concern is in people using global state to get the expressiveness they desire for their APIs.
With Nimble in particular, expect has state which currently (because it is a global function) has to be globally defined. This state also can’t be thread local, as your actual tests might involve multiple threads or callbacks.
- Finally, as a side-effect you can choose to bind a parameter to self independent of the API designer if you feel that makes your code more readable. Admittedly, I removed my example as the code was simplistic enough that $0 was more concise
That said, as proposed this is an expressiveness feature. I proposed it because I found I was uncomfortable with the number of global functions and amount of global state I was seeing in Swift modules - but could not come up with a way to make said code more robust/safe without negatively affecting expressiveness.
-------------- next part --------------
An HTML attachment was scrubbed...
More information about the swift-evolution