Hi, Ricky,
First, why are actors currently untyped?
Originally, the actors library emerged from an experiment to implement
Erlang in Scala. In that model, it is common to have nested receives, i.e.
receive {
case first =>
...
receive {
case second => ...
}
}
Now, if the types of `first` and `second` are unrelated, then certainly
the actor must be able to accept messages of type `Any`. Otherwise, at
least one of the receives would block indefinitely which is undesirable.
If the outer receive could enforce that inner receives operate on a
related message type, then we could give the actor a more precise
(input) type.
Why does this "untyped" scheme work surprisingly well in Scala? Pattern
matching helps you recover types, e.g.:
case class DoThis(x: List[(Int, String)])
receive {
case DoThis(args) => // know that args: List[(Int, String)]
}
Since you alluded to the simplicity of your library:
Much of the complexity of the actors library stems from the fact that an
actor can suspend in a nested event-based receive (called `react`). This
is explained in the first actors paper [1]. There is a set of
combinators, such as `loop`, that makes actors written in that style
composable. To some extent this is explained in the second actors paper [2].
There are more reasons why `scala.actors` doesn't fit on a screenful of
code. An important one is that it is guaranteed that only a single
thread is executing "inside" an actor at a time (which your
implementation does not guarantee since it submits `Callable`s right
away). For this you need an additional message queue. Other reason are
interoperability with Java threads (see [2]), monitoring through links etc.
Now, if you would disallow nested receives, then you could certainly
give those restricted actors a more precise type.
Basically, given a function `fun: M => R` the typed actors would fit the
pattern
loop {
react { case any => reply(fun(any)) }
}
where `fun` would not contain any calls to potentially suspending
methods, such as `react`.
In fact, this is how you can do input-typed actors using the existing
library:
def typedActor[A](fun: A => Unit): OutputChannel[A] = {
val sink = new SyncVar[Channel[A]]
actor {
val in = new Channel[A](self)
sink set in
loop {
in react { case any => reply(fun(any)) }
}
}
sink.get
}
Output types are also useful, e.g., to type synchronous and future-type
message sends, such as `!?` and `!!`. However, this probably requires a
little more effort.
But, I agree with you and others that it would good to have more support
for this in the library.
Cheers,
Philipp
[1] Haller, Odersky: Event-based Programming without Inversion of
Control, Proc. JMLC 2006
[2] Haller, Odersky: Actors that Unify Threads and Events, Proc.
COORDINATION 2007
Ricky Clarkson wrote:
> Hi all,
>
> I came across a use case for actors in my Java-only job, so I started
> thinking about how to go about implementing them for Java (or possibly
> using Scala's actors as a library, though $bang isn't a great method
> name). I learned enough of Erlang to be dangerous, then looked at
> Scala's implementation, to see how it would typically be implemented on
> the JVM. I was quite surprised to see that Scala's actors' messages are
> dispatched on through pattern matching - not the usual pattern matching
> that you can use to take a List, Option or Either to bits, but the kind
> equivalent to instanceof and casts from Javaland - i.e.,
> BreaksWhenYouChangeStuff(tm).
>
> So I had a quick go and had typed actors working well in Java in a
> couple of hours, enough to get my job done. Of course, programming for
> a Javaland project means that certain features seem too leftfield, e.g.,
> using Either, tuples or Option, so in my own time I decided to port my
> typed actors to less than a screenful of Scala.
>
> Then I had a look at Phillip Haller's tutorial on Scala Actors (
>
http://lamp.epfl.ch/~phaller/doc/ActorsTutorial.html ), and ported his
> PingPong example to my library. (Library sounds grandiose for less than
> a screenful of code, but nevermind). I link here to my version of that
> example -
http://cime.net/~ricky/tmp/PingPong.scala . I tried to keep
> close to the original, but I found it hard to keep track of using Ping
> and Pong twice (as a class extending Actor and as a case object
> representing a message), so I renamed the case objects to PingMessage
> and PongMessage (and changed them to case classes).
>
> But for the second example, I really have no idea why you would ever use
> actors to implement synchronous iteration over a tree, so I deleted my
> half-written port of that until I have fresher eyes. If anyone can
> explain why that example is useful, please do!
>
> So far though, I'm really unsure why the original library is untyped -
> if it turns out when I do more interesting things with my version that
> typing is impossible or too awkward sometimes, it would make sense to
> route around it, but to have untyping as the default in Scala seems.. odd.
>
> So, please enlighten me - why is it better to have untyped actors than
> typed actors? If you quote the test case above in your explanation,
> please don't count Either against it - that can easily be removed.
>
> Here's my working version of the 'library' for typed actors - names
> copied from scala.actors, laughably small -
>
http://cime.net/~ricky/tmp/Actor.scala . I'm not really sure right now
> whether Actor should have a result type too (it does in that version) or
> just a message type.
>
> Cheers,
> Ricky.