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
andEapproximator
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.
The receiver type is the type of the called object,
e_approx
here. The target type is the type ofthis
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 thatevaluate
cannot be called one_approx
because of a type mismatch.e_approx
is anEapproximator tag
. Atag
object can neither be read nor written. I'll detail whye_approx
istag
in a minute.this
inside ofevaluate
is anEapproximator box
. Abox
object can be read, but not written.this
isbox
becauseevaluate
is declared asfun evaluate
, which implicitly meansfun box evaluate
(which means that by default, methods cannot modify their receiver.)According to this error message, a
tag
object isn't a subtype of abox
object, which means that atag
cannot be used as if it was abox
. This is logical if we look at whattag
andbox
allow.box
allows more things thantag
: it can be read whiletag
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
withactor
make the objecttag
? 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 astag
, because it would be unsafe to read from or write to them. The main useful thing it can do with thosetag
references is send asynchronous messages (by calling thebe
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.