[swift-evolution] [Pitch] Contextual variables
Brent Royal-Gordon
brent at architechies.com
Sun Jul 2 19:49:48 CDT 2017
> On Jun 28, 2017, at 5:33 AM, Dimitri Racordon via swift-evolution <swift-evolution at 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
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170702/da439fb4/attachment.html>
More information about the swift-evolution
mailing list