> On Jun 28, 2017, at 5:33 AM, Dimitri Racordon via swift-evolution
> <[email protected]> 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
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution