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

David Waite david at alkaline-solutions.com
Fri Dec 4 00:28:42 CST 2015


( Originally proposed here: 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("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


-DW

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20151203/2155c8d2/attachment.html>


More information about the swift-evolution mailing list