scala-subscript / subscript

9 stars 2 forks source link

Simplify syntax for actor message reception #41

Closed AndreVanDelft closed 8 years ago

AndreVanDelft commented 8 years ago

The current syntax for actor message reception, <<...>>, has three down sides:

We can largely unify the syntaxes, but maybe not totally. I propose the following forms:

  a ~~> s
 ?a ~~> s
??a ~~> s

Here

The first form is for receiving a message from a specific actor a. The second form accepts any actor as sender, and assigns the reference to a. The third form is valid when a is an adapting parameter in the enclosing script.

It should even be possible to have an extra check on the sender, like what is allowed for output parameters:

?a ?if (mySet.contains:a) ~~> s

I got this idea because I was not entirely satisfied with this example that I have used on presentations:

class DataProxy(dataStore: ActorRef) extends SubScriptActor {

  script live = 

    << req: InfoRequest ==> {dataStore ? req} 
            ~~(data   :Data   )~~> {dataStore ? DetailsRequest:data}
            ~~(details:Details)~~> {! sender  ! (data, details)!}
    >> 
    ...

}

Two different kinds of arrows, and the bracket pair: unnice. I would rather write:

class DataProxy(dataStore: ActorRef) extends SubScriptActor {
  var requester: ActorRef

  script live =

    ?requester ~~(req    : InfoRequest)~~> {dataStore ? req} 
               ~~(data   : Data       )~~> {dataStore ? DetailsRequest:data}
               ~~(details: Details    )~~> do requester ! (data, details)
    ...

}

