4

I have a python library which builds special iterators (a behavior tree) out of nested function calls. While the API has a fairly nice and light-weight syntax (due to it being python), it could really use a declarative DSL.

Here's a rough sketch of what I'm envisioning:

The DSL (using YAML):

tree:
  - sequence:
    - do_action1
    - do_action2
    - select:
      - do_action3
      - sequence:
        - do_action4
        - do_action5
      - do_action6

would result in the following nested function calls:

visit(
    sequence(
        do_action1(),
        do_action2(),
        select(
            do_action3(),
            sequence(
                do_action4(),
                do_action5(),
                ),
            do_action6(),
            )
        )
    )

I'm having trouble visualizing exactly how to do this. Because the DSL must represent a tree, a simple depth-first traversal seems appropriate. But in order to build the nested function calls, I have to turn this inside out somehow. It probably involves something clever with an intermediary stack or some-such, but I can't quite grasp it. What's the correct way to perform this transformation?

SashaZd
  • 3,315
  • 1
  • 26
  • 48
David Eyk
  • 12,171
  • 11
  • 63
  • 103
  • "it could really use a declarative DSL"? Really? Your Python code is *more* readable than your DSL. How did the DSL help? – S.Lott Sep 08 '11 at 22:00
  • In this *very simple* example, the parentheses especially bug me. In a more complicated tree, especially when keyword args are involved, requiring children (`*args`) to come before options (`**kwargs`) severely degrades readability. The [link to the real-life example](https://github.com/eykd/owyl/blob/master/examples/boids.py#L281) demonstrates this--the `policy` argument to the `parallel` node is very easy to miss. – David Eyk Sep 08 '11 at 22:07
  • 1
    If the "real life example" actually shows the problem, then perhaps you should include something like that here. DSL's are an attractive nuisance. I have not seen one offer any value. If you have an example, please fix this question to show some compelling reason for the added complexity. – S.Lott Sep 09 '11 at 02:01
  • The question isn't about the merits or demerits of a DSL. A more complex example would detract from the real question of how to transform the parse tree into a specific construct. – David Eyk Sep 09 '11 at 04:54
  • Imagine that select, means "do_one_of", the python as written actually calls all the children, then returns one of the results. You may actually mean do only one of the actions. In that case the python becomes significantly uglier (IMO) than the DSL. – Michael Anderson Sep 09 '11 at 11:27
  • @Michael: Well, a `select` is actually more like an `if/elif` construct, but I guess I don't see how this affects the aesthetics of the example. Anyway, the DSL design is by no means pinned down--hence a "rough sketch". – David Eyk Sep 09 '11 at 17:20

1 Answers1

3

I think you could let python keep track of the function calls and parameters instead of doing it yourself with a stack.

Suppose you have a YAML parse tree in which each node represents a function call and each child of this node is a parameter (which is also a function call, so it could potentially have its own parameters).

Then define the function evaluate, which evaluates a node of this tree, as follows (pseudocode):

def evaluate(node):
    # evaluate parameters of the call
    params = []
    for child in node:
        params.append(evaluate(child))

    # now make the call to whatever function this node represents,
    # passing the parameters
    return node.function.call(*params)

Finally, call evaluate passing the root of the YAML tree as the parameter and you should get the desired behaviour.


A slightly different eval-apply structure

def evaluate(node):
    # evaluate parameters of the call
    params = [ evaluate(child) for child in node ]

    # apply whatever function this node represents
    return node.function.call(*params)
S.Lott
  • 384,516
  • 81
  • 508
  • 779
Jong Bor Lee
  • 3,805
  • 1
  • 24
  • 27
  • Why yes, that might just do it. I don't know why this was so hard to visualize. – David Eyk Sep 08 '11 at 23:12
  • 1
    "why this was so hard to visualize". Yet another example of why DSL's don't help much. The Python was easy to visualize. Stick with easy. – S.Lott Sep 09 '11 at 02:03
  • I'm quite convinced that you have a low opinion of DSLs, and I truly hope you never find yourself in need of one. Suffice it to say that I want *my* bike shed to be painted blue, and I'm off to the paint store right now. – David Eyk Sep 09 '11 at 04:57
  • It's difficult, zero value, and still very important. Interesting. This is, BTW, exactly how Python does expression evaluation. – S.Lott Sep 09 '11 at 10:20
  • You contradict yourself. It's of zero value, yet I just learned exactly how Python does expression evaluation. Do you mean to tell me that learning foundational concepts of computer science by application is of zero value, or that in 30 years of programming experience, you've never built anything remotely resembling a compiler, or never found any value in the building? Please, I appreciate you taking valuable time to try and help, but I'm trying to learn here. I may yet come to a similar conclusion about DSLs, but I need to come to my own conclusions on the matter. – David Eyk Sep 09 '11 at 16:22