Skip to main content

Object Run

#[run] does for a whole object what #[main] does for a document: self.run() executes every #[run]-tagged function and field on that object, treating it as a workflow rather than something you call one function at a time.

Basic Run​

Every #[run] function on the object executes when you call run():

basic-run.stofPlayground ↗
Workflow: {
  out: []

  #[run]
  fn step_one() { self.out.push_back('one'); }

  #[run]
  fn step_two() { self.out.push_back('two'); }
}

#[main]
fn main() {
  self.Workflow.run();
  pln(self.Workflow.out);
}
Output

Ordering with #[run(N)]​

A number controls execution order — regardless of the order things are declared in the document:

ordering.stofPlayground ↗
Workflow: {
  out: []

  #[run(3)]
  fn third() { self.out.push_back(3); }

  #[run(1)]
  fn first() { self.out.push_back(1); }

  #[run(2)]
  fn second() { self.out.push_back(2); }
}

#[main]
fn main() {
  self.Workflow.run();
  pln(self.Workflow.out);
}
Output

Running Nested Objects​

If a child object is itself #[run]-tagged, running the parent recurses into it — each step in a multi-stage workflow can be its own object:

nested.stofPlayground ↗
Workflow: {
  #[run(1)]
  step_one: {
      #[run]
      fn execute() { pln('step 1'); }
  }

  #[run(2)]
  step_two: {
      #[run]
      fn execute() { pln('step 2'); }
  }
}

#[main]
fn main() {
  self.Workflow.run();
}
Output

Passing Arguments​

#[run({'args': [...]})] passes arguments through to the function it's attached to:

args.stofPlayground ↗
Workflow: {
  #[run({'args': [42]})]
  fn setup(val: int) {
      self.configured = val;
  }
}

#[main]
fn main() {
  self.Workflow.run();
  pln(self.Workflow.configured);
}
Output

Running a List​

#[run] on a list field runs every element — an arrow function, an object with its own #[run] members, whatever's in there:

list-run.stofPlayground ↗
Workflow: {
  done: false
  sub_done: false

  #[run]
  steps: [
      () => { self.done = true; },
      { #[run] fn inner() { super.sub_done = true; } }
  ]
}

#[main]
fn main() {
  self.Workflow.run();
  pln(self.Workflow.done, self.Workflow.sub_done);
}
Output

A Realistic Shape​

None of these mechanics need a network to demonstrate, but the pattern they're building toward usually involves one — fetch something, validate what comes back, and record the result, all in one self-contained object:

#[type]
Task: {
ok: false
result: null
config: {
endpoint: "https://myendpoint"
schema: { /* a schema to validate the result against */ }
}

#[run]
fn exec() {
const res = await Http.fetch(self.config.endpoint);

const result = new {};
parse(res.remove("text"), result, "json");

self.ok = self.config.schema.schemafy(result);
self.result = result;
}
}

This one isn't runnable here — Http isn't a library this sandbox has enabled — but it's the shape #[run] is really for: a task object that knows how to execute itself, called the same way (task.run()) whether it's this or one of the five-line examples above it.

Async Run​

Obj.run(..) is syncronous on purpose, but it is common to have async pipelines. With Async, there are many ways one can go about this.

This example may give you a few ideas:

async-run.stofPlayground ↗
#[type]
/// Setup a type that handles async the way you'd like.
/// Common to put helpers in a Job type that can resolve to the Workflow parent, etc.
Workflow: {
  list jobs: []
  list handles: []

  #[run(1)]
  fn run_jobs() {
      for (const job in self.jobs) {
          // in real-life I'd switch on type and add fn (async gets pushed to handles, etc.)
          if (job.parent() != self) job.move(self);
          job.run();
      }
  }

  #[run(2)]
  fn await_handles() {
      pln('awaiting handles', self.handles.len());
      await self.handles;
      self.handles = []; // reset handles for any #[run(3)] and beyond
  }
}

#[type]
Job: {
  text: 'default'
  #[run] fn running() { pln(self.text); }
}

#[type]
AsyncJob: {
  #[run]
  fn running() {
      const handles = super.handles ?? [];
      handles.push_back(async { sleep(10ms); pln('Running an Async Job!'); });
  }
}

#[main]
fn main() {
  const flow = new Workflow {};
  flow.jobs.push_back(new Job { text: 'First Job' });
  flow.jobs.push_back(new AsyncJob {});
  flow.run();
  pln('Done Running');
}
Output