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

Add #87: Guards #27

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open

Add #87: Guards #27

wants to merge 1 commit into from

Conversation

nmichaels
Copy link
Member

No description provided.

@natebragg
Copy link
Member

I expressed some of my thoughts over gitter, but I'll summarize:

It would be good to make a syntax that could later be subsumed by a more general meaning. Namely, treating guards as a type of machine. Implementation-wise this could be hacked in first,
and the meaning wouldn't change later when the semantics were expanded.

As a side note, I'm particularly allergic to the idea of order mattering. No matter what we do I would rather enforce the idea that cases must be disjoint---though they may all be false, only one may be true. Because that's undecidable at compile time, if more than one is true, then the one that runs is undefined.

My motivation comes from Smalltalk, where ifTrue:ifFalse: is method of the boolean class, whose true member calls the first continuation, and whole false member calls the second. That is, it is called like (a < b) ifTrue: ... ifFalse: ... (Smalltalk method name syntax splits on colons).

I feel like there is a solution here using submachines. We need two things: delegating the received event to a predicate, and then selecting how to continue thereafter.

Delegation can still be made to a C function. Eventually it would be nice to be able to delegate to a machine, but we aren't there. The C function could return true/false, or send an event. I prefer the latter, because it preserves the FFI semantics at virtually no runtime cost, although it complicates the required order of execution.

Continuation can be done in the same spirit as Smalltalk: by deferring to another machine. I think the main hangup here is concrete syntax, so I'm going to wander off on a tangent here.

Currently, we lack good concrete syntax for sending events to machines, and for getting responses. Sending is postfix, like a method call (which is an apt analogue; in spite of events carrying data, each could be regarded as a method whose data are its arguments). However, that's for concrete events. We have no syntax for variable events. This isn't bad (consider the case of point-free functional programs, or stack-based languages like Forth), but it means that the only place an event is available is as an implicit argument within a side-effect list. Side-effects are either function calls (which may ignore the implicit argument) or events (which universally ignore it). So, if we want execution to depend on an event, it ought to be within a side-effect.

I propose hijacking the arrow and become syntax (forbidding nested side-effect lists, at least for now), overloading them so that within a side-effect, the arrow points always to another machine (a named one or an anonymous one) and denotes capturing whatever queue was filled by that side-effect, and giving it to that machine. To stick to the running example, I'm thinking something like this:

check_voltage (@read_voltage)
[
    voltage -(
        @v_high ~~> { _ [ true ~~> overvolt ] },
        @v_nominal ~~> { _ [ true ~~> continue ] },
        @v_low ~~> { _ [ true ~~> undervolt ] },
    )-
]

Alternately:

check_voltage (@read_voltage)
[
    voltage -(
        @dispatch_voltage ~~> { _ [
            voltage_high ~~> overvolt,
            voltage_low ~~> undervolt,
            _ ~~> continue
        ] }
    )-
]

While this has a bit of a funny syntax, it would have several benefits:

  1. It allows order to matter in a way we have already defined.
  2. The syntax looks vaguely similar to existing switch or case statements.
  3. It generalizes nicely
  4. It would require scant new syntax

Thoughts? Rebuttals?

@nmichaels
Copy link
Member Author

That's a lot of stuff. Do you think that returning a state in the calling machine is a thing we're going to have in submachines? That seems...not right to me.

@nmichaels
Copy link
Member Author

nmichaels commented Oct 3, 2019

My comment on the "Alternatively" approach is that I don't think it adds a lot to what we already have. The impetus for this is to make conditional dispatch simpler to express in Smudge, and that option still requires the user to call a nebulous @function that may or may not send all the appropriate events under the appropriate conditions.

Granted, it makes it more explicit which events ought to be sent by that function, but I don't think that's enough of an improvement over the existing syntax to justify a change.

@natebragg
Copy link
Member

natebragg commented Oct 5, 2019

First, to quickly address your comments about the alternate version, that was just demonstrating that a submachine solution could work either way.

Now, regarding returning states. I agree, and I wasn't sure how to go about that. I was more feeling out than advocating. We need two things: 1) a way to choose which events to send, and 2) a way to choose which state to transition to. 1 is simpler, and I feel like many different approaches could be satisfactory. 2 is harder to do well without events. To start, I believe that to do this "right" requires subverting the queue semantics by hijacking new events. That is, the decision must be made based off of information local to the event handling, not information available to the whole machine, like a local queue. Submachines can do that. But returning a state violates a kind of abstraction barrier.

My ambition here is to generize this problem in order to create an equivalent to the case expression. Maybe some kind of event pipeline syntax that makes a local queue? Perhaps the pipe gets to preprocess the queue and act on it, and anything not processed winds up in the machine's queue. Something like your original proposal, but expanded to chains and arbitrary events. This could also cover responses, by the way. To tweak my syntax above to remove the submachines, I'm thinking something like this:

voltage -(@v_high)-> [ true --> overvolt]
        -(@v_nominal)-> [ true --> continue ]
        -(@v_low)-> [ true --> undervolt ]

Or equivalently,

voltage -(@dispatch_voltage)-> [
        voltage_high --> overvolt,
        voltage_low --> undervolt,
        _ --> continue
    ]

This would be like a "local state", one the machine couldn't normally wind up in, but can occupy temporarily in order to make a local decision. The syntax chains arrows, and the semantics eats the in-scope event and any local queue.

Is that an improvement?

@natebragg
Copy link
Member

Oh, even better name for that last syntax proposal: "substates".

@natebragg
Copy link
Member

Now I'm wondering if there is a way to have a syntax sugar over that, like if p then t else f can be sugar for case p of true -> t; false -> f. "Substates" could semantically underlie a terser guard syntax similar to your original proposal. Thoughts?

@nmichaels
Copy link
Member Author

nmichaels commented Oct 7, 2019

Alright, I like the substate thing, and adding sugar to the first of the 2 substate proposals is a good approach. I'm not a huge fan of the bit where we add events though. What if the user wants an event called true? Maybe we can scope the event to just the substate? Submachines inherit the events of their parents, so that would be a difference from a special case of submachines.

I think it's worth while to also include a default case, maybe named _?

Here's a suggestion for the sugar as a starting point:

   voltage ?v_high --> overvolt 
           ?v_nominal --> continue 
           ?v_low --> undervolt

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