Hi, Jesper,
Of course, one can choose a type that somehow subsumes all the types
that an actor receives. (As a minor point, note that when using `Either`
or other types, one has to re-organize the code accordingly.)
However, after giving it more thought I realized that the problem comes
from the dynamic scoping of `self` which refers to the currently
executing actor:
actor {
actor {
... self ...
}
... self ...
}
In the above example the two occurrences of `self` refer to two
different actors. Note that `self` has dynamic scope, therefore we can
use `self` inside a function, e.g.
def f(x: Int): Unit = {
... self ...
}
and it will continue to refer to the currently executing actor even if
the concrete actor instance is not known at compile time:
actor {
actor {
if (b1) f(0)
}
if (b2) f(1)
}
Assuming that using `actor` one could create typed actors `Actor[+Msg]`,
and types `S` and `T` are unrelated, then in
actor[S] {
actor[T] {
... self ...
}
... self ...
}
the type of `self` would have to be `Actor[Nothing]` and therefore
useless for typed message sends.
Currently, I see two approaches to allow typed actors:
1. Remove the dynamically-scoped `self` altogether. Replace the untyped
`actor` function with
def actor[T](Actor[T] => Unit): Actor[T]
Example use:
actor[Int] { self => ... }
The downside to this is that there is no common way to refer to
the currently executing actor (regardless of how it is created). As a
consequence, some patterns used in, e.g., Erlang, are ruled out.
2. Allow some dynamic scoping w.r.t. `self`:
Keep the untyped `actor` function as a way to create `Actor[Any]`s.
These actors can use `self` with dynamic scope.
Typed actors are created by sub-classing `Actor[+Msg]`. Inside typed
actors, use of the dynamically-scoped `self` results in a runtime
error.
Cheers,
Philipp
Jesper Nordenberg wrote:
> Philipp Haller wrote:
>> 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.
>
> So, in this case you create an Actor[Any] (or a more advanced type with
> Either or the message sequence encoded in the type). I can't think of
> any reason why the Actor class shouldn't have a type parameter.
>
>> 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)]
>> }
>
> The usefulness of message type check is not primarily on the receiver
> side, it's on the caller side. You don't want to send a message of
> incorrect type to an actor and get a runtime error.
>
> In essence, you can view an actor as a function of type "A =>
> Future[B]". Types A and B are clearly related, if you send an actor a
> certain message type you would expect a reply of a certain type. How can
> we encode this in Scala? The straightforward way would be to encode the
> return type in the message type, for example:
>
> trait Message { type ReturnType }
>
> case class MyMess(x : Int, y : Int) extends Message {
> type ReturnType = Int
> }
>
> trait Actor[Msg] {
> def ! : Future[T#ReturnType]
> }
>
> (alternatively "trait Actor[Msg <: Message]")
>
> However, implementing the receive method in a type safe way is AFAIK not
> possible to do in Scala, you would have to resort to casting.
>
> /Jesper Nordenberg
>