[Concurrency] Actors + Behaviors + Signals

Hi,

here are some high-level thoughts on extending Actors with Behaviors and Signals.

Actors + Behaviors:

The set of messages an Actor might handle at a time is defined by the current behavior which can be changed with the `become` instruction:

behavior B1 {
  message m1()
  message m2() -> Int
}

behavior B2 {
  message m3(str: String)
}

actor A1: B1, B2 {
  init() {
    become B1
  }
  message m1() {
    print(„m1“)
    become B2
  }
  message m2() -> Int {
    print(„m2“)
    return 1
  }
  message m3(str: String) {
    print(„m3\(str)“)
    become B1
  }
}

`behavior` is a kind of `protocol` which contains only actor functions.

BTW, to allow for some bikeshed discussions here - I propose to use `message` instead of `actor func` - like preferring `actor` to `actor class`.

Behaviors are optional - if you don’t define/adopt behaviors, all messages in an actor are part of an implicit behavior.

If a message arrives, which is not part of the current behavior, it will stay in the message queue but ignored until a supporting behavior becomes current again.

To allow the reception of another message while a message handler is currently suspended in some async call - maybe communicating with another actor - an `interleaved` modifier for messages might be introduced. These interleaved messages are not allowed to change the current behavior though.

Actors + Signals:

Like classes, which often provide reactive APIs (via `delegates`, `KVO`, `NotificationCenter`, `Observables`, …) for unsolicited events, Actors will need a way to send messages to unknown other Actors.

I thus propose the introduction of Signals:

actor A2 {
  signal s1(val: Int)

  message m1(val: Int) {
    print(„m1“)
    s1(val)
  }
}

actor A3 {
  message m1(val: Int) { … }
  message m2(str: String) { … }
}

let a2 = A2()
let a3 = A3()
a2.s1.connect(to: a3.m1)

Signals might also support stream functions like `map`, `filter`, `throttle`, `distinctUntilChanged`, etc, to allow for reactive value transformations of signals sent by actors:

a2.s1.throttle(interval: 0.2).map { String($0) }.connect(to: a3.m2)

Cheers
Marc

Hi,

to anyone interested, I put some experimental Actor demo here:

The motivation of it is to have some setup to be able to play with async
actor methods for further discussions. As we don't have async/await right
now, I use Continuation Passing Style instead. I think this demo helps to
understand the difference between the actors dispatch queue and its
message/activity queue.

Also, I added `Behaviour` support to it, as I think this is a necessary
part unless we introduce a `wait-for-any-message-in()` method which can be
placed anywhere inside an actor-method.

Obviously, topics like securely passing arguments, master-worker setup,
actor lookup, remote capabilities etc. are not touched here.

Cheers
Marc

···

On Sun, Aug 20, 2017 at 9:43 PM, Marc Schlichte via swift-evolution < swift-evolution@swift.org> wrote:

Hi,

here are some high-level thoughts on extending Actors with Behaviors and
Signals.

Actors + Behaviors:

The set of messages an Actor might handle at a time is defined by the
current behavior which can be changed with the `become` instruction:

behavior B1 {
  message m1()
  message m2() -> Int
}

behavior B2 {
  message m3(str: String)
}

actor A1: B1, B2 {
  init() {
    become B1
  }
  message m1() {
    print(„m1“)
    become B2
  }
  message m2() -> Int {
    print(„m2“)
    return 1
  }
  message m3(str: String) {
    print(„m3\(str)“)
    become B1
  }
}

`behavior` is a kind of `protocol` which contains only actor functions.

BTW, to allow for some bikeshed discussions here - I propose to use
`message` instead of `actor func` - like preferring `actor` to `actor
class`.

Behaviors are optional - if you don’t define/adopt behaviors, all messages
in an actor are part of an implicit behavior.

If a message arrives, which is not part of the current behavior, it will
stay in the message queue but ignored until a supporting behavior becomes
current again.

To allow the reception of another message while a message handler is
currently suspended in some async call - maybe communicating with another
actor - an `interleaved` modifier for messages might be introduced. These
interleaved messages are not allowed to change the current behavior though.

Actors + Signals:

Like classes, which often provide reactive APIs (via `delegates`, `KVO`,
`NotificationCenter`, `Observables`, …) for unsolicited events, Actors will
need a way to send messages to unknown other Actors.

I thus propose the introduction of Signals:

actor A2 {
  signal s1(val: Int)

  message m1(val: Int) {
    print(„m1“)
    s1(val)
  }
}

actor A3 {
  message m1(val: Int) { … }
  message m2(str: String) { … }
}

let a2 = A2()
let a3 = A3()
a2.s1.connect(to: a3.m1)

Signals might also support stream functions like `map`, `filter`,
`throttle`, `distinctUntilChanged`, etc, to allow for reactive value
transformations of signals sent by actors:

a2.s1.throttle(interval: 0.2).map { String($0) }.connect(to: a3.m2)

Cheers
Marc

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution