Is there something analogous to callbacks as opposed to closures?


#1

I see Closures as a type of ‘lamda’ expression, but I don’t see how to use them as traditional callback functions. Is there something like that? Here’s a simple example in C++ I just wrote off the top of my head (might have errors):

class A {
public:
    A() : data(0) {}
    void foo(int addToData) { this->data += addToData; }

    int data;
};

class B {
public:
	B() : callback(NULL) {}
	setCallback(void (*cb)(int)) { callback = cb; }
	call(int val) { (callback)?(*callback)(val); }
	void (*callback)();
};

main() {
	A a;
	B b;
	B.setcallack(a.foo);
	B.call(9000);
}

I would use it to have one class register one of its functions with another class that would call it with some data it needs at a later time.

I realize there are other ways to do this, but was wondering if this kind of thing is possible at all in Sk.


#2

This is a good example on how you’d do callbacks using a list of closures.

Also this one is quite similar.


#3

Those look like a good starting point. I’m looking to implement callbacks with different parameters; say a reference, a value, and a name. I’ll look into it some more.


#4

I’ll add some more detailed examples later tonight here on the forum.


#5

Here is how you would implement in SkookumScript what you wrote in C++:

A class:

// A!Data
Integer !@data

// A!() Constructor
()
  [
  @data: 0
  ]
  
// A@foo()
(Integer addToData)
  [
  @data += addToData
  ]

B class:

// B!Data
(Integer addToData) !@callback
Boolean             !@callback?

// B!() Constructor
() 
  [
  @callback?: false
  ]

// B@setCallBack()
((Integer addToData) callback)
  [
  @callback:  callback
  @callback?: true
  ]

// B@call()
(Integer addToData)
  [
  @callback(addToNum) when @callback?
  ]

Using the callback

!a: A!
!b: B!
b.setCallBack[a.foo(addToData)]
b.call(9000)
b.call(9)
a.@data  // returns 9009

Note that there are several ways to set the callback:

// Fully specify the closure - has lots of brackets
b.setCallBack((Integer addToData)[a.foo(addToData)])
// Same as above - infer the closure parameters from setCallBack()
b.setCallBack(^[a.foo(addToData)])
// Same as above - cleanest: brackets are optional when closure is last parameter
b.setCallBack[a.foo(addToData)]
// Similar to above, though with `a` as the `this` and parameters inferred
b.setCallBack(^a[foo(addToData)])

Though this usually isn’t the easiest way to do this sort of thing in SkookumScript. :madsci:

Often callbacks are used as a mechanism to do an action after something that takes time. Since SkookumScript is designed for concurrency this can be really easy:

(Using commands that will work in SkookumDemo)

// Some time-taking command
// - note the underscore `_` that indicates it is a coroutine
Enemy@'RoboChar1'._path_to_actor(player_pawn)
// Some completion command to call after time-taking command
// - acts as the "callback"
player_pawn._boom
// Use this to reset the robot to its starting position
robo_reset

Sure, you say. Though you want it to be a fire and forget command and not have the rest of your code have to wait for your time-taking command. All you need to do is wrap it in one of the concurrency flow control commands:

// Simple fire and forget
branch
  [
  Enemy@'RoboChar1'._path_to_actor(player_pawn)
  player_pawn._boom  // callback
  ]

// Commands to run at the same time - it might complete before or after the above
Enemy@'RoboChar2'._path_to_actor(player_pawn)
Enemy@'RoboChar3'._path_to_actor(player_pawn)
// Reset the robots
robo_reset
// Run at the same time as some other expressions or code blocks
sync
  [
  // Treated as a group
    [
    Enemy@'RoboChar1'._path_to_actor(player_pawn)
    player_pawn._boom  // callback
    ]

  // Run at the same time as the group above
    Enemy@'RoboChar2'._path_to_actor(player_pawn)

  // Run at the same time as the group above
    Enemy@'RoboChar3'._path_to_actor(player_pawn)
  ]
// Reset the robots
robo_reset
// Run at the same time as some other expressions or code blocks
// and stop whenever the fastest completes
race
  [
  // Treated as a group
    [
    Enemy@'RoboChar1'._path_to_actor(player_pawn)
    player_pawn._boom  // callback
    ]

  // Run at the same time as the group above
    Enemy@'RoboChar2'._path_to_actor(player_pawn)

  // Run at the same time as the group above
    Enemy@'RoboChar3'._path_to_actor(player_pawn)
  ]
// Reset the robots
robo_reset

There are lots of other mechanisms to run specified code after other code that takes some time, though hopefully this makes sense and gives some ideas.

If you have any more questions on callbacks, closures or concurrency flow control feel free to ask.