← Back to context

Comment by pg314

8 years ago

Since if-statements are built out of blocks and message sending, you can easily do some cool things. One of them is building up an abstract syntax tree (AST) of an expression without parsing that expression.

E.g. suppose you want to build up an AST of the statement

  (x < 0) ifTrue:[-x] ifFalse:[x]

Instead of starting with an x of type Number, you would start with an object of type ASTVariable, that responds to the message < by creating and returning an object ASTLessThan instead of the Boolean True or False. When that object is sent the ifTrue:ifFalse: message it evaluates both branches to create the ASTs for both branches and then creates an ASTIfStatement.

As an sexp:

  (ASTIfStatement
    :condition (ASTLessThan #1=(ASTVariable :name x) (ASTConstant :value 0))
    :consequent (ASTUnaryMinus #1#)
    :alternative #1#)

I've seen it used in a number of embedded languages in Smalltalk, such as SQL.

Something quite similar is used in some RethinkDB drivers, especially around lambdas. For example:

    r.table('users').filter(lambda user:
        user["age"] == 30
    ).run(conn)

The lambda there is actually only ever called once, on the client-side, with an equivalent of your ASTVariable, to generate an AST of the comparison which is sent to the server and processed there.

This technique is limited by the fact that it only works if all the messages are sent to x, rather than x being sent some message.

  • You mean "rather than x being sent to some message"? E.g. 0 > x wouldn't work, because the message #> is sent to 0 with x as an argument? In a case like that you can still make it work by exploiting the fact that arithmetic and comparison operators implement double dispatch. The implementation of > for an Integer would send a message to x:

      Integer::> aNumber
        ^x adaptToInteger: self andCompare: #<
    

    Even if x is passed as an argument, to interact with it at some point somebody has to send a message to it. I agree that it can become unwieldy to intercept all possible messages, but for a well-defined subset in your DSL, this can usually be done.

Is this a bit like a runtime macro? Does it have performance disadvantages?

  • > Is this a bit like a runtime macro?

    You can indeed accomplish some similar things with it.

    > Does it have performance disadvantages?

    A Common Lisp macro is evaluated once at compile time. In Smalltalk this is evaluated at runtime, so that adds some overhead. Usually you only have to build the AST only once though, and can use the result many times, so that overhead isn't really relevant.