allending/Kiwi — pull request #355 by sharplet (2013-08-27 03:02:55+00:00)
Title: Implement RSpec's "let" method

Here's an initial pass at implementing RSpec's `let` in Kiwi.(*) The goal is an API like this:

```objc
describe(@"Greeting", ^{
let(subject, ^{ return @""; });
let(greeting, ^{ return [NSString stringWithFormat:@"Hello, %@!", subject]; });

describe(@"default subject", ^{
specify(^{ [[subject should] beEmpty]; });
});

context(@"with the subject \"world\"", ^{
let(subject, ^{ return @"world"; });

specify(^{ [[greeting should] equal:@"Hello, world!"]; });
});

context(@"with the subject \"Kiwi\"", ^{
let(subject, ^{ return @"Kiwi"; });

specify(^{ [[greeting should] equal:@"Hello, Kiwi!"]; });
});
});
```

This pull request contains first-draft implementation where this functional test passes.

(\*) *Technically, the behaviour of this implementation is closer to `let!`, which memoises the block before entering each example. This was necessary in Objective-C becauses I'm using local variables and pointers, not dynamically defined methods.*

It adds these DSL functions to `KWExample.h`:

```objc
void let_(id *anObjectRef, const char *aSymbolName, id (^block)(void));
void letWithCallSite(KWCallSite *aCallSite, id *anObjectRef, NSString *aSymbolName, id (^block)(void));
```

As well as a `let()` macro and fake function declartion for autocomplete support:

```objc
void let(id name, id (^block)(void));
```

The high-level algorithm is this:

1. Declare a local variable using the `let` macro
2. Pass a pointer to that variable, along with its stringified name and the block, into the example group builder
3. If a `let` is called again with the same symbol name, the block is registered to the same key, allowing for nested contexts
4. Before each example, iterate through all the registered `KWLetNode` objects, evaluate the correct block for the context, and assign the result to all registered `id *` pointers for that symbol

Would love to hear people's thoughts on this, whether you think it's a good idea, feedback on the implementation, refactoring, bugs, anything.

If you decide it's a feature worth having, I plan to add more documentation, tests, etc. This is just a proof of concept I really wanted to make to convince myself it was possible!