How can I reconcile untyped actors with typeful programming?

796 views
Skip to first unread message

Daniel Armak

unread,
Mar 7, 2014, 5:10:23 PM3/7/14
to akka...@googlegroups.com
Hello,

I hope someone will take the time to read this message (sorry about its length) and help me.

I'm writing a fairly large system from scratch in Scala. I love the rich typing and functional programming style, and I have experience with future-based programming, but not actors. 

When I try to use actors, I feel like I'm giving up static typing, even the basic feature of a type declaring its methods (=received messages). Using partial functions feels like casting, because I really want to assert that the 'partial' function will always match.

This can't be right. How do you reconcile the two worlds?

Specific issues I'm struggling with:

1. Typed code is discoverable via methods and fields. An actor relies on documentation. 

One option is to define all messages an actor can receive in its companion object, and document each message; but many libraries (e.g. spray) define many different actors that all send and receive the same types (eg HttpRequest), which is convenient for forwarding messages between actors. But then the *only* way to figure out which messages are legal and the semantics of what they do is to read the docs.

When an actor manages complex state and might receive ten or twenty different message types, this is hard to manage.

Even if I document the actor, this documentation is still not discoverable because the client has an ActorRef, not an ActorRef[MyActorTrait]. So I have to figure out manually what behavior this actor is expected to implement and read the docs for that.

2. Refactoring actors is hard. To make a non-backward-compatible API change to an actor, I have to go over all references to the message types involved. No more 'find all usages of method' or 'rename method'.

3. Typed Actors lack important Actor features (become/unbecome, etc), have to declare exceptions, suffer a proxy performance penalty, and aren't recommended as a replacement for actors in general. 


I understand that features like become() and remoting make it difficult to type actors, and maybe this is just an unsolved difficult problem. So how do mature actor-based projects handle these issues? How can I have the benefits of both actors and static typing?

TIA for any insight.

Daniel Armak

Adam

unread,
Mar 8, 2014, 9:42:16 AM3/8/14
to akka...@googlegroups.com

While far from perfect, one thing that I do is strictly following the convention that the only objects a specific actor will process are defined within that actor's companion object.

I also refrain from directly importing those objects/classes anywhere so I always have to write out actor.message when sending it. I find that actually helps a lot.

Derek Wyatt

unread,
Mar 8, 2014, 12:51:28 PM3/8/14
to akka...@googlegroups.com
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 whole new set of facilities.  Don't want to lose the type-safety?  Close the door :)

I had the exact same reservations as you have right now.  I tried to reconcile them myself, using typed actors and my world turned into a pile of crap... I got no real flexibility out of that system.  There was a cognitive overhead to switching to actors and no benefit.

In the end, I learned to embrace the patterns and philosophy and grew to love it so much that I wrote a book about it (the first one, I think) and now code in Scala and Akka every single day.  Your mileage may vary :)

Daniel Armak

unread,
Mar 8, 2014, 1:31:21 PM3/8/14
to akka...@googlegroups.com
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?

Thanks!

--
>>>>>>>>>> Read the docs: http://akka.io/docs/
>>>>>>>>>> Check the FAQ: http://doc.akka.io/docs/akka/current/additional/faq.html
>>>>>>>>>> Search the archives: https://groups.google.com/group/akka-user
---
You received this message because you are subscribed to the Google Groups "Akka User List" group.
To unsubscribe from this group and stop receiving emails from it, send an email to akka-user+...@googlegroups.com.
To post to this group, send email to akka...@googlegroups.com.
Visit this group at http://groups.google.com/group/akka-user.
For more options, visit https://groups.google.com/d/optout.

Derek Wyatt

unread,
Mar 8, 2014, 1:59:31 PM3/8/14
to akka...@googlegroups.com
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 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.
 
You received this message because you are subscribed to a topic in the Google Groups "Akka User List" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/akka-user/rLKk7-D_jHQ/unsubscribe.
To unsubscribe from this group and all its topics, send an email to akka-user+...@googlegroups.com.

