3. Programmer avec des acteurs et des messages▲
Voici un exemple montrant un domaine d'application pour lequel Scala est particulièrement bien adapté. Supposons que l'on veuille mettre en place un service d'enchères électroniques. Nous utiliserons le modèle de traitement par acteurs d'Erlang pour implémenter les participants aux enchères. Les acteurs sont des objets auxquels on envoie des messages. Chaque acteur dispose d'une « boîte aux lettres » dans laquelle arrivent ses messages et qui est représentée comme une file d'attente. Il peut traiter séquentiellement les messages de sa boîte ou rechercher les messages qui correspondent à certains critères.
Pour chaque article mis aux enchères, il y a un acteur commissaire-priseur qui publie les informations sur cet article, qui reçoit les offres des clients et qui communique avec le vendeur et celui qui a remporté l'enchère afin de clore la transaction. Nous présenterons ici une implémentation simple de cet acteur.
La première étape consiste à définir les messages échangés au cours d'une enchère. Nous utiliserons deux classes de base abstraites, AuctionMessage pour représenter les messages des clients adressés au service des enchères et AuctionReply pour représenter les réponses du service aux clients. Pour ces deux classes de base, il existe un certain nombre de cas, définis dans le code ci-dessous.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
import
scala.actors.Actor
abstract
class
AuctionMessage
case
class
Offer
(
bid: Int
, client: Actor
) extends
AuctionMessage
case
class
Inquire
(
client: Actor
) extends
AuctionMessage
abstract
class
AuctionReply
case
class
Status
(
asked: Int
, expire: Date) extends
AuctionReply
case
object
BestOffer extends
AuctionReply
case
class
BeatenOffer
(
maxBid: Int
) extends
AuctionReply
case
class
AuctionConcluded
(
seller: Actor
, client: Actor
)
extends
AuctionReply
case
object
AuctionFailed extends
AuctionReply
case
object
AuctionOver extends
AuctionReply
Ces case classes définissent le format des différents messages de chacune des classes de base. Ces messages pourraient être traduits en petits documents XML, car nous supposons qu'il existe des outils automatiques permettant d'effectuer des conversions entre documents XML et ces représentations internes.
Le code ci-dessous présente une implémentation Scala d'une classe Auction permettant de représenter les acteurs qui coordonnent les enchères pour un article particulier. Les objets de cette classe sont créés en indiquant :
- un acteur vendeur qui doit être prévenu lorsque l'enchère est terminée ;
- une enchère minimale ;
- la date de fin des enchères.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
class
Auction
(
seller: Actor
, minBid: Int
, closing: Date) extends
Actor
{
val
timeToShutdown =
36000000
// msec
val
bidIncrement =
10
def
act
(
) {
var
maxBid =
minBid -
bidIncrement
var
maxBidder: Actor
=
null
var
running =
true
while
(
running) {
receiveWithin ((
closing.getTime
(
) -
new
Date
(
).getTime
(
))) {
case
Offer
(
bid, client) =>
if
(
bid >=
maxBid +
bidIncrement) {
if
(
maxBid >=
minBid) maxBidder !
BeatenOffer
(
bid)
maxBid =
bid; maxBidder =
client; client !
BestOffer
}
else
{
client !
BeatenOffer
(
maxBid)
}
case
Inquire
(
client) =>
client !
Status
(
maxBid, closing)
case
TIMEOUT
=>
if
(
maxBid >=
minBid) {
val
reply =
AuctionConcluded
(
seller, maxBidder)
maxBidder !
reply; seller !
reply
}
else
{
seller !
AuctionFailed
}
receiveWithin
(
timeToShutdown) {
case
Offer
(
_, client) =>
client !
AuctionOver
case
TIMEOUT
=>
running =
false
}
}
}
}
}
Le comportement de l'acteur est défini par sa méthode act qui passe son temps à sélectionner un message (avec receiveWithin) et qui y réagit ; ceci jusqu'à la fin de l'enchère, qui est signalée par le message TIMEOUT. Avant de se terminer, l'acteur reste actif pendant une période déterminée par la constante timeToShutdown et répond aux autres offres que l'enchère est terminée.
Voici quelques informations supplémentaires sur les constructions utilisées dans ce programme :
- • la méthode receiveWithin de la classe Actor prend en paramètres une durée en millisecondes et une fonction qui traite les messages de la boîte aux lettres. Cette fonction est décrite par une suite de cas qui précisent, chacun, un motif et une action à réaliser lorsqu'un message correspond à ce motif. La méthode receiveWithin sélectionne le premier message de la boîte aux lettres qui correspond à l'un de ces motifs et lui applique l'action correspondante ;
- • le dernier cas de receiveWithin utilise le motif TIMEOUT. Si aucun autre message n'est reçu dans l'intervalle, ce motif est déclenché après l'expiration du délai passé à la méthode receiveWithin englobante. TIMEOUT est un message spécial qui est déclenché par l'implémentation de la classe Actor ;
- • les messages de réponse sont envoyés en utilisant la syntaxe destination ! UnMessage. Le symbole ! est utilisé ici comme un opérateur binaire portant sur un acteur et un message : c'est l'équivalent Scala de l'appel destination.!(UnMessage), c'est-à-dire de l'appel de la méthode ! de l'acteur destination avec le message en paramètre.
Cette présentation donne un avant-goût de la programmation distribuée en Scala. Il pourrait sembler que Scala dispose d'un grand nombre de constructions gérant les processus acteurs, l'envoi et la réception des messages, la gestion des délais d'expiration, etc., mais il n'en est rien : toutes ces constructions sont des méthodes de la classe Actor de la bibliothèque de Scala. Cette classe est elle-même implémentée en Scala et repose sur le modèle sous-jacent des threads du langage hôte (Java ou .NET). La section 17.11Acteurs présentera l'implémentation de toutes les fonctionnalités de la classe Actor utilisées ici.
Se reposer sur une bibliothèque permet d'avoir un langage relativement simple et d'offrir beaucoup de souplesse aux concepteurs des bibliothèques. En effet, le langage n'ayant pas besoin de spécifier les détails de la communication entre les processus, il peut rester plus simple et plus général. En outre, le modèle particulier des messages stockés dans une boîte aux lettres étant un module de la bibliothèque, il peut être librement modifié si d'autres applications ont besoin d'autres modèles. Cette approche nécessite toutefois que le langage soit suffisamment expressif pour fournir les abstractions nécessaires de façon simple. Scala a été conçu dans cet esprit : l'un des principaux objectifs était que le langage soit suffisamment souple pour servir de langage hôte pour des langages spécifiques au domaine implémentés au moyen de modules de bibliothèque. Les constructions permettant la communication des acteurs, par exemple, peuvent être considérées comme l'un de ces langages spécifiques, qui étend conceptuellement le langage Scala de base.