Re: [akka-user] Re: How can I reconcile untyped actors with typeful programming?
Hi Daniel, The issue is that type systems are designed for local and not distributed computations. Let's look at an example. Imagine an actor that has three states, A, B and C In state A it accepts messages of type X, and when received one, it transitions to B In state B it accepts messages of type X and Y. When X is received, transitions to C, if Y, then stays in B In state C it accepts messages of type Z Now you send to an actor starting from state A a message X. Two things can happen: - X is delivered, so the possible accepted types are {X, Y} - X is lost, so the accepted type is {X} The intersection of those is {X}. Now imagine that you send another message X. Three things can happen: - both X's were delivered, so the accepted type is {Z} - only one of the X's were delivered, the other is lost, so the accepted types are {X, Y} - both X's were lost, the accepted type is {X} The intersection of the above cases is the empty set. So what should be the local type representation of an actor that you have sent two messages of type X? Let's modify the example, and assume that there was no message loss, but let's take the viewpoint of another sender. This sender knows that two X's were sent to our example actor by the other sender. What messages can we send? There are three scenarios: - both X's sent by the other sender has arrived already, so the accepted type is {Z} - only the first X sent by the other sender has arrived yet, so the accepted types are {X, Y} - no X's has arrived yet, accepted type is {X} The intersection of the above cases is the empty set. As you see, without receiving a reply from an actor, the provable type of an actor is usually Nothing, or something useless. Only replies can convey the *possible* type of an actor, and even that cannot be guaranteed if there are concurrent senders. -Endre On Sat, Mar 8, 2014 at 8:08 PM, Daniel Armak danar...@gmail.com wrote: My intent was definitely to throw away type safety. In my example you can wrap any ActorRef using any trait, and you can extract and use the raw ActorRef from a typed wrapper. The only thing I was trying to salvage was for the sender to declare what kind of actor / behavior they thought they were dealing with. But I'm taking your advice to heart. I'll try to use pure actors until I have more of a sense of how problematic various issues are. Thanks! Daniel Armak On Sat, Mar 8, 2014 at 8:59 PM, Derek Wyatt de...@derekwyatt.org wrote: On 8 March 2014 13:31, Daniel Armak danar...@gmail.com wrote: Hi Derek. Yes, I'm aware of the tradeoffs and the reasons for them. It just seems to me there ought to be a middle road. Here's an idea off the top of my head. If remoting is going to be transparent, there won't be compile time type assurance. So let's give up on local type assurance too, and just use types for documentation and refactoring; that still seems a lot better than nothing. Suppose for each actor I wanted to implement, I declare a trait with a method for each message it might receive. Each method must receive exactly one parameter, which is the message, and return Unit: trait Printer { def print(msg: String): Unit } The method name 'print' is just for documentation; only the parameter is sent as the message. This preserves transparency with the actor model; you don't rely on the trait implementation to build the actual message. The user of an ActorRef would use a wrapper (value type around the ActorRef) generated by a macro to implement this trait. The macro would also validate the trait: each method has one argument, returns Unit, and all argument types are distinct. Each method call would be implemented as a send(). val actor : ActorRef = ??? val typedActor: Printer = mymacro.wrap[Printer](actor) // Or is there a way to declare types with macros? I forget. typedActor.print(my message) The macro would add an (implicit sender: ActorRef) argument to each method. The actor itself would extend the Printer trait and implement its methods. Another macro would concatenate them to implement receive: class PrinterActor extends Actor with Printer with TypedActorMacro[Printer] { def print(msg: String): } To use become/unbecome, we could introduce something more complicated. Or, even, use the Printer trait only on the sender side - it would still be useful. This is just a rough outline. Do you think it might be useful, or do you think I shouldn't go down this road and trying to marry akka actors and (actor) types is a futile endeavour? Not futile, but highly suspect. You've barely scratched the surface with the above and much research is way ahead of you here. The ??? you have above isn't exactly trivial to implement, for example. What's more is that you've probably thrown away a ton more features in the process. e.g.: class MyActor(printActor: PrinterActor) { ... } // later... val myActor =
Re: [akka-user] Re: How can I reconcile untyped actors with typeful programming?
Derek, the loadbalancer can remain an Actor[Any] and we can safely wrap Actor[T] with Actor[Any] and cast the reference back to Actor[T] if we trust that untyped actor. That doesn't mean forfeiting type safety in all other cases is way to go. One thing that is more difficult to handle (both using types or documentation) is the fact that we 're not really sure what a reply to a particular message will be or even how much or replies there will be. I would welcome any advances in this area, be it typed channels or something else. At the moment i don't feel comfortable with the current situation, e.g. even with a simplistic application i wrote a couple of months ago there is a lot of time even for myself required to understand wtf is going, who sends what/when and where. Another example of frustration i've recently had was with IO api, as even the docs are not enough to understand the flow of messages. Thanks, Aleh On Saturday, March 8, 2014 9:59:31 PM UTC+3, Derek Wyatt wrote: On 8 March 2014 13:31, Daniel Armak dana...@gmail.com javascript:wrote: Hi Derek. Yes, I'm aware of the tradeoffs and the reasons for them. It just seems to me there ought to be a middle road. Here's an idea off the top of my head. If remoting is going to be transparent, there won't be compile time type assurance. So let's give up on local type assurance too, and just use types for documentation and refactoring; that still seems a lot better than nothing. Suppose for each actor I wanted to implement, I declare a trait with a method for each message it might receive. Each method must receive exactly one parameter, which is the message, and return Unit: trait Printer { def print(msg: String): Unit } The method name 'print' is just for documentation; only the parameter is sent as the message. This preserves transparency with the actor model; you don't rely on the trait implementation to build the actual message. The user of an ActorRef would use a wrapper (value type around the ActorRef) generated by a macro to implement this trait. The macro would also validate the trait: each method has one argument, returns Unit, and all argument types are distinct. Each method call would be implemented as a send(). val actor : ActorRef = ??? val typedActor: Printer = mymacro.wrap[Printer](actor) // Or is there a way to declare types with macros? I forget. typedActor.print(my message) The macro would add an (implicit sender: ActorRef) argument to each method. The actor itself would extend the Printer trait and implement its methods. Another macro would concatenate them to implement receive: class PrinterActor extends Actor with Printer with TypedActorMacro[Printer] { def print(msg: String): } To use become/unbecome, we could introduce something more complicated. Or, even, use the Printer trait only on the sender side - it would still be useful. This is just a rough outline. Do you think it might be useful, or do you think I shouldn't go down this road and trying to marry akka actors and (actor) types is a futile endeavour? Not futile, but highly suspect. You've barely scratched the surface with the above and much research is way ahead of you here. The ??? you have above isn't exactly trivial to implement, for example. What's more is that you've probably thrown away a ton more features in the process. e.g.: class MyActor(printActor: PrinterActor) { ... } // later... val myActor = MyActor.props(loadBalancer(printerActor.props())) // oops. Does the loadBalancer now have to // implement Printer? What if it's a load // balancer in front of scatter gather routers // that talk to forwarders that talk to various // different versions of Printers? Does everyone // have to implement the same API? If not, how // are you not throwing away type safety? And, if so // how am I doing anything but writing annoying code // that keeps me a slave to the API? And how do I // easily manage API changes, and so on, and so forth? Maybe not theoretically futile, but practically? Probably :) To be perfectly honest, it seems as though you're trying to fix a problem without having travelled a mile in its shoes yet. There are subsets of the problem that are much more important and more possible to cage, and you will see them as you progress. When you do, focusing on those (should you still believe them to be worth it) might be the far better option. Thanks! On Sat, Mar 8, 2014 at 7:51 PM, Derek Wyatt de...@derekwyatt.orgjavascript: wrote: What you're experiencing is a trade-off. Actors provide a trade-off that you don't seem to be taking into account; endpoints (Actors) are untyped and the messages that they handle are strongly typed. You can't have an Actor be able to process anything with a type-specific receive method. With Actor programming, I should be able to add as many intermediaries in the message flow as
Re: [akka-user] Re: How can I reconcile untyped actors with typeful programming?
Endre, i don't think this is related to distributed computations at all. Any method call can fail even locally. The way to represent that is with sum types e.g. Either. Thanks, Aleh On Sunday, March 9, 2014 8:42:11 PM UTC+3, drewhk wrote: Hi Daniel, The issue is that type systems are designed for local and not distributed computations. Let's look at an example. Imagine an actor that has three states, A, B and C In state A it accepts messages of type X, and when received one, it transitions to B In state B it accepts messages of type X and Y. When X is received, transitions to C, if Y, then stays in B In state C it accepts messages of type Z Now you send to an actor starting from state A a message X. Two things can happen: - X is delivered, so the possible accepted types are {X, Y} - X is lost, so the accepted type is {X} The intersection of those is {X}. Now imagine that you send another message X. Three things can happen: - both X's were delivered, so the accepted type is {Z} - only one of the X's were delivered, the other is lost, so the accepted types are {X, Y} - both X's were lost, the accepted type is {X} The intersection of the above cases is the empty set. So what should be the local type representation of an actor that you have sent two messages of type X? Let's modify the example, and assume that there was no message loss, but let's take the viewpoint of another sender. This sender knows that two X's were sent to our example actor by the other sender. What messages can we send? There are three scenarios: - both X's sent by the other sender has arrived already, so the accepted type is {Z} - only the first X sent by the other sender has arrived yet, so the accepted types are {X, Y} - no X's has arrived yet, accepted type is {X} The intersection of the above cases is the empty set. As you see, without receiving a reply from an actor, the provable type of an actor is usually Nothing, or something useless. Only replies can convey the *possible* type of an actor, and even that cannot be guaranteed if there are concurrent senders. -Endre On Sat, Mar 8, 2014 at 8:08 PM, Daniel Armak dana...@gmail.comjavascript: wrote: My intent was definitely to throw away type safety. In my example you can wrap any ActorRef using any trait, and you can extract and use the raw ActorRef from a typed wrapper. The only thing I was trying to salvage was for the sender to declare what kind of actor / behavior they thought they were dealing with. But I'm taking your advice to heart. I'll try to use pure actors until I have more of a sense of how problematic various issues are. Thanks! Daniel Armak On Sat, Mar 8, 2014 at 8:59 PM, Derek Wyatt de...@derekwyatt.orgjavascript: wrote: On 8 March 2014 13:31, Daniel Armak dana...@gmail.com javascript:wrote: Hi Derek. Yes, I'm aware of the tradeoffs and the reasons for them. It just seems to me there ought to be a middle road. Here's an idea off the top of my head. If remoting is going to be transparent, there won't be compile time type assurance. So let's give up on local type assurance too, and just use types for documentation and refactoring; that still seems a lot better than nothing. Suppose for each actor I wanted to implement, I declare a trait with a method for each message it might receive. Each method must receive exactly one parameter, which is the message, and return Unit: trait Printer { def print(msg: String): Unit } The method name 'print' is just for documentation; only the parameter is sent as the message. This preserves transparency with the actor model; you don't rely on the trait implementation to build the actual message. The user of an ActorRef would use a wrapper (value type around the ActorRef) generated by a macro to implement this trait. The macro would also validate the trait: each method has one argument, returns Unit, and all argument types are distinct. Each method call would be implemented as a send(). val actor : ActorRef = ??? val typedActor: Printer = mymacro.wrap[Printer](actor) // Or is there a way to declare types with macros? I forget. typedActor.print(my message) The macro would add an (implicit sender: ActorRef) argument to each method. The actor itself would extend the Printer trait and implement its methods. Another macro would concatenate them to implement receive: class PrinterActor extends Actor with Printer with TypedActorMacro[Printer] { def print(msg: String): } To use become/unbecome, we could introduce something more complicated. Or, even, use the Printer trait only on the sender side - it would still be useful. This is just a rough outline. Do you think it might be useful, or do you think I shouldn't go down this road and trying to marry akka actors and (actor) types is a futile endeavour? Not futile, but highly suspect. You've barely scratched the
Re: [akka-user] Re: How can I reconcile untyped actors with typeful programming?
Hi Aleh, On Sun, Mar 9, 2014 at 6:56 PM, OlegYch olegl...@gmail.com wrote: Endre, i don't think this is related to distributed computations at all. Any method call can fail even locally. This is true of course, but way more relevant in distributed cases where failures are orders of magnitudes more common. The way to represent that is with sum types e.g. Either. Won't work. Either supposes that you received a reply (even if it is a failure notification). No replies are guaranteed in general (distributed) settings. -Endre Thanks, Aleh On Sunday, March 9, 2014 8:42:11 PM UTC+3, drewhk wrote: Hi Daniel, The issue is that type systems are designed for local and not distributed computations. Let's look at an example. Imagine an actor that has three states, A, B and C In state A it accepts messages of type X, and when received one, it transitions to B In state B it accepts messages of type X and Y. When X is received, transitions to C, if Y, then stays in B In state C it accepts messages of type Z Now you send to an actor starting from state A a message X. Two things can happen: - X is delivered, so the possible accepted types are {X, Y} - X is lost, so the accepted type is {X} The intersection of those is {X}. Now imagine that you send another message X. Three things can happen: - both X's were delivered, so the accepted type is {Z} - only one of the X's were delivered, the other is lost, so the accepted types are {X, Y} - both X's were lost, the accepted type is {X} The intersection of the above cases is the empty set. So what should be the local type representation of an actor that you have sent two messages of type X? Let's modify the example, and assume that there was no message loss, but let's take the viewpoint of another sender. This sender knows that two X's were sent to our example actor by the other sender. What messages can we send? There are three scenarios: - both X's sent by the other sender has arrived already, so the accepted type is {Z} - only the first X sent by the other sender has arrived yet, so the accepted types are {X, Y} - no X's has arrived yet, accepted type is {X} The intersection of the above cases is the empty set. As you see, without receiving a reply from an actor, the provable type of an actor is usually Nothing, or something useless. Only replies can convey the *possible* type of an actor, and even that cannot be guaranteed if there are concurrent senders. -Endre On Sat, Mar 8, 2014 at 8:08 PM, Daniel Armak dana...@gmail.com wrote: My intent was definitely to throw away type safety. In my example you can wrap any ActorRef using any trait, and you can extract and use the raw ActorRef from a typed wrapper. The only thing I was trying to salvage was for the sender to declare what kind of actor / behavior they thought they were dealing with. But I'm taking your advice to heart. I'll try to use pure actors until I have more of a sense of how problematic various issues are. Thanks! Daniel Armak On Sat, Mar 8, 2014 at 8:59 PM, Derek Wyatt de...@derekwyatt.orgwrote: On 8 March 2014 13:31, Daniel Armak dana...@gmail.com wrote: Hi Derek. Yes, I'm aware of the tradeoffs and the reasons for them. It just seems to me there ought to be a middle road. Here's an idea off the top of my head. If remoting is going to be transparent, there won't be compile time type assurance. So let's give up on local type assurance too, and just use types for documentation and refactoring; that still seems a lot better than nothing. Suppose for each actor I wanted to implement, I declare a trait with a method for each message it might receive. Each method must receive exactly one parameter, which is the message, and return Unit: trait Printer { def print(msg: String): Unit } The method name 'print' is just for documentation; only the parameter is sent as the message. This preserves transparency with the actor model; you don't rely on the trait implementation to build the actual message. The user of an ActorRef would use a wrapper (value type around the ActorRef) generated by a macro to implement this trait. The macro would also validate the trait: each method has one argument, returns Unit, and all argument types are distinct. Each method call would be implemented as a send(). val actor : ActorRef = ??? val typedActor: Printer = mymacro.wrap[Printer](actor) // Or is there a way to declare types with macros? I forget. typedActor.print(my message) The macro would add an (implicit sender: ActorRef) argument to each method. The actor itself would extend the Printer trait and implement its methods. Another macro would concatenate them to implement receive: class PrinterActor extends Actor with Printer with TypedActorMacro[Printer] { def print(msg: String): } To use become/unbecome, we could introduce something more complicated. Or, even, use the Printer trait only on
Re: [akka-user] Re: How can I reconcile untyped actors with typeful programming?
On Sun, Mar 9, 2014 at 8:04 PM, Endre Varga endre.va...@typesafe.comwrote: Hi Aleh, On Sun, Mar 9, 2014 at 6:56 PM, OlegYch olegl...@gmail.com wrote: Endre, i don't think this is related to distributed computations at all. Any method call can fail even locally. This is true of course, but way more relevant in distributed cases where failures are orders of magnitudes more common. There is a huge difference here where the caller knows when a method call fails. The way to represent that is with sum types e.g. Either. Won't work. Either supposes that you received a reply (even if it is a failure notification). No replies are guaranteed in general (distributed) settings. It also assumes that replies are always sent to the sender and are never forwarded. -Endre Thanks, Aleh On Sunday, March 9, 2014 8:42:11 PM UTC+3, drewhk wrote: Hi Daniel, The issue is that type systems are designed for local and not distributed computations. Let's look at an example. Imagine an actor that has three states, A, B and C In state A it accepts messages of type X, and when received one, it transitions to B In state B it accepts messages of type X and Y. When X is received, transitions to C, if Y, then stays in B In state C it accepts messages of type Z Now you send to an actor starting from state A a message X. Two things can happen: - X is delivered, so the possible accepted types are {X, Y} - X is lost, so the accepted type is {X} The intersection of those is {X}. Now imagine that you send another message X. Three things can happen: - both X's were delivered, so the accepted type is {Z} - only one of the X's were delivered, the other is lost, so the accepted types are {X, Y} - both X's were lost, the accepted type is {X} The intersection of the above cases is the empty set. So what should be the local type representation of an actor that you have sent two messages of type X? Let's modify the example, and assume that there was no message loss, but let's take the viewpoint of another sender. This sender knows that two X's were sent to our example actor by the other sender. What messages can we send? There are three scenarios: - both X's sent by the other sender has arrived already, so the accepted type is {Z} - only the first X sent by the other sender has arrived yet, so the accepted types are {X, Y} - no X's has arrived yet, accepted type is {X} The intersection of the above cases is the empty set. As you see, without receiving a reply from an actor, the provable type of an actor is usually Nothing, or something useless. Only replies can convey the *possible* type of an actor, and even that cannot be guaranteed if there are concurrent senders. -Endre On Sat, Mar 8, 2014 at 8:08 PM, Daniel Armak dana...@gmail.com wrote: My intent was definitely to throw away type safety. In my example you can wrap any ActorRef using any trait, and you can extract and use the raw ActorRef from a typed wrapper. The only thing I was trying to salvage was for the sender to declare what kind of actor / behavior they thought they were dealing with. But I'm taking your advice to heart. I'll try to use pure actors until I have more of a sense of how problematic various issues are. Thanks! Daniel Armak On Sat, Mar 8, 2014 at 8:59 PM, Derek Wyatt de...@derekwyatt.orgwrote: On 8 March 2014 13:31, Daniel Armak dana...@gmail.com wrote: Hi Derek. Yes, I'm aware of the tradeoffs and the reasons for them. It just seems to me there ought to be a middle road. Here's an idea off the top of my head. If remoting is going to be transparent, there won't be compile time type assurance. So let's give up on local type assurance too, and just use types for documentation and refactoring; that still seems a lot better than nothing. Suppose for each actor I wanted to implement, I declare a trait with a method for each message it might receive. Each method must receive exactly one parameter, which is the message, and return Unit: trait Printer { def print(msg: String): Unit } The method name 'print' is just for documentation; only the parameter is sent as the message. This preserves transparency with the actor model; you don't rely on the trait implementation to build the actual message. The user of an ActorRef would use a wrapper (value type around the ActorRef) generated by a macro to implement this trait. The macro would also validate the trait: each method has one argument, returns Unit, and all argument types are distinct. Each method call would be implemented as a send(). val actor : ActorRef = ??? val typedActor: Printer = mymacro.wrap[Printer](actor) // Or is there a way to declare types with macros? I forget. typedActor.print(my message) The macro would add an (implicit sender: ActorRef) argument to each method. The actor itself would extend the Printer trait and implement its methods. Another macro would
Re: [akka-user] Re: How can I reconcile untyped actors with typeful programming?
Part of the difficulty I'm experiencing is that most of the time I want to write code that is definitely local - a set of actors that are guaranteed to run on the same JVM - but communication between them has to deal with complications that only really arise from distributed computation, like loss of type safety. Maybe the concepts of an actor as concurrency unit with private mutable state and failure management, and actor as a possibly-remote entity with no guaranteed delivery, would be better partially separated. In my admittedly limited experience, separating actors into deployment sets is part of the system design, needs careful planning, and doesn't change often. Inside each local set, the benefits of guaranteed locality would be large (messages aren't lost, static typing, etc.) On Sun, Mar 9, 2014 at 7:42 PM, Endre Varga endre.va...@typesafe.comwrote: Hi Daniel, The issue is that type systems are designed for local and not distributed computations. Let's look at an example. Imagine an actor that has three states, A, B and C In state A it accepts messages of type X, and when received one, it transitions to B In state B it accepts messages of type X and Y. When X is received, transitions to C, if Y, then stays in B In state C it accepts messages of type Z Now you send to an actor starting from state A a message X. Two things can happen: - X is delivered, so the possible accepted types are {X, Y} - X is lost, so the accepted type is {X} The intersection of those is {X}. Now imagine that you send another message X. Three things can happen: - both X's were delivered, so the accepted type is {Z} - only one of the X's were delivered, the other is lost, so the accepted types are {X, Y} - both X's were lost, the accepted type is {X} The intersection of the above cases is the empty set. So what should be the local type representation of an actor that you have sent two messages of type X? Let's modify the example, and assume that there was no message loss, but let's take the viewpoint of another sender. This sender knows that two X's were sent to our example actor by the other sender. What messages can we send? There are three scenarios: - both X's sent by the other sender has arrived already, so the accepted type is {Z} - only the first X sent by the other sender has arrived yet, so the accepted types are {X, Y} - no X's has arrived yet, accepted type is {X} The intersection of the above cases is the empty set. As you see, without receiving a reply from an actor, the provable type of an actor is usually Nothing, or something useless. Only replies can convey the *possible* type of an actor, and even that cannot be guaranteed if there are concurrent senders. -Endre On Sat, Mar 8, 2014 at 8:08 PM, Daniel Armak danar...@gmail.com wrote: My intent was definitely to throw away type safety. In my example you can wrap any ActorRef using any trait, and you can extract and use the raw ActorRef from a typed wrapper. The only thing I was trying to salvage was for the sender to declare what kind of actor / behavior they thought they were dealing with. But I'm taking your advice to heart. I'll try to use pure actors until I have more of a sense of how problematic various issues are. Thanks! Daniel Armak On Sat, Mar 8, 2014 at 8:59 PM, Derek Wyatt de...@derekwyatt.org wrote: On 8 March 2014 13:31, Daniel Armak danar...@gmail.com wrote: Hi Derek. Yes, I'm aware of the tradeoffs and the reasons for them. It just seems to me there ought to be a middle road. Here's an idea off the top of my head. If remoting is going to be transparent, there won't be compile time type assurance. So let's give up on local type assurance too, and just use types for documentation and refactoring; that still seems a lot better than nothing. Suppose for each actor I wanted to implement, I declare a trait with a method for each message it might receive. Each method must receive exactly one parameter, which is the message, and return Unit: trait Printer { def print(msg: String): Unit } The method name 'print' is just for documentation; only the parameter is sent as the message. This preserves transparency with the actor model; you don't rely on the trait implementation to build the actual message. The user of an ActorRef would use a wrapper (value type around the ActorRef) generated by a macro to implement this trait. The macro would also validate the trait: each method has one argument, returns Unit, and all argument types are distinct. Each method call would be implemented as a send(). val actor : ActorRef = ??? val typedActor: Printer = mymacro.wrap[Printer](actor) // Or is there a way to declare types with macros? I forget. typedActor.print(my message) The macro would add an (implicit sender: ActorRef) argument to each method. The actor itself would extend the Printer trait and implement its methods. Another macro would
Re: [akka-user] Re: How can I reconcile untyped actors with typeful programming?
Hi Daniel, Please read carefully, in my second example there is no message loss. Concurrency is enough to have the same result since arbitrary delay is indistinguishable from message loss in any finite window of time. -Endre On Sun, Mar 9, 2014 at 8:31 PM, Daniel Armak danar...@gmail.com wrote: Part of the difficulty I'm experiencing is that most of the time I want to write code that is definitely local - a set of actors that are guaranteed to run on the same JVM - but communication between them has to deal with complications that only really arise from distributed computation, like loss of type safety. Maybe the concepts of an actor as concurrency unit with private mutable state and failure management, and actor as a possibly-remote entity with no guaranteed delivery, would be better partially separated. In my admittedly limited experience, separating actors into deployment sets is part of the system design, needs careful planning, and doesn't change often. Inside each local set, the benefits of guaranteed locality would be large (messages aren't lost, static typing, etc.) On Sun, Mar 9, 2014 at 7:42 PM, Endre Varga endre.va...@typesafe.comwrote: Hi Daniel, The issue is that type systems are designed for local and not distributed computations. Let's look at an example. Imagine an actor that has three states, A, B and C In state A it accepts messages of type X, and when received one, it transitions to B In state B it accepts messages of type X and Y. When X is received, transitions to C, if Y, then stays in B In state C it accepts messages of type Z Now you send to an actor starting from state A a message X. Two things can happen: - X is delivered, so the possible accepted types are {X, Y} - X is lost, so the accepted type is {X} The intersection of those is {X}. Now imagine that you send another message X. Three things can happen: - both X's were delivered, so the accepted type is {Z} - only one of the X's were delivered, the other is lost, so the accepted types are {X, Y} - both X's were lost, the accepted type is {X} The intersection of the above cases is the empty set. So what should be the local type representation of an actor that you have sent two messages of type X? Let's modify the example, and assume that there was no message loss, but let's take the viewpoint of another sender. This sender knows that two X's were sent to our example actor by the other sender. What messages can we send? There are three scenarios: - both X's sent by the other sender has arrived already, so the accepted type is {Z} - only the first X sent by the other sender has arrived yet, so the accepted types are {X, Y} - no X's has arrived yet, accepted type is {X} The intersection of the above cases is the empty set. As you see, without receiving a reply from an actor, the provable type of an actor is usually Nothing, or something useless. Only replies can convey the *possible* type of an actor, and even that cannot be guaranteed if there are concurrent senders. -Endre On Sat, Mar 8, 2014 at 8:08 PM, Daniel Armak danar...@gmail.com wrote: My intent was definitely to throw away type safety. In my example you can wrap any ActorRef using any trait, and you can extract and use the raw ActorRef from a typed wrapper. The only thing I was trying to salvage was for the sender to declare what kind of actor / behavior they thought they were dealing with. But I'm taking your advice to heart. I'll try to use pure actors until I have more of a sense of how problematic various issues are. Thanks! Daniel Armak On Sat, Mar 8, 2014 at 8:59 PM, Derek Wyatt de...@derekwyatt.orgwrote: On 8 March 2014 13:31, Daniel Armak danar...@gmail.com wrote: Hi Derek. Yes, I'm aware of the tradeoffs and the reasons for them. It just seems to me there ought to be a middle road. Here's an idea off the top of my head. If remoting is going to be transparent, there won't be compile time type assurance. So let's give up on local type assurance too, and just use types for documentation and refactoring; that still seems a lot better than nothing. Suppose for each actor I wanted to implement, I declare a trait with a method for each message it might receive. Each method must receive exactly one parameter, which is the message, and return Unit: trait Printer { def print(msg: String): Unit } The method name 'print' is just for documentation; only the parameter is sent as the message. This preserves transparency with the actor model; you don't rely on the trait implementation to build the actual message. The user of an ActorRef would use a wrapper (value type around the ActorRef) generated by a macro to implement this trait. The macro would also validate the trait: each method has one argument, returns Unit, and all argument types are distinct. Each method call would be implemented as a send(). val actor : ActorRef = ??? val typedActor:
Re: [akka-user] Re: How can I reconcile untyped actors with typeful programming?
I'm sorry if I'm being obtuse here and missing something fundamental. Don't worry, you're not the only one who's been confused by this! I'm following this thread because I'd want to see any explanations too =P - Someone who likes Futures but thinks Actors are hard to use. On Sun, Mar 9, 2014 at 3:32 PM, Daniel Armak danar...@gmail.com wrote: Hi Endre, Concurrency alone isn't a reason to give up type safety. Future-based APIs are typed even though futures may never complete. (I'm not sure what you refer to with message loss; I didn't talk about that.) It's true that in the actor model each state change can change the set of valid message types. But with regular objects, some method calls are also invalid depending on the current state, they may even be invalid based on parameter values and not types, and yet we don't give up on static typing. I understand that with the current actor model, static typing probably isn't possible. But I don't understand why a different model couldn't in principle capture most or all of the benefits of actors, certainly including concurrency and indeterminacy, and still preserve typing. I'm sorry if I'm being obtuse here and missing something fundamental. On Sun, Mar 9, 2014 at 10:30 PM, Endre Varga endre.va...@typesafe.comwrote: Hi Daniel, Please read carefully, in my second example there is no message loss. Concurrency is enough to have the same result since arbitrary delay is indistinguishable from message loss in any finite window of time. -Endre On Sun, Mar 9, 2014 at 8:31 PM, Daniel Armak danar...@gmail.com wrote: Part of the difficulty I'm experiencing is that most of the time I want to write code that is definitely local - a set of actors that are guaranteed to run on the same JVM - but communication between them has to deal with complications that only really arise from distributed computation, like loss of type safety. Maybe the concepts of an actor as concurrency unit with private mutable state and failure management, and actor as a possibly-remote entity with no guaranteed delivery, would be better partially separated. In my admittedly limited experience, separating actors into deployment sets is part of the system design, needs careful planning, and doesn't change often. Inside each local set, the benefits of guaranteed locality would be large (messages aren't lost, static typing, etc.) On Sun, Mar 9, 2014 at 7:42 PM, Endre Varga endre.va...@typesafe.comwrote: Hi Daniel, The issue is that type systems are designed for local and not distributed computations. Let's look at an example. Imagine an actor that has three states, A, B and C In state A it accepts messages of type X, and when received one, it transitions to B In state B it accepts messages of type X and Y. When X is received, transitions to C, if Y, then stays in B In state C it accepts messages of type Z Now you send to an actor starting from state A a message X. Two things can happen: - X is delivered, so the possible accepted types are {X, Y} - X is lost, so the accepted type is {X} The intersection of those is {X}. Now imagine that you send another message X. Three things can happen: - both X's were delivered, so the accepted type is {Z} - only one of the X's were delivered, the other is lost, so the accepted types are {X, Y} - both X's were lost, the accepted type is {X} The intersection of the above cases is the empty set. So what should be the local type representation of an actor that you have sent two messages of type X? Let's modify the example, and assume that there was no message loss, but let's take the viewpoint of another sender. This sender knows that two X's were sent to our example actor by the other sender. What messages can we send? There are three scenarios: - both X's sent by the other sender has arrived already, so the accepted type is {Z} - only the first X sent by the other sender has arrived yet, so the accepted types are {X, Y} - no X's has arrived yet, accepted type is {X} The intersection of the above cases is the empty set. As you see, without receiving a reply from an actor, the provable type of an actor is usually Nothing, or something useless. Only replies can convey the *possible* type of an actor, and even that cannot be guaranteed if there are concurrent senders. -Endre On Sat, Mar 8, 2014 at 8:08 PM, Daniel Armak danar...@gmail.comwrote: My intent was definitely to throw away type safety. In my example you can wrap any ActorRef using any trait, and you can extract and use the raw ActorRef from a typed wrapper. The only thing I was trying to salvage was for the sender to declare what kind of actor / behavior they thought they were dealing with. But I'm taking your advice to heart. I'll try to use pure actors until I have more of a sense of how problematic various issues are. Thanks! Daniel Armak On Sat, Mar 8, 2014 at 8:59 PM, Derek Wyatt
Re: [akka-user] Re: How can I reconcile untyped actors with typeful programming?
On 8 March 2014 13:31, Daniel Armak danar...@gmail.com wrote: Hi Derek. Yes, I'm aware of the tradeoffs and the reasons for them. It just seems to me there ought to be a middle road. Here's an idea off the top of my head. If remoting is going to be transparent, there won't be compile time type assurance. So let's give up on local type assurance too, and just use types for documentation and refactoring; that still seems a lot better than nothing. Suppose for each actor I wanted to implement, I declare a trait with a method for each message it might receive. Each method must receive exactly one parameter, which is the message, and return Unit: trait Printer { def print(msg: String): Unit } The method name 'print' is just for documentation; only the parameter is sent as the message. This preserves transparency with the actor model; you don't rely on the trait implementation to build the actual message. The user of an ActorRef would use a wrapper (value type around the ActorRef) generated by a macro to implement this trait. The macro would also validate the trait: each method has one argument, returns Unit, and all argument types are distinct. Each method call would be implemented as a send(). val actor : ActorRef = ??? val typedActor: Printer = mymacro.wrap[Printer](actor) // Or is there a way to declare types with macros? I forget. typedActor.print(my message) The macro would add an (implicit sender: ActorRef) argument to each method. The actor itself would extend the Printer trait and implement its methods. Another macro would concatenate them to implement receive: class PrinterActor extends Actor with Printer with TypedActorMacro[Printer] { def print(msg: String): } To use become/unbecome, we could introduce something more complicated. Or, even, use the Printer trait only on the sender side - it would still be useful. This is just a rough outline. Do you think it might be useful, or do you think I shouldn't go down this road and trying to marry akka actors and (actor) types is a futile endeavour? Not futile, but highly suspect. You've barely scratched the surface with the above and much research is way ahead of you here. The ??? you have above isn't exactly trivial to implement, for example. What's more is that you've probably thrown away a ton more features in the process. e.g.: class MyActor(printActor: PrinterActor) { ... } // later... val myActor = MyActor.props(loadBalancer(printerActor.props())) // oops. Does the loadBalancer now have to // implement Printer? What if it's a load // balancer in front of scatter gather routers // that talk to forwarders that talk to various // different versions of Printers? Does everyone // have to implement the same API? If not, how // are you not throwing away type safety? And, if so // how am I doing anything but writing annoying code // that keeps me a slave to the API? And how do I // easily manage API changes, and so on, and so forth? Maybe not theoretically futile, but practically? Probably :) To be perfectly honest, it seems as though you're trying to fix a problem without having travelled a mile in its shoes yet. There are subsets of the problem that are much more important and more possible to cage, and you will see them as you progress. When you do, focusing on those (should you still believe them to be worth it) might be the far better option. Thanks! On Sat, Mar 8, 2014 at 7:51 PM, Derek Wyatt de...@derekwyatt.org wrote: What you're experiencing is a trade-off. Actors provide a trade-off that you don't seem to be taking into account; endpoints (Actors) are untyped and the messages that they handle are strongly typed. You can't have an Actor be able to process anything with a type-specific receive method. With Actor programming, I should be able to add as many intermediaries in the message flow as I like and not disturb the two endpoints. The intermediaries should equally be ignorant of what's happening (load balancers, routers, loggers, cachers, mediators, scatter-gather, and so forth). You should also be able to move them around a cluster without disturbing the endpoints. You can also set up dynamic delegation in an Actor without it having to really understand what's going on - for example, an Actor that speaks the main dialect but delegates to something else when it doesn't understand what's being said. If you want to eliminate all of those features, then you will be able to get the type-safe determinism you're looking for (so long as you stay in the same JVM - crossing JVMs will incur a what the hell am I *really* talking to? question that eliminates a compile time assurance). Roland also tried bringing the best of both worlds together using Typed Channels but I think it was lacking a high enough success level to survive, but it might be a way to get closer to your ideal. In short, you're giving up type safety in order to open the door to a
Re: [akka-user] Re: How can I reconcile untyped actors with typeful programming?
My intent was definitely to throw away type safety. In my example you can wrap any ActorRef using any trait, and you can extract and use the raw ActorRef from a typed wrapper. The only thing I was trying to salvage was for the sender to declare what kind of actor / behavior they thought they were dealing with. But I'm taking your advice to heart. I'll try to use pure actors until I have more of a sense of how problematic various issues are. Thanks! Daniel Armak On Sat, Mar 8, 2014 at 8:59 PM, Derek Wyatt de...@derekwyatt.org wrote: On 8 March 2014 13:31, Daniel Armak danar...@gmail.com wrote: Hi Derek. Yes, I'm aware of the tradeoffs and the reasons for them. It just seems to me there ought to be a middle road. Here's an idea off the top of my head. If remoting is going to be transparent, there won't be compile time type assurance. So let's give up on local type assurance too, and just use types for documentation and refactoring; that still seems a lot better than nothing. Suppose for each actor I wanted to implement, I declare a trait with a method for each message it might receive. Each method must receive exactly one parameter, which is the message, and return Unit: trait Printer { def print(msg: String): Unit } The method name 'print' is just for documentation; only the parameter is sent as the message. This preserves transparency with the actor model; you don't rely on the trait implementation to build the actual message. The user of an ActorRef would use a wrapper (value type around the ActorRef) generated by a macro to implement this trait. The macro would also validate the trait: each method has one argument, returns Unit, and all argument types are distinct. Each method call would be implemented as a send(). val actor : ActorRef = ??? val typedActor: Printer = mymacro.wrap[Printer](actor) // Or is there a way to declare types with macros? I forget. typedActor.print(my message) The macro would add an (implicit sender: ActorRef) argument to each method. The actor itself would extend the Printer trait and implement its methods. Another macro would concatenate them to implement receive: class PrinterActor extends Actor with Printer with TypedActorMacro[Printer] { def print(msg: String): } To use become/unbecome, we could introduce something more complicated. Or, even, use the Printer trait only on the sender side - it would still be useful. This is just a rough outline. Do you think it might be useful, or do you think I shouldn't go down this road and trying to marry akka actors and (actor) types is a futile endeavour? Not futile, but highly suspect. You've barely scratched the surface with the above and much research is way ahead of you here. The ??? you have above isn't exactly trivial to implement, for example. What's more is that you've probably thrown away a ton more features in the process. e.g.: class MyActor(printActor: PrinterActor) { ... } // later... val myActor = MyActor.props(loadBalancer(printerActor.props())) // oops. Does the loadBalancer now have to // implement Printer? What if it's a load // balancer in front of scatter gather routers // that talk to forwarders that talk to various // different versions of Printers? Does everyone // have to implement the same API? If not, how // are you not throwing away type safety? And, if so // how am I doing anything but writing annoying code // that keeps me a slave to the API? And how do I // easily manage API changes, and so on, and so forth? Maybe not theoretically futile, but practically? Probably :) To be perfectly honest, it seems as though you're trying to fix a problem without having travelled a mile in its shoes yet. There are subsets of the problem that are much more important and more possible to cage, and you will see them as you progress. When you do, focusing on those (should you still believe them to be worth it) might be the far better option. Thanks! On Sat, Mar 8, 2014 at 7:51 PM, Derek Wyatt de...@derekwyatt.org wrote: What you're experiencing is a trade-off. Actors provide a trade-off that you don't seem to be taking into account; endpoints (Actors) are untyped and the messages that they handle are strongly typed. You can't have an Actor be able to process anything with a type-specific receive method. With Actor programming, I should be able to add as many intermediaries in the message flow as I like and not disturb the two endpoints. The intermediaries should equally be ignorant of what's happening (load balancers, routers, loggers, cachers, mediators, scatter-gather, and so forth). You should also be able to move them around a cluster without disturbing the endpoints. You can also set up dynamic delegation in an Actor without it having to really understand what's going on - for example, an Actor that speaks the main dialect but delegates to something else when it doesn't understand what's