Here's my code:
class Eapproximator
var step : F64
new create(step' :F64) =>
step = step'
fun evaluate() :F64 =>
var total = F64(0)
var value = F64(1)
while total < 1 do
total = total + step
value = value + (value * step)
end
value
actor Main
new create(env: Env) =>
var e_approx = Eapproximator(0.00001)
var e_val = e_approx.evaluate()
env.out.print(e_val.string())
It works well and prints (as expected) 2.7183. However, if I replace class
with actor
in Eapproximator
definition I get a bunch of errors:
Error:
/src/main/main.pony:18:34: receiver type is not a subtype of target type
var e_val = e_approx.evaluate()
^
Info:
/src/main/main.pony:18:17: receiver type: Eapproximator tag
var e_val = e_approx.evaluate()
^
/src/main/main.pony:6:3: target type: Eapproximator box
fun evaluate() :F64 =>
^
/src/main/main.pony:3:3: Eapproximator tag is not a subtype of Eapproxim
ator box: tag is not a subcap of box
new create(step' :F64) =>
^
Error:
/src/main/main.pony:19:19: cannot infer type of e_val
env.out.print(e_val.string())
What can I do to fix this?
The actor is the unit of concurrency in Pony. This means that many different actors in the same program can run at the same time, including your Main
and Eapproximator
actors. Now what would happen if the fields of an actor were modified by multiple actors at the same time? You'd most likely get some garbage value in the end because of the way concurrent programs work on modern hardware. This is called a data race and it is the source of many, many bugs in concurrent programming. One of the goals of Pony is to detect data races at compile time, and this error message is the compiler telling you that what you're trying to do is potentially unsafe.
Let's walk through that error message.
receiver type is not a subtype of target type
The receiver type is the type of the called object, e_approx
here. The target type is the type of this
inside of the method, Eapproximator.evaluate
here. Subtyping means that an object of the subtype can be used as if it was an object of the supertype. So that part is telling you that evaluate
cannot be called on e_approx
because of a type mismatch.
receiver type: Eapproximator tag
e_approx
is an Eapproximator tag
. A tag
object can neither be read nor written. I'll detail why e_approx
is tag
in a minute.
target type: Eapproximator box
this
inside of evaluate
is an Eapproximator box
. A box
object can be read, but not written. this
is box
because evaluate
is declared as fun evaluate
, which implicitly means fun box evaluate
(which means that by default, methods cannot modify their receiver.)
Eapproximator tag is not a subtype of Eapproxim ator box: tag is not a subcap of box
According to this error message, a tag
object isn't a subtype of a box
object, which means that a tag
cannot be used as if it was a box
. This is logical if we look at what tag
and box
allow. box
allows more things than tag
: it can be read while tag
cannot. A type can only be a subtype of another type if it allows less (or as much) things than the supertype.
So why does replacing class
with actor
make the object tag
? This has to do with the data race problems I talked about earlier. An actor has free reign over its own fields. It can read from them and write to them. Since actors can run concurrently, they must be denied access to each other's fields in order to avoid data races with the fields' owner. And there is something in the type system that does exactly that: tag
. An actor can only see other actors as tag
, because it would be unsafe to read from or write to them. The main useful thing it can do with those tag
references is send asynchronous messages (by calling the be
methods, or behaviours), because that's neither reading nor writing.
Of course, since you're not doing any mutation of Eapproximator
in your program, your specific case would be safe. But it is much easier to try to forbid every unsafe program than to try to allow every safe program in addition to that.
To sum it up, there isn't really a fix for your program, except keeping Eapproximator
as a class. Not anything needs to be an actor in a Pony program. The actor is the unit of concurrency, but that means it is also the unit of sequentiality. Computations that need to be sequential and synchronous must live in a single actor. You can then break down those computations into various classes for good code hygiene.