Daniel Armak

unread,
Mar 8, 2014, 2:08:40 PM3/8/14
to akka-user
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

Endre Varga

unread,
Mar 9, 2014, 1:42:11 PM3/9/14
to akka...@googlegroups.com
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

OlegYch

unread,
Mar 9, 2014, 1:51:31 PM3/9/14
to akka...@googlegroups.com
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

OlegYch

unread,
Mar 9, 2014, 1:56:09 PM3/9/14
to akka...@googlegroups.com
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

Endre Varga

unread,
Mar 9, 2014, 3:04:18 PM3/9/14
to akka...@googlegroups.com
Hi Aleh,


On Sun, Mar 9, 2014 at 6:56 PM, OlegYch <oleg...@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

√iktor Ҡlang

unread,
Mar 9, 2014, 3:20:21 PM3/9/14
to Akka User List
On Sun, Mar 9, 2014 at 8:04 PM, Endre Varga <endre...@typesafe.com> wrote:
Hi Aleh,


On Sun, Mar 9, 2014 at 6:56 PM, OlegYch <oleg...@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.



--
Cheers,

———————
Viktor Klang
Chief Architect - Typesafe

Twitter: @viktorklang

Daniel Armak

unread,
Mar 9, 2014, 3:31:54 PM3/9/14
to akka-user
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.)

Endre Varga

unread,
Mar 9, 2014, 4:30:00 PM3/9/14
to akka...@googlegroups.com
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

Daniel Armak

unread,
Mar 9, 2014, 6:32:31 PM3/9/14
to akka-user
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.

Haoyi Li

unread,
Mar 9, 2014, 8:10:11 PM3/9/14
to akka...@googlegroups.com
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.

Roland Kuhn

unread,
Mar 10, 2014, 3:11:45 AM3/10/14
to akka-user
Hi Daniel,

I’m glad that you bring up this discussion, my desire to add some level of static typing to Akka is as old as my involvement with the project. If you look into the 1.x past you’ll find akka.actor.Channel[T] which was conceived with that in mind, and in 2.1 and 2.2 there were Typed Channels as a macro-based experiment. The latter actually crossed the line from thought experiment into code, and you are welcome to try it out to get a feeling for how static types interact with a very dynamic system.

The main shortcoming of Typed Channels was its inappropriate complexity (too many type parameters and too complex types—with type-level lists and maps—in them). We are gradually converging on a design which may strike the right balance, but in essence it means removing `sender` from Akka actors (which has also other very welcome benefits concerning closing over things in Future transformations). The gist of it is to parameterize ActorRef[T] with the type of message it accepts (with the obvious knock-on effects on Props[T], Actor[T] and so on). Then an Actor can expose references to itself with the appropriate type and that it sends to other actors—in specific messages in order to get around type erasure. This would even allow the formulation of message protocols, a.k.a. session types or at least close to it.

Derek made an excellent point about how the actor model really benefits from being unconstrained by types: a message router does not necessarily need to know anything about the messages passing through it. How well it works to parameterize the router itself remains to be seen, but in general such routing stages will destroy the type information, there is just not much we can do there. Your point that having some type-checking is better than none at all is one which resonates well with me, as long as the difference is really obvious to the developer: we must avoid a false sense of security.

This gets me to Endre’s valid interjection that concurrent behavior is not accessible to static verification. The problem is much broader than message loss in that any nondeterministic action would have to result in a type disjunction, killing our nice static types through exponential explosion of the type structure. This means that we can only practically express using types those parts which are deterministic: if you send a message of type A to an actor, then you may get back a message of type B (which translates into having to supply an ActorRef[B] within the A message), where A and B typically are sum types like “all commands accepted by this actor” and “all replies which can possibly be sent”. It is impossible to model qualitative state changes of an actor because the compiler cannot know whether they will actually occur or not.

There is some light, though: if you receive message B, which includes an ActorRef[C] from the target, then you have evidence that the effect of message A has occurred, so you can assume that the actor is now in a state where it accepts message C. But this is not a guarantee, the actor might have crashed in the meantime.

Note how none of this depends on remote messaging. Your desire to split actors into a concurrency and a distribution part are very comprehensible, I used to think the same. Then I came to realize that concurrency and distribution are in fact the same thing: processes can only run concurrently if their execution is separated in space or time, which means being distributed, and on the other hand the finite speed of light implies that distributed processes will by definition be concurrent. We want encapsulation and compartmentalization for our actors, only communicating using messages, and this model means that two actors are always separated from each other, they are distributed even if they run on the same JVM (queues can run full, failures can occur, communication is not fully reliable—although its reliability is definitely a lot higher than in the network case). If you think about modern processors, the different cores and especially sockets are separated by networks as well, they are just a lot faster than your grand-dad’s gigabit ethernet.

This is precisely why I believe that the Actor model is exactly the right abstraction for modeling independent pieces in your applications now and in the future, since the hardware itself is going more and more distributed and actors capture just the essence of that. And as I argued above, I do see room for improvement on the static typing side of things.

Regards,

Roland

BTW: almost all Future combinators are not concurrent (as in nondeterministic), Futures are therefore very useful for modeling parallelism without the concurrency pain. I invite you to scrutinize the scala.concurrent.Future API for non-determinism and then see for yourself how that is reflected in the types.


Dr. Roland Kuhn
Akka Tech Lead
Typesafe – Reactive apps on the JVM.
twitter: @rolandkuhn


Alexandru Nedelcu

unread,
Mar 10, 2014, 6:01:19 PM3/10/14
to akka...@googlegroups.com
On Mon, Mar 10, 2014 at 9:11 AM, Roland Kuhn <goo...@rkuhn.info> wrote:
BTW: almost all Future combinators are not concurrent (as in nondeterministic), Futures are therefore very useful for modeling parallelism without the concurrency pain. I invite you to scrutinize the scala.concurrent.Future API for non-determinism and then see for yourself how that is reflected in the types.

Stating the obvious for Dr. Roland Kuhn :-), but in my opinion the difference between a Future[T] and an ActorRef is that actors are too low-level and general, whereas Future[T] is very specialized - the Future combinators simply assume that Future is a container for a Try[T] result that will be available either now or in the future, the actual moment being of no consequence for the combinators that simply guide you through the happy path. 

The problem with Future[T] is that it can't be applied for example on problems where streams and/or bi-directional communications are involved. An ActorRef can provide an awesome implementation for Future[T] instances. However, between an ActorRef and a Future[T], there is a need for abstractions in-between. 

I know there are already patterns and best practices for designing stuff with actors, but whenever I work with actors, somehow I always end up with fragile and tightly-coupled designs, precisely because I had to deal with low-level stuff. E.g. actor A sends a message to actor B, but actor B could crash, so now you're thinking of using an external queue that persists messages for actor B's return. But once that external queue is operational, now you're thinking of redundancy for actor B, so you actually want actors B1 and B2, but because you designed actor B as statefull, now you're thinking in terms of primary / backup nodes and maybe you want those messages to not be pulled out of that queue unless they were processed in a reasonable amount of time by the primary actor, or otherwise have a backup that takes over that responsibility. Or maybe you have stateless B actors, so round-robin dispatching is not a problem, yet the message processing itself is long and could crash, so maybe you want to try again later with another actor in case it didn't finish in a reasonable amount of time. Then you realize that actor A should maybe send the same or different messages to different actors, but then you realize that this is actually tight coupling and you actually want a channel on which actors could subscribe for the messages they are interested in. Or maybe you want your cluster to scale depending on the load, so you want to create actors dynamically depending on runtime stats, yet this is a far cry from bringing up HTTP servers dynamically in an AWS ELB environment, because it's more generic and those docs give me the creeps. 

Somewhere in that process, my head explodes, especially given that I haven't solved the first problem I always have, which is good future-proof design, because actors don't guide your implementation at all and many times it's totally non-obvious of what to do.

Kudos on debating the deprecation of "sender". That's a step in making that interface more solid. I also await eagerly on your Reactive Streams - I do feel the need for an Rx streams implementation that also does back-pressure.

--
Alexandru Nedelcu
www.bionicspirit.com

PGP Public Key:
https://bionicspirit.com/key.aexpk

Haoyi Li

unread,
Mar 10, 2014, 6:32:51 PM3/10/14
to akka...@googlegroups.com
in essence it means removing `sender` from Akka actors (which has also other very welcome benefits concerning closing over things in Future transformations)

I always wondered: if the problem is all about unwanted closure over mutable state, why not just make sender a parameter instead of a magic global?

def receive(sender: ActorRef) = {
  ...
}

Voila, no more incorrect use of sender inside of futures or props or any of that stuff!


--

√iktor Ҡlang

unread,
Mar 10, 2014, 7:48:21 PM3/10/14
to Akka User List

receive is a method that returns a PartialFunction. PartialFunctions don't take 2 params and you don't want to allocate/box a Tuple2 for each receive.

Haoyi Li

unread,
Mar 10, 2014, 7:54:22 PM3/10/14
to akka...@googlegroups.com
I didn't mean make it take a tuple, I meant make it a function that takes a sender and *then* returns a partial function:

def receive(sender: ActorRef) = {
  case ... =>
  case ... =>
}

I guess the performance cost for doing this is that you have to allocate a new partial function each time, but is it really that expensive? (Maybe the answer is yes, I am asking out of ignorance)

√iktor Ҡlang

unread,
Mar 10, 2014, 8:15:37 PM3/10/14
to Akka User List

Yes, allocating millions of PFs per second is expensive.

Roland Kuhn

unread,
Mar 13, 2014, 5:27:59 PM3/13/14
to akka-user
Hi Alexandru,

10 mar 2014 kl. 23:01 skrev Alexandru Nedelcu <al...@bionicspirit.com>:

On Mon, Mar 10, 2014 at 9:11 AM, Roland Kuhn <goo...@rkuhn.info> wrote:
BTW: almost all Future combinators are not concurrent (as in nondeterministic), Futures are therefore very useful for modeling parallelism without the concurrency pain. I invite you to scrutinize the scala.concurrent.Future API for non-determinism and then see for yourself how that is reflected in the types.

Stating the obvious for Dr. Roland Kuhn :-), but in my opinion the difference between a Future[T] and an ActorRef is that actors are too low-level and general, whereas Future[T] is very specialized - the Future combinators simply assume that Future is a container for a Try[T] result that will be available either now or in the future, the actual moment being of no consequence for the combinators that simply guide you through the happy path. 

An actor is simply an exchangeable behavior which reacts to incoming messages at its own pace. Actors and Futures are equally low-level and general; they also happen to model very different things :-)

The problem with Future[T] is that it can't be applied for example on problems where streams and/or bi-directional communications are involved. An ActorRef can provide an awesome implementation for Future[T] instances. However, between an ActorRef and a Future[T], there is a need for abstractions in-between. 

Futures very cleanly model local dataflow. Actors very cleanly model distribution and concurrency. My guess is that if you try to create an abstraction in-between then it will have the disadvantages of both and the advantages of neither.

I know there are already patterns and best practices for designing stuff with actors, but whenever I work with actors, somehow I always end up with fragile and tightly-coupled designs, precisely because I had to deal with low-level stuff. E.g. actor A sends a message to actor B, but actor B could crash, so now you're thinking of using an external queue

Nope, A simply needs to resend. If that is inappropriate then that means that A does not really care about B’s response either.

that persists messages for actor B's return. But once that external queue is operational, now you're thinking of redundancy for actor B, so you actually want actors B1 and B2, but because you designed actor B as statefull, now you're thinking in terms of primary / backup nodes