Note that I used the word do at the end. This is another new enhancement proposal (https://github.com/scala-subscript/subscript/issues/41); do is like let, but it creates a normal code fragment (atomic action) instead of a tiny one. Maybe in this case a tiny code fragment would be appropriate as well; I am not sure.

AndreVanDelft commented 8 years ago

For the implementation: I think a ~~> s may be shorthand for _msgFrom:a ~~> s and likewise for all variations of the LHS and the arrow. This is what the compiler should do.

Maybe it would be good to allow for ~/~> as well; the internals of _msgFrom (possibly through some kind of initialization) could decide on what incoming messages to fail with exceptions.

Essentially it should work the same as the current implementation. Since multiple instances of _msgFrom may be active concurrently, they need to know which of them should handle a given message. That depends on the specified receiver, and also on the acceptable data flows in the specified arrows. So the code inside the _msgFrom script would need access to that information. That is certainly possible with a high level method available from the script node, which looks up the dataflow construct one level higher.

We drop the special support of a local sender value. Thus, this proposal is not married to Akka actors any more; it may be applicable to different frameworks as well - I don't know yet.

anatoliykmetyuk commented 8 years ago

Good idea. I think here the theory should become wider than the actors and the actors should become the special case of the theory. For actors are not the only case where two entities exchange data.

If we make an implicit conversion ActorRef => Script the right way, a ~~> s syntax will become available by default.

Maybe we should also generalize the ?a syntax, where the source is unknown till the transmission takes place. It could then be used in constructs like a ~~> b, b = ?source ~~(data)~~ [handler], that is ~~> automatically transmitting the data inside the RHS script, if it is a script call on the RHS.

A ready use case for the latter: the SubScriptApplication convenience class has the method live which is an entry point, but the Java/Scala entry point is a main(args: Array[String]) - it accepts input. In some SubScript programs, I’d also like to accept some input. For this, we’d need to redefine the live as live(args: Array[String]) but it will break the API and cause the need to rewrite the examples to use the live(args: Array[String]) version.

If ?a syntax is implemented, however, we’d be able just to feed the args from the main to the abstract live with ^args ~~> live. Then, if the user wants actually to accept some arguments, they will write live = ?source ~~(args: Array[String])~~> theirCode. The listener ?source will wait for the args, then proceed. If the user doesn’t want to accept any input, they will just write live = theirCode as usual, without handling any data.

In general, I think scripts shouldn’t be like methods in that they accept their arguments only at the invocation time. They would look much better if they could accept anything at any point of their lifecycle.

On Apr 23, 2016, at 21:03, André van Delft notifications@github.com wrote:

The current syntax for actor message reception, <<...>>, has three down sides:

it is a bracket pair with potentially large contents. I have put much energy in SubScript fighting the need for such bracket pairs, because looking up a related bracket far away hinders code understanding it is in principle nice that such message reception may be nested using the brackets, but the sender is only available for the innermost message. there is semantic overlap with the current dataflow syntax, but syntax is quite different. For dataflow the alternative composition uses +~~> arrows, whereas for message reception each alternative starts with case We can largely unify the syntaxes, but maybe not totally. I propose the following forms:

a ~~> s ?a ~~> s ?!a ~~> s Here

> stands for arrow variations, with alternative flows (+>) and parameter specifications ((p:AType)>). But without arrows that have a / inside, which do the exception handling, since there are no such exceptions

a is a simple Scala expression (as already allowed as script expression term), but having static type ActorRef

The first form is for receiving a message from a specific actor a. The second form accepts any actor as sender, and assigns the reference to a. The third form is valid when a is an adapting parameter in the enclosing script.

It should even be possible to have an extra check on the sender, like what is allowed for output parameters:

?a ?if (mySet.contains:a) ~~> s I got this idea because I was not entirely satisfied with this example that I have used on presentations:

class DataProxy(dataStore: ActorRef) extends SubScriptActor {

script live =

<< req: InfoRequest ==> {dataStore ? req} 
        ~~(data   :Data   )~~> {dataStore ? DetailsRequest:data}
        ~~(details:Details)~~> {! sender  ! (data, details)!}
>> 
...

} Two different kinds of arrows, and the bracket pair: unnice. I would rather write:

class DataProxy(dataStore: ActorRef) extends SubScriptActor { var requester: ActorRef

script live =

?requester ~~(req    : InfoRequest)~~> {dataStore ? req} 
           ~~(data   : Data       )~~> {dataStore ? DetailsRequest:data}
           ~~(details: Details    )~~> do requester ! (data, details)
...

} Note that I used the word do at the end. This is another new proposal; it is like let, but it creates a normal code fragment (atomic action) instead of a tiny one. Maybe in this case a tiny code fragment would be appropriate as well; I am not sure.

— You are receiving this because you are subscribed to this thread. Reply to this email directly or view it on GitHub https://github.com/scala-subscript/subscript/issues/41

AndreVanDelft commented 8 years ago

I do not completely get your point. Could you give a complete command line application (5 to 10 lines long), old style vs new style that you now propose, so that I can compare the two?

anatoliykmetyuk commented 8 years ago
import subscript.language
import subscript.Predef._

import subscript.SubScriptApplication

object Main extends SubScriptApplication {
  script live = println("Hello, ???")
}

vs

import subscript.language
import subscript.Predef._

import subscript.SubScriptApplication

object Main extends SubScriptApplication {
  script live = ?source ~~(args: Array[String])~~> println("Hello, " + args.head)
}

or vs

import subscript.language
import subscript.Predef._

import subscript.SubScriptApplication

object Main extends SubScriptApplication {
  script..
    live = ?source ~~(args: Array[String])~~> ^args.head ~~> printStuff
    printStuff = ?source ~~(name: String)~~> println("Hello, " + name)
}
AndreVanDelft commented 8 years ago

As for the ?a and ??a syntax: I think these should just be syntactic sugar. The preprocessor currently packs such occurrences in ActualOutputParameter, ActualConstrainedParameter and ActualAdaptingParameter. This is done when the expression is inside a script parameter list, but such ?-expressions etc (also with ?if and ??) should also be allowed as terms in script expressions. I think that is not supported yet, so I just created #43.

Then I think for the context of Akka actors we just need to write an implicit script that has an ActorRef as an adapting parameter:

implicit script convertActorRefToScript(?a: ActorRef): Any = ????

I am not sure yet what should be inside ????. As I wrote earlier, this will want to access the dataflow arrow, to see what kind of parameters are accepted in the partial function/script. This is a runtime decision, done in SubScriptActor: callHandlers.collectFirst { case handler if handler isDefinedAt msg => handler(msg) } I am also not sure how easy this access would be to implement.

And maybe a special declaration annotation for convertActorRefToScript could be possible, to ensure at compile time that such calls can only occur at the LHS of a dataflow.

Then I think we do not need any specific syntactical handling for actors any more.

AndreVanDelft commented 8 years ago

About your new code snippets: I still don't get it. Could you first update the code with declarations for the source variable? In the third snippet: do you mean 2 different variables? What should their values become at runtime?

AndreVanDelft commented 8 years ago

Assuming there is no real need for ?source in the printStuff definition, I rather write that as a normal script. Note that the simple arrow ~~> at the right on the first line expects a parameterized lambda at the RHS. So is printStuff if you don't specify the actual parameter.

  script..

    live = ?source ~~(args: Array[String])~~> ^args.head ~~> printStuff

    printStuff(name: String) = println("Hello, " + name)

BTW In analogy to what is possible for Scala methods, the latter would almost be equivalent to a lambdaesque declaration:

 def printStuff = (name: String) ==> println("Hello, " + name)

which is equivalent to:

 def printStuff = (name: String) => [println("Hello, " + name)]

Type is String => Script[Unit] or maybe in the future: String ==> Unit. But maybe we should drop all ==> syntax, or mark it as "strictly experimental".

AndreVanDelft commented 8 years ago

At compile time the search for implicit conversions in the LHS for those parts that constitute the lambda's result, should ideally not just be for conversions into a normal Script[_], but take into account that a value is required for a dataflow LHS.

At run time everything inside the LHS of a dataflow arrow should be able to easily access the partial function that is implied by the dataflow arrow.

AndreVanDelft commented 8 years ago

It is even a bit more complicated:

normally, the LHS of ~~> must yield a result of a type that is acceptable for the partial function that is defined by the arrow; or the arrow may just define values, while the types thereof is inferred.

an implicit conversion for an ActorRef will want to do things differently; it wants to make sure the message is only received when that yields data accepted by the dataflow arrow. Maybe this can be enforced by a macro annotation.

It is comparable to the classic key script. This only wants to accept a KeyPressed event when the actual parameter is an output parameter, or, in case it is constrained, when the constraint matches. This behavior is implemented with the help of wrappers such as ActualConstrainedParameter. For the LHS of dataflows we need something similar, but this will be more difficult since there is some distance between the arrows and the LHS. E.g. we would even want to enable:

[a1 + a2] ~~> s
AndreVanDelft commented 8 years ago

I think I have a good part of a solution:

We create a new type/class/trait: subscript.DataflowSource. The compiler will eventually rewrite this to Any.

An implicit conversion SomeType => Script[DataflowSource] will only be carried out if either

A dataflow arrow normally does some type checking: normally the LHS should have a result type that is a subtype of the things that the partial function of the arrow expects. However, if the LHS result type is DataflowSource then this check is not done.

To compensate, the DataflowSource conversion script should at run time query an API (TBD) about the dataflow arrow; and what types are acceptable, so that it only performs its receive action if handler isDefinedAt msg.

The overall effect should be similar as with the key script, see my previous comment.

AndreVanDelft commented 8 years ago

I am not sure anymore whether this is a real solution. Is it possible to create for instance

implicit script convertActorToMessageReceive(??a: ActorRef): DataflowSource

so that this can have anything as a result value? Even if we will rewrite DataflowSource to Any, would we be able to set the result value to whatever we want? I think so, possibly with casting, but we need to try this out.

anatoliykmetyuk commented 8 years ago

I have implemented all the functionality except ?!a ~~> s (see updated PingPong). What exactly should ?!a do? If it has something to do with the AdaptingParameters, what exactly are they, why can't we do with just OutputParams? What is the generic behaviour of ?! syntax, outside the scope of actors?

AndreVanDelft commented 8 years ago

That is my fault; long ago ?! denoted adapting parameters; now it is ??. I will update this in the previous text.

anatoliykmetyuk commented 8 years ago

Can you please provide a full example of their usage? In scope of actors and outside of it. I understand that OutputParams are like C++ pointers, but I have a very vague idea of the AdaptingParams. On May 12, 2016 4:16 PM, "André van Delft" notifications@github.com wrote:

That is my fault; long ago ?! denoted adapting parameters; now it is ??. I will update this in the previous text.

— You are receiving this because you commented.

Reply to this email directly or view it on GitHub https://github.com/scala-subscript/subscript/issues/41#issuecomment-218753524

AndreVanDelft commented 8 years ago

A few weeks ago I read a piece on (or via) Reddit or so, that argued that programming languages should provide full refinement support. This is a good principle. SubScript (as well as its predecessor) does so with, among others, adapting parameters.

E.g. there is the key script in LookupFrame2, which adds a parameter to the library key script:

implicit script vkey(??k: Key.Value) = vkey2: top, ??k

I don't have an example for dataflow at hand, and in particular not for actors, but there will surely come up one at some time.

AndreVanDelft commented 8 years ago

This was the article. Excerpt:

The pastiche test

May 1, 2016

I’d like to coin a term for an old but (to my knowledge) previously nameless idea. When considering the primitive constructs of a programming language, there is a litmus test for evaluating the quality of their design: can I implement a wrapper on top of the construct that looks and behaves exactly the same as the original, modulo the name? Passing this test is a good property for a language construct to have; it means the programmer has the ability to substitute the construct with their own equally powerful ones.

When designing a programming language and you find yourself tempted to add a new construct, ask yourself: can the programmer build similar constructs on top of this? Does it pass the pastiche test?

anatoliykmetyuk commented 8 years ago

Good test. But still doesn't explain much about the ?? vars. They don't add to the refinability as far as I can see.

From the key example, though, I see why they don't work the way I implemented them: you're supposed to pass another formal param to their constructor and not a local var. Strange...

AndreVanDelft commented 8 years ago

I did not mean "?? vars". But there should be ?? operands. E.g., suppose the previous implicit script vkey is in scope, and we want to call it giving a prompt:

script promptKey(??k: Key.Value) = @{prompt: _k}: ??k

where prompt is a method that gives an appropriate prompt, based on the given actual parameter holder.