> 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

Reply via email to