Nope, this is the old traditional thinking: you will need to split up your state in order to distribute it over multiple actors; this is called sharding. Then you make every shard persistent and you’re good to go.

and maybe you want those messages to not be pulled out of that queue unless they were processed in a reasonable amount of time by the primary actor, or otherwise have a backup that takes over that responsibility. Or maybe you have stateless B actors, so round-robin dispatching is not a problem, yet the message processing itself is long and could crash, so maybe you want to try again later with another actor in case it didn't finish in a reasonable amount of time.

Again: if there is an actor that is interested in things getting processed, then that actor will have to be the one which resends the job until it gets a confirmation. It’s like in real life: if your boss wants you to do something, he or she will bug you. If they don’t bug you, it can’t be important.

Then you realize that actor A should maybe send the same or different messages to different actors, but then you realize that this is actually tight coupling

Nope, you send ActorRefs around to introduce actors to each other; very loose coupling.

and you actually want a channel on which actors could subscribe for the messages they are interested in. Or maybe you want your cluster to scale depending on the load, so you want to create actors dynamically depending on runtime stats, yet this is a far cry from bringing up HTTP servers dynamically in an AWS ELB environment, because it's more generic and those docs give me the creeps. 

I can’t answer those creeps, but spinning up new cluster nodes on AWS or GCE to participate in a cluster—with automatic actor deployment—is probably even simpler than doing the same with fat HTTP servers (not sure what you were referring to here, so correct me if I’m wrong).

Somewhere in that process, my head explodes, especially given that I haven't solved the first problem I always have, which is good future-proof design, because actors don't guide your implementation at all and many times it's totally non-obvious of what to do.

The actor model is a completely generic model of computation, just like lambda calculus or the Turing machine. Therefore it is not surprising that the model itself does not guide you on its own. When in doubt, anthropomorphize (as I did above).


Kudos on debating the deprecation of "sender". That's a step in making that interface more solid. I also await eagerly on your Reactive Streams - I do feel the need for an Rx streams implementation that also does back-pressure.

Yes, fully agreed :-)

Regards,

Roland


--
Alexandru Nedelcu
www.bionicspirit.com

PGP Public Key:
https://bionicspirit.com/key.aexpk

--
>>>>>>>>>> Read the docs: http://akka.io/docs/
>>>>>>>>>> Check the FAQ: http://doc.akka.io/docs/akka/current/additional/faq.html
>>>>>>>>>> Search the archives: https://groups.google.com/group/akka-user
---
You received this message because you are subscribed to the Google Groups "Akka User List" group.
To unsubscribe from this group and stop receiving emails from it, send an email to akka-user+...@googlegroups.com.
To post to this group, send email to akka...@googlegroups.com.
Visit this group at http://groups.google.com/group/akka-user.
For more options, visit https://groups.google.com/d/optout.

Daniel Armak

unread,
Mar 13, 2014, 6:06:11 PM3/13/14
to akka-user
On Thu, Mar 13, 2014 at 11:27 PM, Roland Kuhn <goo...@rkuhn.info> wrote:
Futures very cleanly model local dataflow. Actors very cleanly model distribution and concurrency. My guess is that if you try to create an abstraction in-between then it will have the disadvantages of both and the advantages of neither.

I think that's right, and I'm starting to think it might help to have a cleaner way of combining futures with actors. (At least it would help me, if not others.)

In one direction, I sometimes want an actor not to process any further messages until a Future completes. This is because a message caused the actor to do something implemented in a Future-based library and I want to serialize operations against that library, as if it were an internal implementation detail of the actor.

I could become an actor that stashes all messages until a continuation of the future sends me another message. But that requires a special mailbox and the actor actually reacts to each message. I don't know if there are any performance concerns here, but it would look cleaner if the actor could logically return a Future from receive and the dispatcher would understand that.

