> On Jun 28, 2017, at 5:33 AM, Dimitri Racordon via swift-evolution > <swift-evolution@swift.org> wrote: > > Using our contextual variables, one could rewrite our motivational example as > follows: > > class Interpreter: Visitor { > func visit(_ node: BinExpr) { > let lhs, rhs : Int > set accumulator = nil in { > node.lhs.accept(self) > lhs = accumulator! > } > set accumulator = nil in { > node.lhs.accept(self) > rhs = accumulator! > } > > switch node.op { > case "+": > accumulator = lhs + rhs > case "-": > accumulator = lhs - rhs > default: > fatalError("unexpected operator \(node.op)") > } > } > > func visit(_ node: Literal) { > accumulator = node.val > } > > func visit(_ node: Scope) { > set symbols = [:] in { > for child in node.children { > child.accept(self) > } > } > } > > func visit(_ node: Identifier) { > guard let val = symbols?[node.name] else { > fatalError("undefined symbol: \(node.name)") > } > accumulator = val > } > > context var accumulator: Int? > context var symbols: [String: Int]? > } > > It is no longer unclear what depth of the tree the accumulator variable > should be associated with. The mechanism is handled automatically, preventing > the programmer from incorrectly reading a value that was previously set for > another descent. It is no longer needed to manually handle the stack > management of the symbols variable, which was error prone in our previous > implementation.
As far as I can see, you can do this with existing features: struct Contextual<Value> { private var values: [Value] var value: Value { get { return values.last! } set { values[values.index(before: values.endIndex)] = newValue } } mutating func with<R>(_ value: Value, do body: () throws -> R) rethrows -> R { values.append(value) defer { values.removeLast() } return try body() } } class Interpreter: Visitor { var accumulator: Contextual<Int?> var symbols: Contextual<[String: Int]> func visit(_ node: BinExpr) { let lhs, rhs : Int accumulator.with(nil) { node.lhs.accept(self) lhs = accumulator.value! } accumulator.with(nil) { node.lhs.accept(self) rhs = accumulator.value! } switch node.op { case "+": accumulator.value = lhs + rhs case "-": accumulator.value = lhs - rhs default: fatalError("unexpected operator \(node.op)") } } func visit(_ node: Literal) { accumulator.value = node.val } func visit(_ node: Scope) { symbols.with([:]) { for child in node.children { child.accept(self) } } } func visit(_ node: Identifier) { guard let val = symbols.value[node.name] else { fatalError("undefined symbol: \(node.name)") } accumulator.value = val } } (There is actually a minor problem with this: the closures passed to `with(_:do:)` can't initialize `lhs` and `rhs` because DI can't prove they're run exactly once. I'd like an `@once` annotation on closure parameters to address this, but in the mean time, you can use `var` for those parameters instead of `let`.) Obviously, your `context` keyword is slightly prettier to use. But is that enough? Is this use case *so* common, or the syntax of `with(_:do:)` and `value` *so* cumbersome, that it's worth adding language features to avoid it? And if the existing syntax *is* too cumbersome, could the Property Behaviors proposal (with some of the extensions described at the end) allow you to implement this yourself with an acceptable syntax? <https://github.com/apple/swift-evolution/blob/master/proposals/0030-property-behavior-decls.md> Basically, what about this problem *requires* a solution built in to the language? Language-level solutions are difficult to design and have to be maintained forever, so it's going to take a lot to convince us this is a good addition to the language. -- Brent Royal-Gordon Architechies
_______________________________________________ swift-evolution mailing list swift-evolution@swift.org https://lists.swift.org/mailman/listinfo/swift-evolution