tonarino / actor

A minimalist actor framework aiming for high performance and simplicity.
MIT License
40 stars 6 forks source link

A better way to organize `spawn` function family? #10

Closed skywhale closed 3 years ago

strohel commented 3 years ago

Current state (with docs tweaks):

    /// Spawn a normal [`Actor`] in the system.
    pub fn spawn<A>(&mut self, actor: A) -> Result<Addr<A>, Error>;

    /// Spawn a normal [`Actor`] in the system, with non-default capacity for its input channel.
    pub fn spawn_with_capacity<A>(&mut self, actor: A, capacity: usize) -> Result<Addr<A>, Error>;

    /// Spawn a normal Actor in the system, using a factory that produces an [`Actor`].
    pub fn spawn_fn<F, A>(&mut self, factory: F) -> Result<Addr<A>, Error>;

    /// Spawn a normal Actor in the system, using a factory that produces an [`Actor`],
    /// with non-default capacity for its input channel. See [`System::spawn_fn()`].
    pub fn spawn_fn_with_capacity<F, A>(&mut self, factory: F, capacity: usize) -> Result<Addr<A>, Error>;

    /// Spawn a normal Actor in the system, using a factory that produces an [`Actor`],
    /// and an address that will be assigned to the Actor.
    pub fn spawn_fn_with_addr<F, A>(&mut self, factory: F, addr: Addr<A>) -> Result<(), Error>;

There are 3 independent aspects (capacity or not, direct or factory, explicit vs. implicit address) that together create combinatorial explosion.

For the direct or factory aspect, I don't see any better alternative than a pair of functions. Stdlib with funs like or(), or_else() is the precedent here.

The "generic" solution to this is a builder pattern (which I haven't fallen in love with). How would that look like?

let addr = system.prepare(actor).with_capacity(20).spawn()?;

// This would probably needlessly clone() and return addr, which would be dropped with away.
system.prepare_fn(|| make_actor(param)).with_addr(its_addr).with_capacity(1).spawn()?;  

...not that bad? #[must_use] could prevent folks from forgetting to call .spawn(). All names subject to bikeshedding.

If we decide to approach the builder pattern:

  1. Should we still provide system.spawn(actor) shorthand? (yes)
  2. Should we still provide system.spawn_fn(|| factory()) shorthand? (no)
  3. Should we provide system.prepare(actor) in addition to system.prepare_fn(|| factory())? (yes?)
bschwind commented 3 years ago

The builder pattern actually seems like the right approach here. I also wouldn't worry about needless clones for initialization code.

The API you've prototyped here seems pretty reasonable and is definitely an improvement over the current situation.

skywhale commented 3 years ago

I agree with @bschwind. With modern IDE integration, a builder pattern could provide a more straightforward coding experience, without having to internalize all the function variants.

mcginty commented 3 years ago

+1, builder pattern looks great here.