In the other direction, we have the ask pattern with its known shortcomings (can't receive more than one message, creating an actor per message can be expensive). It would be nice to send the actor a message + a promise it can complete, but with support for transparent remoting, perhaps falling back to the ask pattern's implementation in the remote case. (I'm assuming here that, for local message sends, creating a Promise is significantly cheaper than creating an Actor that will only ever receive one message. I may be wrong.)

Maybe the correct answer is it really isn't important enough to optimize and I should just use stash and pattern.ask everywhere. What do you think?

Roland Kuhn

unread,
Mar 13, 2014, 6:16:55 PM3/13/14
to akka-user
Hi Daniel,

13 mar 2014 kl. 23:06 skrev Daniel Armak <dana...@gmail.com>:

On Thu, Mar 13, 2014 at 11:27 PM, Roland Kuhn <goo...@rkuhn.info> wrote:
Futures very cleanly model local dataflow. Actors very cleanly model distribution and concurrency. My guess is that if you try to create an abstraction in-between then it will have the disadvantages of both and the advantages of neither.

I think that's right, and I'm starting to think it might help to have a cleaner way of combining futures with actors. (At least it would help me, if not others.)

There is the Aggregator pattern which helps in certain typical situations.


In one direction, I sometimes want an actor not to process any further messages until a Future completes. This is because a message caused the actor to do something implemented in a Future-based library and I want to serialize operations against that library, as if it were an internal implementation detail of the actor.

I could become an actor that stashes all messages until a continuation of the future sends me another message. But that requires a special mailbox and the actor actually reacts to each message. I don't know if there are any performance concerns here, but it would look cleaner if the actor could logically return a Future from receive and the dispatcher would understand that.

The idea with actors is that they shall remain responsive whenever possible. Another way to look at it is that the internal queue (or stash) should be limited, and when it overflows you might want to send a negative reply right away. For that the actor needs to be runnable while the Future executes.

In the other direction, we have the ask pattern with its known shortcomings (can't receive more than one message, creating an actor per message can be expensive). It would be nice to send the actor a message + a promise it can complete, but with support for transparent remoting, perhaps falling back to the ask pattern's implementation in the remote case. (I'm assuming here that, for local message sends, creating a Promise is significantly cheaper than creating an Actor that will only ever receive one message. I may be wrong.)

The ask pattern creates a very special PromiseActorRef which is just a wrapped Promise and therefore a lot lighter than even an Actor (which is not exactly heavy as well).


Maybe the correct answer is it really isn't important enough to optimize and I should just use stash and pattern.ask everywhere. What do you think?

Yes, Confucius says: measure first, and optimize only when the measurement tells you to.

Regards,

Roland


--
>>>>>>>>>> Read the docs: http://akka.io/docs/
>>>>>>>>>> Check the FAQ: http://doc.akka.io/docs/akka/current/additional/faq.html
>>>>>>>>>> Search the archives: https://groups.google.com/group/akka-user
---
You received this message because you are subscribed to the Google Groups "Akka User List" group.
To unsubscribe from this group and stop receiving emails from it, send an email to akka-user+...@googlegroups.com.
To post to this group, send email to akka...@googlegroups.com.
Visit this group at http://groups.google.com/group/akka-user.
For more options, visit https://groups.google.com/d/optout.

Daniel Armak

unread,
Mar 13, 2014, 6:43:42 PM3/13/14
to akka-user
On Fri, Mar 14, 2014 at 12:16 AM, Roland Kuhn <goo...@rkuhn.info> wrote:
The idea with actors is that they shall remain responsive whenever possible. Another way to look at it is that the internal queue (or stash) should be limited, and when it overflows you might want to send a negative reply right away. For that the actor needs to be runnable while the Future executes.

Thinking about it, most of my usecases of actors using futures are related to IO and other cases that I hope will be handled by reactive streams. 

Thanks,
Daniel

Reply all
Reply to author
Forward
0 new messages