[proposal] bind input parameter to self in lambda

Unfortunately there doesn't appear to be a forum yet for proposing and discussing features to the Swift language (yet - I imagine this is coming once the language implementation is open sourced). So I propose this here, and copy the text to a bug report as well.


Some scripting languages (examples include javascript and ruby) allow another object to be used to resolve variable/function references. This is used within ruby specifically to allow you to pass code into a method, using an API defined by that method in a very terse and readable manner (albeit perhaps "magical" for someone unfamiliar with the feature, or the specific API that is being exposed). This functionality is also used in Ruby to allow you to dynamically define additional methods on a class from outside that class, while the code you write still reads/operates as a member of said class.


Lack of this results in equivalent terse code requiring top-level functions to be declared in modules, corrupting the namespace and also resulting in unfortunate design artifacts like thread local or global state behind said API.


While not as terse or powerful, I think there is a solution for Swift to gain quasi- equivalent functionality, without compromising Swift design goals of understandable code


A simple code example:

func with<T:AnyObject>(value:T, @noescape closure:(T)->()) {
    closure(value)
}
func given<T:AnyObject>(value:T?, @noescape closure:(T)->()) {
    if let value = value {
        closure(value)
    }
}
class Foo {
    var counter = 0
}
var a = Foo()
var b = Optional(a)

with(a) {
    value in
    value.counter += 1
}
given(b) {
    value in
    value.counter += 2
}
print(a.counter) // 3


A bit more practical example of use of these example functions:

var userActivitySlider = UISlider()

with(userActivitySlider) {
    $0.minimumValue = 0
    $0.maximumValue = 100
    $0.value = Float(a.counter)
    $0.userInteractionEnabled = true
    $0.tintColor = UIColor.redColor()
    $0.thumbTintColor = UIColor.grayColor()
}


What I propose is the ability to bind an input variable to self, to allow this code to be written as if it were an extension method on said object. However, to simplify this proposal and make things more transparent to the developer, I propose this be explicit in the declaration of the lambda, not a special attribute of the function/method that the lambda is being passed to. Rewriting the above call, I propose:


with(userActivitySlider) {
    self in
    minimumValue = 0
    maximumValue = 100
    value = Float(a.counter)
  
    userInteractionEnabled = true
    tintColor = UIColor.redColor()
    thumbTintColor = UIColor.grayColor()
}


This has several benefits. By choosing to name one of the variables as self, the choice whether a lambda should bind to an alternate self isn't just in the calling developer's control, it is explicit in the call. While Ruby or Javascript must document the binding context that the method will be evaluated within to clarify behavior, in this case the behavior is explicit - self is early bound to the type of the argument, not some object pointer to be named later. Some APIs might expect that you bind self to a value in your lambdas for readability, but again it remains in the control of the developer writing the calling code.


One consequence of doing this is that the original 'self' context is no longer available - this lambda acts as if it is some anonymous function declared as an extension of UISlider. But that can be solved by expanding the use of captures to allow for aliases:


with(userActivitySlider) {
  [weak my = self] self in
  value = my.userActivityValue
  ...
}

Replies

The use case which caused me to start thinking about this, btw, Is the Quick/Nimble library, which provides an expect-style interface for testing:


expect(foo+bar).is(lessThan(10))


In this case, expect and lessThan, along with all the other provided matcher methods have to be global. But worse, any configuration on how the system should work (such as, how to report success and failure conditions) also has to be global, since testing could involve multithreaded code.


As this is a utility class, subclassing it is not viable in all cases - you only get single inheritance.


However, using the above proposal, expect, lessThan, and any other methods can be exposed on an object passed into a testing lambda. That object can contain all the desired state, and there has been no corruption of the global function namespace.