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

Joe Groff jgroff at apple.com
Fri Dec 4 11:36:32 CST 2015


> On Dec 3, 2015, at 10:28 PM, David Waite <david at alkaline-solutions.com> wrote:
> 
> ( Originally proposed here: https://forums.developer.apple.com/thread/13806 <https://forums.developer.apple.com/thread/13806> )
> 
> Often, frameworks will wish to provide a domain specific language for configuring or composition. Web frameworks for instance may define methods like
> 
> get(“/“) { request, result in … }
> 
> Testing frameworks (such as Quick/Nimble) may wish to define a terse and expressive syntax for defining behavioral tests:
> 
> describe("the 'Documentation' directory") {
>       it("has everything you need to get started") {
>             let sections = Directory("Documentation").sections
>             expect(sections).to(contain("Organized Tests with Quick Examples and Example Groups”))
>       }
> }
> 
> While expressive, this has a big problem - describe, it, expect and contain are now defined as either global functions (which cause namespace pollution and mandate global state) or instance methods on a class (which requires the use of class inheritance, and is limited by single class inheritance)
> 
> You could have some sort of context object passed into the closure instead:
> 
> protocol SpecBuilder {
>       func describe(description:String, inner:(QuickSpecContext)->())
> }
> 
> protocol QuickSpecContext {
>       func it(description:String, inner:(QuickSpecContext)->()) 
>       func expect<T>(statement:@autoclosure ()->T, file: StaticString = __FILE__, line: UWord = __LINE__ ) -> Expectation<T>
> }
> 
> var spec = QuickSpecBuilder(config)
> spec.describe("the 'Documentation' directory") {
>       context in
>       context.it <http://context.it/>("has everything you need to get started") {
>             context in
>             let sections = Directory("Documentation").sections
>             context.expect(sections).to(contain("Organized Tests with Quick Examples and Example Groups”))
>       }
> }
> 
> But this has significantly more noise. So my proposal is to allow for a closure argument to be used as the current type instance- to be able to redefine ‘self’ within a block.
> 
> var spec = QuickSpecBuilder(config)
> spec.describe("the 'Documentation' directory") {
>       self in
>       it("has everything you need to get started") {
>             self in
>             let sections = Directory("Documentation").sections
>             expect(sections).to(contain("Organized Tests with Quick Examples and Example Groups”))
>       }
> }
> 
> resolution remains the same (lexical scope shadowing the type), this is merely shorthand to allow expressive grammars without requiring class inheritance or global functions. It also remains optional to use - the last two examples are based around the same protocols and should compile to the same code.
> 
> I considered alternate syntaxes to express this, mostly alternatives on the bracketing of the closure itself to indicate binding a parameter to self. In the end, I decided:
> 1. When a block has multiple parameters, you would still need syntax decide which, if any, is bound to self
> 2. The language complexity in having another syntax for expressing closures with different behavior may not be worth it
> 3. Code would be confusing for those not knowing the new syntax. “self in” is (comparatively) straightforward and descriptive

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

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.

-Joe
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20151204/843b5d38/attachment-0001.html>


More information about the swift-evolution mailing list