Search code examples
classactorponylang

Replacing class keyword with actor causes an error


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?


Solution

  • 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 Eapproximatorin 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.