[swift-evolution] Idea for enabling DSLs: bind to self in closures

David Waite 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
> 
>   expect(sections).to(....)
> }

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.

> 
> Being able to elide self is already somewhat controversial, and a number of people find it makes code harder to read. I worry that allowing closures to change 'self' has the potential to be even more confusing. In Javascript, it's my understanding the ability to arbitrarily rebind 'this' is seen as a design flaw rather than a feature people regularly take advantage of.
> 

Ruby has it as well, and while it perhaps a bit more ‘magic’ than some would desire, you will not find Ruby developers lamenting its existence. Javascript’s inconsistent historical behaviors greatly contribute to the perception that rebinding is a design flaw.

However, the behavior I’m describing is different than either Ruby or Javascript:
- both Ruby and Javascript are late bound, which allows the JIT implementations to adapt to whatever type instance is given. Swift is not a late bound language, so it must know the types it is working with beforehand
- Ruby and Javascript allow the caller to set self/this. The choice to consider one of the parameters to have ‘self’ behavior in my proposal is entirely in the hands of the person writing the block of code being called, by choosing to name one of the passed in parameters ‘self'
- Due to late binding, it may be very difficult to determine what type your self/this is bound to in Ruby/Javascript. This is explicit in the signature of the closure in my proposal.
- Ruby and Javascript have no visual indicator that a block of code may be called with some other object as self/this. This is explicit in my proposal by naming one of the parameters in your closure ‘self'
- 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.  

-DW
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20151204/6d9610b7/attachment.html>


More information about the swift-evolution mailing list