Occasionally a computation can only be made once data is retrieved (messages are received) from two or more different data sources. For example, you can't tell whether you won the lottery until you've got your ticket number and the winning number. You can't make change for a purchase until you know the price and the amount tendered. You can't tell who wins a pushup contest until you know how many pushups each competitor completed.
Rholang has the join operator for exactly this situation. To perform a join, just use the ;
character.
for (p1Pushups <- player1; p2Pushups <- player2) {
stdout!("The winner is...")
}
When you use the join operator, neither message will be received until both messages are available.
A space exploration company wants to make sure their rocket only launches when both of two flight engineers, Alice and Bob, give the launch command. As an example, Bob would give the go ahead by sending bobLaunch!("launch")
. When both engineers give the command, the rocket can be launched.
Consider how this code might be written using the join operator that we just discussed.
One may have been tempted to solve the rocket problem by first receiving one launch command then the other.
REMINDER
This is the BAD way to solve the problem.
new aliceLaunch, bobLaunch, stdout(`rho:io:stdout`) in {
// Listen for Alice's then Bob's launch commands
for (x <- aliceLaunch){
for (y <- bobLaunch){
stdout!("Launching the rocket")
}
}
|
// When ready, Engineers send their commands
aliceLaunch!("launch")
|
bobLaunch!("launch")
}
The problem here is when Alice okay's the launch, but Bob hasn't yet. Alice should be able to change her mind, but she cannot. Imagine if she suddenly noticed a problem with the rocket, or received some bad news and wanted to abort the launch.
When using a join, she can still change her mind because the for
won't consume her launch message until Bob's message also appears and is ready to be consumed.
new aliceLaunch, bobLaunch, stdout(`rho:io:stdout`) in {
// Listen for both launch commands
for (x <- aliceLaunch; y <- bobLaunch){
stdout!("Launching the rocket")
}
|
// When ready, Engineers send their commands
aliceLaunch!("launch")
|
bobLaunch!("launch")
}
What code would Alice need to "par in" to retract her launch command.
aliceCancel!("cancel")
aliceLaunch!("cancel")
for (x <- aliceLaunch){ Nil }
I've explored the concept of joins in the context of the famous dining philosophers problem in this blog post.
In for (x <- y; a <- b){ Nil }
, which channel must be sent on first?
In for (x <- y; a <- b){ Nil }
, which message will be consumed first?
There is a game where two players will each send a message on separate channels. Whoever sends the first message, loses, and whoever sends the second message wins. Your task here is to write the game code that will tell which player won. To play the game, players should send messages like shown.
P1!("Send any message")
P2!("Hope I win")
In this case we actually don't want to use join because we care which player went first. Hope I didn't trick you ;)
new p1, p2, stdout(`rho:io:stdout`) in {
// Send messages like in both orders
p1!("Send any message") |
p2!("Hope I win") |
// When Player one wins
for (m2 <- p2){
for (m1 <- p1){
stdout!("Player one wins!")
}
}
|
// When player two wins
for (m1 <- p1){
for (m2 <- p2){
stdout!("Player two wins!")
}
}
}
Like the comment says, you should send the messages in both orders to make sure of who wins. The easiest way to do that right now is to have one player signal the other for when to go as shown below. We'll explore this concept further in the next lesson.
new p1, p2, signal in {
// P1 sends their message then signals P2 who is waiting
p1!("Send any message")
|
signal!("Go ahead, I'm done.")
|
// When P2 receives the signal, they send their message
for (_ <- signal){
p2!("Hope I win")
}
}
Why is it possible for nobody to win the patience game as we wrote it above?