Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Why isn't "=" an operator? #18

Open
wildthink opened this issue Nov 10, 2018 · 16 comments
Open

Why isn't "=" an operator? #18

wildthink opened this issue Nov 10, 2018 · 16 comments

Comments

@wildthink
Copy link

Looking at your REPL example, I was wondering why you manually subdivide the expression around the "=" sign rather than making the "=" a proper operator, executing the same logic in its definition.

Maybe there is something I'm not understanding as to how the framework works.

Thanks.

@nicklockwood
Copy link
Owner

nicklockwood commented Nov 10, 2018

@wildthink the problem is that Expression doesn't support inout arguments. The left hand side of = is mutated by the assignment, but there's no way to do that because it's passed by value.

In an earlier version of the REPL I did implement = as an operator, and instead of mutating the left hand side, the lhs value was used as a key into a global dictionary that the rhs value was actually assigned into.

I don't recall exactly why I changed it, but I think there was some kind of issue with Expression optimizing away the = expression if the right-hand-side was constant (that problem may have since been solved).

@wildthink
Copy link
Author

Ah, is there no way to get the name of the symbol and/or have the symbol evaluate to itself in some fashion?

@nicklockwood
Copy link
Owner

@wildthink correct. Symbol lookup is done separately prior to calling the operator implementation.

Using AnyExpression, you can do something like have every symbol evaluate to an instance of a custom class like BoxedValue which could then be used for assignment, but if you did that you’d have to overload all the standard math operators to accept BoxedValue operands instead of Double.

@wildthink
Copy link
Author

This is basically what I was trying to do. Any gotchas?

class Scope {

    var variables:[String: Any] = [:]

    func eval (_ e: String) -> Any? {
        return self.eval(e) as Any
    }

    func eval<T>(_ e: String) -> T {
        let parsedExpression = Expression.parse(e)
        let expr = AnyExpression(parsedExpression, impureSymbols: { symbol in
            switch symbol {
            case .infix("="): return { args in
                if let key = args[0] as? String {
                    self.variables[key] = args[1]
                }
                return args[1]
            }
            case .variable:
                return { _ in
                    if let value = self.variables[symbol.name] {
                        return value
                    }
                    return symbol.name
                }
            default:
                return nil
            }
        })
        return try! expr.evaluate()
    }

}

scope.eval("a = 5") // -> 5
scope.eval("a * 5") // -> 25

@nicklockwood
Copy link
Owner

nicklockwood commented Nov 10, 2018

@wildthink that seems very similar to my original implementation.

What happens if you try to reassign a value to a after you’ve assigned it the first time? I’m guessing you’ll get something like this:

scope.eval(“a = 5”) // 5
scope.eval(“a = 6”) // 6
scope.eval(“a”) // 5

That’s because once a has been assigned a value it becomes that value and can no longer be reassigned, so the second expression here is basically 5 = 6.

@wildthink
Copy link
Author

Rats! You got me. Have to think on that. Any ideas?

@nicklockwood
Copy link
Owner

@wildthink you’d probably also get rather confusing errors if you try to use a variable before first assigning it. For

scope.eval(“5 * a”)

Instead of “symbol not found” the error would be a type mismatch because * can’t accept a string operand.

@wildthink
Copy link
Author

I figured that and considered it somewhat acceptable. An undefined variable should be an error.

What do you think of having a "macro" option for functions and operators? Meaning, be definition, they get their arguments unevaluated and must explicitly evaluate any args as they choose/need.

@nicklockwood
Copy link
Owner

@wildthink the BoxedValue solution I mentioned would work if you don’t mind having to reimplement the standard operators.

I can knock up an example for you if you like?

@wildthink
Copy link
Author

Thanks but don't put yourself out on my account. How big a job would that be to do them all? Is there a way to annotate a function definition with some meta data? Say, annotate an operator as being "numeric".

@nicklockwood
Copy link
Owner

@wildthink it's no trouble. This is something that really ought to be supported properly, but I've not really had a need for it besides the REPL example. There are probably ways I could extend Expression to support it properly, but I'd have to think about how to do it without breaking API compatibility.

@nicklockwood
Copy link
Owner

What do you think of having a "macro" option for functions and operators

Yeah, that's the kind of solution I had in mind. Adding new symbol types would be a significant breaking change though.

@wildthink
Copy link
Author

I'm looking through AnyExpression for the point where the arguments are evaluated to be passed to the function block to see how it's done.

@wildthink
Copy link
Author

umm, wondering if ParsedExpression.symbols was an ordered set then we reliably get the lhs of the = operator and nil it out.

@nicklockwood
Copy link
Owner

nicklockwood commented Nov 10, 2018

@wildthink I've written a REPL implementation using a BoxedValue type instead. It works pretty well. I didn't have to reimplement any stdlib operators because I'm just unboxing the values and then calling the original implementations.

The only limitation is that this solution only works for the Expression stdlib (math and boolean operators), not for the extra stuff in AnyExpression. That means you can't use String values, for example, and Bool values print out as 1/0 not true/false. I expect you could get all that working with a bit of extra effort.

Here you go: https://github.com/nicklockwood/Expression/blob/boxed-value-repl/Examples/REPL/main.swift

@wildthink
Copy link
Author

Totally awesome. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants