not that this works...

Two lexical curiosities for you: one that doesn't work at all, and one that works... sometimes. I'm not claiming that either should work mind you... okay, maybe the second one, but, then, I have ulterior motives.

The first curiosity is in Lua, the second is in C++.

According to Programming In Lua, a function declaration:
function curious(test) print( test ) end
is actually syntax sugar for:
curious = function (test) print( test ) end
The result of that behavior means, regardless of which of those two ways you use, the name "curious" winds up as string in the global environment table pointing to the function object. A call like:
_G["curious"]( "this works" ) -- prints "this works" successfully
_G.curious( "this works" )     -- you could also do this, of course.
pulls the function from the globals table, and calls the function perfectly fine. Now, take a look at what it looks like when you define your own table:
t = { not_curious = 5 }
print ( t["not_curious"] )  -- prints 5
print ( t.not_curious )      -- you could also do this, of course.
That's very straightforward. Likewise, there's nothing special about the global dictionary, so we can store functions in our own dictionary as well:
d = { curious = function( test ) print(test) end }
d.curious( "this also works" ) -- prints "this also works" successfully.
That's all well and good, but notice anything interesting? That inner part of the "d" is exactly like the syntax sugar declaration from above:
curious = function (test) print( test ) end
Sure there are some bracket around the outer part, but could we use the syntax sugar form in a dictionary?
d= { function curious(test) print( test ) end }
If anybody would know, it's the interpreter... and here's what it says:
Lua 5.1.4  Copyright (C) 1994-2008 Lua.org, PUC-Rio
> local d= { function curious(test) print( test ) end }
stdin:1: '(' expected near 'curious'
If the syntax sugar was done first, similar to C-macro expansion, then I think it would have worked. But, obviously, something a little more complex is going on under the hood. I suspect it's a conflict with the rules allowing for the more common way of assigning functions to dictionaries:
d= { }
function d.curious(test) print( test ) end
-- expands to d.curious= function .....
I won't lose any sleep over it, it's just a little curious.

Okay, then, enough of that: on to the C++.

The first version of hsm-statechart I wrote was in C++, and I played around with a bunch of different ways of nesting classes. It has often been remarked that states looks like classes*, and it occurred to me that hierarchical machines might therefore work well as nested classes:
struct Outer {
   struct Inner {
      struct Leaf {
      };
   };
};
One of the key-pieces of making that work, though, would be the ability for Inner to know about Outer in a transparent way so that any state could register itself with its parent without writing special code.  That's possible in Java, but not in C++, except.... under some compilers. So here's the lexical curiosity. In C++ it's perfectly legal to have typedefs inside classes. Doing so scopes them to that class.
struct Outer {
   typedef Outer Type;
};
Type foo; // no good, Type isn't in the global namespace
Outer::Type foo; // this is what you need to do.
For nested classes this is interesting, because an inner class inherits the names of an outer class.
struct Outer {
   typedef Outer ParentType;

   struct Inner {
       void inner_foo() {
              ParentType t();  // refers to Outer
       }
   };
};
So, immediately there's potential here that if sub-states were written as inner classes, they could know implicitly who their parents are. Inner, doesn't care who Outer is, it can refer to it via ParentType.

You can carry this forward one more level, and ask the question: what happens when you nest the same typedef name in different classes? Each class has its own scope, so again that's perfectly fine:
struct Outer {
   typedef Outer ParentType;

   struct Inner {
       typedef Inner ParentType;

       struct Leaf {
          void leaf_foo() {
             ParentType t(); // refers to Inner
          }
       };
   };
};
But, of course, Inner just lost its typedef access to Outer:
struct Outer {
   typedef Outer ParentType;

   struct Inner {
       typedef Inner ParentType;

        void inner_foo() {
           ParentType t(); // oops.... this doesn't refer to Outer, it refers to Inner.
       }
   };
};
So, here then is the curiosity.  Can't we just "save" the typedef  off in another typedef before we re-declare it? Shouldn't the following -- admittedly crazy, but potentially useful -- declarations work:
struct Outer {
   typedef Outer RecursiveType;

   struct Inner {
       typedef RecursiveType ParentType;  // ParentType = Outer
       typedef Inner RecursiveType;

       void inner_foo() {
            ParentType parent(); // refers to Outer
       }

       struct Leaf {
          typedef RecursiveType ParentType;  // ParentType = Inner
          typedef Leaf RecursiveType;

          void leaf_foo() {
             ParentType parent(); // refers to Inner
          }
       };
   };
};
The answer is, it depends on your compiler. I've tried two :) gcc, and cl ( visual studio ).

In Visual Studio, it works.  The compiler pass trucks along in-order. It assigns the Inner scope a ParentType matching the current value of RecursiveType, then assigns the Inner scope a RecursiveType of Inner. And so on down the chain.

gcc, however, seems to go out of the way to stop this from happening. As of gcc 4.3, it  will complain saying "Changes meaning of RecursiveType...". And by complain, I mean won't compile. As others have pointed out, that can be painful when trying to alias templates in a nice way. Especially, since, it used to work(!)

 "-f permissive" is supposed to re-enable the old behavior, but this was an indication to me, perhaps I was being a little too crazy.  Especially, since, even though I was able to use the behavior to do interesting things ( such as: determine the depth of a node via recursive enums ) it's fundamentally messy to be forced to include your entire statemachine in one place. Yes: you can use #includes to inject states from other files, but the code dependencies would be a mess. hsm-statechart's builder is way more sane.

--------------

*Someday I'll write a proper rant about how Mealy machines, which have actions on transitions are indeed class-like in their method calls, but Moore machines, which have transitions actions on enter and exit, are more like co-routines than classes with constructors and destructors, while Harel statecharts -- which have properties of both -- are neither precisely class like, nor co-routine like, and therefore need something... else...

0 comments: