Sending and receiving is one of the most important things to understand in Rholang.
Because Rholang is an asynchronous programming language, there is no return in a function.That's why you call a function
HelloWorld(args)
via sending on thefunctions
name HelloWorld. You are sending via the ! syntax.Because of the asynchrony you don't know when the
function
you called via HelloWorld!(args) is sending the processed data back over some other channel. Thats why there is noreturn
in thefunction
. Instead Rholang has the for(args<-channels) syntax for waiting until all the needed arguments are received over the channels.
new HelloWorld, stdout(`rho:io:stdout`) in {
HelloWorld!("Hello, world!") |
for (@text <- HelloWorld) {
stdout!(text)
}
}
This line declares a new name-valued variable HelloWorld
and assigns to it a newly-created private name.
Every name is of the form @P
, where P
is a rholang process. The !
production takes a name n
on its left and a process P
on its right, then sends @P
over the channel named n
. Lineforms the name @"Hello, world"
and sends it on the channel whose name is stored in the variable HelloWorld
.
This for
production creates a process that waits for a single message to be sent on the channel whose name is stored in the variable HelloWorld
. The pattern @text
gets matched against the serialized process, binding the process-valued variable text
to the original process that was sent.
Rholang runtime environments may choose to include built-in processes listening on channels. In this tutorial, we use new with the urn rho:io:stdout
to request a channel where sent messages get printed to a console.
When you send data over a channel and there is no contract which is listening with for(args<-channels) for that channel, then these data is stored in the tuplespace (like a database), until some contract is starting to listening for it.
It is possible to write one single name in several different ways. For example, the two following channels are equivalent:
@{10 + 2}
@{5 + 7}
Any message sent over these channels can be received by listening on the channel @12
. There are other instances in which a name can be written in two different ways. The guiding principle for this is that if P
and Q
are two equivalent processes, then @P
and @Q
are equivalent names. In particular, all of the following channels are equivalent:
@{ P | Q }
@{ Q | P }
@{ Q | P | Nil }
Before using a channel, Rholang first evaluates expressions and accounts for these |
rules at the top level--but only at the top level. This means that if an arithmetic expression forms part of a pattern within a pattern, it is left untouched. Because of this,
for( @{ x + 5 } <- @"chan" ){ ... }
will never receive any message on @"chan"
since if we send anything, such as 10 + 5
, over @"chan"
, the arithmetic expression gets evaluated and the name @15
is sent.
Finally, channels also respect a change in variable name (alpha equivalence), so the following channels are equivalent:
@{ for( x <- chan ){ ... } }
@{ for( z <- chan ){ ... } }
new HelloWorld, stdout(`rho:io:stdout`) in {
for (@text <= HelloWorld) {
stdout!(text)
} |
HelloWorld!("Hello, world!") |
HelloWorld!("Hola, mundo!")
}
2.) Instead of handling only a single message, a for
using a double arrow <=
will persist, spawning a copy of the body for each message received.
5-6) We send the string processes "Hello, world!"
and "Hola, mundo!"
on the channel HelloWorld
. It is non-deterministic which message will be processed first.
new HelloWorld, stdout(`rho:io:stdout`) in {
contract HelloWorld(@text) = {
stdout!(text)
} |
HelloWorld!("Hello, world!") |
HelloWorld!("Hola, mundo!")
}
2.) The only difference between this example and the last one is this line. The contract
production is syntactic sugar. The process contract Name(...formals) = { P }
means the same as for (...formals <= Name) { P }
.
new HelloWorld, stdout(`rho:io:stdout`), stderr(`rho:io:stderr`) in {
HelloWorld!!("Hello, world!") |
for (@text <- HelloWorld) {
stdout!(text)
} |
for (@text <- HelloWorld) {
stderr!(text)
}
}
2.) The double-bang !!
means that this message will be sent again as soon as it is read.
3-4, 6-7) There are two listening processes; each one consumes a single message from the channel and forwards it to either "stdout"
or "stderr"
. The order in which they get forwarded to those channels is nondeterministic.
In order to have one message follow after another is known to have been received, we must use an acknowledgement message.
new chan, ack, stdoutAck(`rho:io:stdoutAck`) in {
chan!(0) |
for (_ <- ack) {
chan!(1)
} |
for (@num <= chan) {
stdoutAck(num, *ack)
}
}
chan
.ack
, throw it away, and then proceed with the body.chan
.chan
."stdoutAck"
, which expects both a value to print and a channel on which to send an acknowledgement message that the text has been received and printed. In this program, we are guaranteed that 0 will be printed before 1.new chan, stdout(`rho:io:stdout`) in {
chan!(1,2,3) |
chan!((4,5,6)) |
chan!(7,8) |
chan!([9, 10], 11) |
chan!(12 | 13) |
for (@x, @y, @z <= chan) {
stdout!(["three", x, y, z])
} |
for (@a, @b <= chan) {
stdout!(["two", a, b])
} |
for (@a <= chan) {
stdout!(["one", a])
}
}
chan
. This send necessarily synchronizes with the for
on line 7.chan
. This send necessarily synchronizes with the for
on line 13.chan
. This send necessarily synchronizes with the for
on line 10.chan
. This send necessarily synchronizes with the for
on line 10.chan
. This send necessarily synchronizes with the for
on line 13.