-
Notifications
You must be signed in to change notification settings - Fork 64
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
C syntax for referencing parameters and state variables #82
Comments
In order to avoid An alternative to preprocessing the target language (and all target languages eventually) could be to allow to escape from the target code in order to reference to a variable defined in Lingua Franca. This could look something like this:
Then the code generateor could generate the proper code for accessing this variable in the target language. This would also add the benefit that the LF compiler would be able to verify that the reaction is actually allowd to access this variable without relying on scoping mechanisms in the target language. Of course this would require to find a proper escape charater that is unlikely to be used in any of the possible target languages. |
This is a good idea, but it would still require parsing that understands to exclude comments and strings, so I'm not sure it buys that much over just parsing the language. The minimal parser would just tokenize with an understanding of comments and strings, which is almost the same parser that would be needed in this case. |
I am not sure why it would require to understand comments and strings. Do you mean to avoid accidental matching of the escape charater in comments and strings? In the case of preprocessing the entire target code block, I don't believe that understanding comments and strings would be sufficient. The preprocessor would require a deeper knowledge of the language. Consider this case in C where there is a struct with a member called name:
Simple replacement based on tokens would break this code. It requires deeper knowledge of the language to get it right. This is probably dooable with reasonable effort for C, but I think it can become arbitrarily complex in other languages. |
Good point. I guess macros will have the same problem? |
In the TypeScript runtime, there would be no problem admitting arbitrary objects as a parameter of the Because of this, we just pass in a reference to the parent (a Reactor) of the In order to gain visibility into the members of the parent, which must subclass
I.e., we refer to I personally don't feel it's so terrible to refer to state variables via the |
Yes I am also good with using BTW: Instead of this:
one can also write this:
So using |
Here's another suggestion: since we're declaring state using the keyword |
I don't think 'state' will work well because we also need a syntax for parameters. |
Right. How about |
This is just my personal preference, but I still think While we are at it: I was always wondering why |
I agree, which is why I wanted to get rid of
vs.
The latter can only be done by making |
We can eliminate the use of
This approach also ensures that every reaction references to primitive state variable (i.e, of type It would be silly to write a hand-written reactor in this style, however: one would have the state as a public member of the reactor and reference it directly (as I showed in the previous example). These two approaches are compatible though. So, should we decide that in LF source code we don't want reactions to need to refer to |
I forgot to add code-generated lines at the end of Here's a corrected example:
|
But this fix doesn't take in account that the reaction code could possibly return... We could possibly use a closure to catch this without inspecting the code, but that most certainly poses difficulties to the type checker. So I take back what I said; I'm not entirely sure we can make this work. |
One reason to justify this is that the interface of the reaction declares explicitly the things that it can reference in its local scope; we don't do that for state and parameters, so why would we expect them to be locally accessible? The rationale is no different from OOP, actually, where a member function directly references its arguments but has to use I'm starting to get convinced that we should probably just leave things as they are... |
In Java, there is no need to use this to access object members.
Edward
…------------
Edward A Lee
Professor
UC Berkeley
On Jan 10, 2020, at 8:59 PM, Marten Lohstroh <notifications@github.com> wrote:
While we are at it: I was always wondering why self is used for state and
parameters but not for ports and actions. I understand that this comes from
the way things where developed in C (avoid copies of state and parameters),
but in my opinion it would be a more consistent design if either everything
is accessed via self or everything is accessed without such an indirection
(or with an escaping mechanism).
One reason to justify this is that the interface of the reaction declares
explicitly the things that it can reference in its local scope; we don't do
that for state and parameters, so why would we expect them to be locally
accessible? The rationale is no different from OOP, actually, where a
member function directly references its arguments but has to use this in
order to refer to member fields of the object that it is a member of.
I'm starting to get convinced that we should probably just leave things as
they are...
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
<#82>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ACA6ONR7PHHRZJEZRDGBTZDQ5DHRDANCNFSM4KEC5ONQ>
.
|
True. But I personally think it is bad style not to use Java's ability to refer to class variables without |
Doesn’t JavaScript have try...finally?
This is missing in C, unfortunately...
Edward
…---------
Edward A. Lee
EECS, UC Berkeley
eal@eecs.berkeley.edu
http://eecs.berkeley.edu/~eal
On Jan 10, 2020, at 8:42 PM, Marten Lohstroh ***@***.***> wrote:
But this fix doesn't take in account that the reaction code could possibly return... We could possibly use a closure to catch this without inspecting the code, but that most certainly poses difficulties to the type checker. So I take back what I said; I'm not entirely sure we can make this work.
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub, or unsubscribe.
|
It does, but how did you imagine using it to prevent a |
Put the coda in the finally clause. |
You can only return in a function. If you're in a function you're in a different scope. I don't see how this helps... What you can do, as I suggested earlier, is use a closure. I didn't think TypeScript's type inference was strong enough to preserve our ability to ensure that the types of the ports and the types of the arguments in the
So, assuming that we would be able to get this to work in TypeScript, what would the solution be in C? |
I don't understand. Why won't this work:
|
Ah, I didn't realize that finally also caught returns. I was under the assumption that it would only catch exceptions... In other words, I expected the following code to print nothing:
But it prints "never to be seen", so you must be right :-) This is a neat solution. The open question then remains: how do we do it in C? |
We could use |
longjmp is extremely dangerous. It can leave inconsistent state and cause memory leaks.
I personally like best referring to parameters and state simply by name. This seems the most natural and readable to me. We could implement this with macros for now, with a "to do" item to add a parser. I think we are going to want to add a parser eventually anyway to get a better editing/IDE experience. I don't think it's really necessary for the syntax to be identical across target languages. I don't think you will find many programmers writing reactors simultaneously in multiple languages. Those that do will be facing bigger cognitive dissonances than this one. |
I agree with all the points you make. However, I would be more inclined to keep things the way they are now, and add the macros only after we've mixed in a grammar for C and are sure we can do it safely. Once we have that, all sorts of things can be done to make the programming experience more fluent; we could do away with the |
OK, let's proceed that way. Moving from "self->statename" to just "statename" can be done in a backward-compatible way, whereas going the other direction cannot, so let's leave it the way it is for now. |
I implemented the try, finally approach to parameter and state naming in the TypeScript generator. An example generated react function looks like this:
It occurred to me that my scheme of prefacing state and parameters with a "__" in the react function signature would fail if you happened to have two state variables named for example "foo" and "__foo" because then you couldn't declare the prologue variable "__foo" for state "__foo" without conflicting with "__foo" in the signature for state "foo". Any thoughts on adding a check in the validator to prohibit naming state and parameters with an initial "__"? I think this would prevent various other potential problems in the TS target like naming a state variable "__parent__". |
I could also sidestep the underscore issue entirely if I were to write state and parameters into a state object and pass that in as an argument. So my example would become.
I think I should just do this. Then we just have to prohibit "__stateVar" or whatever I wind up naming it. |
It sounds reasonable to me to prohibit a "__" prefix in the names of ports, actions, timers, state, and parameters. |
I'm not sure that the type inference that TypeScript does is clever enough to correctly infer the type of a single state object that holds all the stuff. Have you confirmed that things get typed correctly with this approach? And if so, are the type errors readable when something is off? |
Also, shouldn't the ports in the signature be prefixed with a double underscore? I would expect we treat state and ports equally. In which case, lumping together the state doesn't solve any problem at all. |
I’m in favor of prohibiting __ prefixes across the board. |
I can confirm the type inference is okay with either of the two options I gave above. It catches errors with reasonable messages. If we do get rid of the __prefixes, and based on your feedback it sounds like I should go ahead and do that, I'll keep the original "add an underscore to everything" scheme. It's simpler. Regarding ports, I thought it might be best to avoid including them in the try finally trick because unlike state, we don't want to call |
As for the epilogue: we can just check whether the variable is defined and only call set on those that have indeed been assigned a value. As for the prologue: we can do |
I like explicit sets and gets on ports because it makes it clear you're preforming an operation on a port. Usually when I make an assignment to a variable I don't expect that assignment to result in a function being called, so there's counterintuitive to me about this. The really annoying case for state was when you wanted to increment it because you had to write Is undefined a valid type for ports? Because we'd have to prohibit it if we wanted to do the "set if it has indeed been assigned a value" strategy. |
I don't see the point of treating ports and state differently. We also don't use I was OK with not having the try/catch, but if we use it, it is my strong preference that we treat ports, actions, timers, state, and parameters the same and define them all directly as local variables in reaction scope. |
In that case, a local var corresponding to a |
I see this as an issue of providing the author of a react function the most reasonable abstractions for ports, actions, timers, etc. It makes a lot of sense to me to represent state and parameters as local variables because they're really just variables that belong to the reactor. Ports are a bit more complicated because a port is conceptually an object you can interact with. Sure, they're like variables in that they carry values, but there's a semantic distinction between writing a value and sending a message. For example
means something different than
because in the first case it's clear you're sending two messages, and in the second case you've staged the value 1 before changing your mind and staging the value 2. Similarly, an import port is an object you can ask multiple questions to: did you receive a message (what used to be That said, I understand that recent changes you've made to the reactor-ts API have changed ports so they can be encoded into variables: Using the value null to represent an absent value on an input port to get rid of isPresent(). And the fact that you actually call I'd lump timers and triggering actions into the same category as input ports because they really aren't variables, but can be encoded as such with null representing absent. But schedulable actions definitely can't be encoded as variables because you schedule an action with both a delay and a value. You can't just schedule an action as
because its delay is unspecified. So at the very least we have to leave some actions as non-variables. I can get 100% behind parameters and state as variables because that's where they are. I'm maybe 40% behind encoding everything else as a variable. It seems to have some pros and cons. |
I don't see the conceptual distinction you're making between ports and state. In fact, the way they are currently implemented in the TypeScript runtime is exactly the same. Actions are a different, I agree. They could be dealt with by bringing into scope a |
I think that this discussion is going in the wrong direction. I would like to go back to a suggestion I made earlier (escaping traget code), as I strongly believe that the question of what ports, actions, state, etc. are and how they are accessed should be answered by LF and not by the implementation of each target. LF is the central place where this problem can be solved. If we do it in the target code, we end up with a multitude of solutions where languange A useses appraoch X to access LF primitves and language B uses approach Y, while the compiler of language A can check constraints 1,2 and 3, but the compiler of language B can only check constraints 2,4 and 5. Why should we rely on the target compiler for validating port accesses and the scheduling of actions if we can do this in the Lingua Franca compiler? What I have in mind is something like this (modified from ActionDelay.lf):
Where everything between two Combine this approach with a type-system in LF and you get a really powerful tool. |
This is an intriguing proposal, but I have to admit an aesthetic negative reaction to the syntax. It seems to me that the "right" long-term solution is to integrate a compiler for the target language with the Lingua Franca compiler. In principle, this would not be that hard to do with an LLVM-based compiler, but of course, it would be a tremendous amount of work. Nevertheless, if our language takes of (a BIG if), then that is what should eventually happen. Analogously, the first C++ compilers translated C++ to C. That didn't last long once the language became established. So I would prefer to find a solution that has an eye towards this long term solution, and I believe that that solution refers to inputs, outputs, and state variables simply by name. Also, I'm not that bothered by the syntax of target differing across target languages. These syntax differences pale in comparison to the syntax differences in the languages themselves. |
Since there seemed to be a consensus on this, I committed a change to the validator to block on all targets leading "__" on names for inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiations. I wrote JUnit tests for these as well (although part of the reactor definitions, and reactor instantiation tests are incomplete because of reasons I don't understand yet). @lhstrh I see this as a question of choosing the most concise, intuitive, aesthetically pleasing, etc. framework to LF programmers. Even if ports and state are implemented a particular way, there's no reason we have to expose them the same way to LF programmers. I think there's a case to be made that it's more intuitive to treat some of the entities available inside a reaction as variables and others as objects even if that's a little more verbose. Again, I'm feeling maybe 60% that it's worth it. |
How do we feel about blocking entities named "_" (just a single underscore)? Currently the name "__" is illegal, I think "_" should be too. |
In some languages there is a convention to use the prefix |
I should explain because my last post was a bit confusing. I had just caught a bug in validation that came up when checking for a leading |
Ah, I see. Yes, I am fine with banning the single character |
After some time playing with TS tests I have revised thoughts on naming elements in TypeScript for reactions. When writing reactions using the input.get() or action.get() style, I found myself doing this a lot:
and that However, I did not have a similar frustration with outports or schedulable actions. It's not particularly painful to write
and it's a lot clearer to me that this code is performing an operation on a port than
There's also a case with actions that's particularly hard to handle with assignments to local variables. With z as an action:
Imagine how this would look if we tried turning z into a local variable assigned the value of its payload in a reaction prologue:
Even if we created a utility function to schedule the action, how do we tell that function to schedule z? We can't put z where the question marks are because z is just a local variable that might contain the string In summary I think we we should do local variable assignments for read-only elements, but it's better to leave the objects that need to have actions performed on them available as objects. |
I had a similar problem with the C target and ended deciding to define a variable z_value to represent the value of the action, whereas z represents the action itself. |
After some discussion we settled on having a
Scheduling
|
I believe the discussion involving TypeScript is now resolved. Should we leave this open for C and C++ discussion or are we good to close the issue? |
I think that enough time has passed with the |
Currently, we address parameters and state variables using the syntax
self->name
.I’m finding this rather awkward. I would like to change it to just
name
.Doing this requires using macros, not C variables, because a new local C variable on the stack would be a copy of the parameter or state rather than the parameter or state itself.
I suggest we generate code something like this:
The above
#undef
is necessary to limit the scope of the macro. Otherwise, two reactors in the same file could not have the same state variable or parameter names.Using macros is fraught with peril, and we are already probably doing too much. We can, with some work, replace all the macros with our own preprocessing of the C code. This could be done with a relatively simple parser that understands comments, strings, and tokens. I think we are going to want to incorporate target-language grammars more fully eventually anyway to support a better editing experience in Eclipse.
I've poked around a bit in xtext docs to look for a way to conditionally include a sub grammar within our
{= ... =}
code block delimiters. This would be easy to do if we had only one target language, but since we don't, it is not obvious how to do this. Suggestions?The text was updated successfully, but these errors were encountered: