Generic programming question

Edit: Added a simpler example towards the end.
Edit: Added a C++ example for clarity towards the end.

Hey all,

Just started looking at SkookumScript recently. It seems like a cool language and I have high hopes for it reducing the amount of blueprints and C++ I might need to do.

Having said that, I’m a bit puzzled as to how I might go about this task I’ve set myself on.

I want to implement something akin to the c# event functionality. If I don’t care about type safety, this seems fairly straightforward, it would be something along these lines (pseudocode):

// Event class members:

@handlers List{ItemClass_}

! () Object
[
@handlers : {}
]

add_assign (ItemClass_ handler) List{ItemClass_}
[
  @handlers.append(handler)
  @handlers
]

// This should work fine so far as ItemClass is always just a single object.
// While not my main concern here, it would be nice to not have that restriction.
// For instance, to allow using handlers with signatures like (Integer i, String s).
// I am curious if there's a nice way to handle that?
invoke (ItemClass_ i)
[
  @handlers.do[item(i)]
]

subtract_assign (ItemClass_ handler) List{ItemClass_}
[
  @handlers.remove_same(handler)
  @handlers
]

So I could instantiate an Event, add some handlers to it, invoke it with some input.

The problem I have with this approach, though, is that the interface to the Event class hides the type.
Actually, it basically doesn’t care at all about the type.
That is to say, the below would compile despite the erroneous manner in which the types are used:

// In some method using an Event:
!e : Event!

e += (Actor a)[println("Actor name is: " a.name)]
e += (Integer i)[println("Integer value is: " i)]

// Though even if one of Actor or Integer were used, one of the added handlers would not work correctly,
// they really can't coexist.
e.invoke("oh look a string")

Ideally, I’d be able to instead do something like this and have type safety enforced. I’d like the below code to fail to compile when the wrong types are used:

// Specify that the Actor is the type to operate on.
!e : Event!(Actor)

// Ok because e is aware it should use Actor for its ItemClass_
e += (Actor a)[println("Actor name is: " a.name)]

// Fails to compile because e is aware it should use Actor for its ItemClass_
e += (Integer i)[println("Integer value is: " i)]

// Likewise fails to compile because again, not an Actor
e.invoke("oh look a string")

For a simpler example:

// Methods of a class "GenericClass"

// Specify a type to act on somehow...
!(<Object> type) Object
[
]

// The typed_print method should (somehow) determine its signature requires
// thing_to_print to be of the type specified in the constructor.

typed_print(type.class thing_to_print)
[
  println(thing_to_print)
]

// Usage:

// Create an instance of GenericClass that requires the type to be String, somehow...
!generic_class : GenericClass!(String)

// Should compile because the passed argument is of the correct type.
generic_class.typed_print("a string is fine")

// Should fail to compile because the passed argument is not of the correct type.
generic_class.typed_print(12345)

In C++

#include <iostream>
#include <string>

template <class T>
class GenericClass
{
  public GenericClass() {}

  public void typed_print(T thing_to_print)
  {
    std::cout << thing_to_print << std::endl;
  }
}


int main(int argc, char** argv)
{
  // Create an instance of GenericClass with std::string as its template parameter.
  GenericClass<std::string> generic_class;

  // Compiles because the passed argument is of the correct type, a std::string.
  generic_class.typed_print("A string is fine");

  // Fails to compile because the passed argument is not of the correct type.
  generic_class.typed_print(12345);
}

Is it possible to do something like this in SkookumScript?

Thank you for reading.

This is an interesting question that kind of brings to light one major way in which :sk: breaks from other languages that are strongly typed.

At least in the :ue4: implementation of :sk:, you cannot create more than 1 function of the same name. So while in your example, C++ would generate prototypes for:
typed_print(std::string)
typed_print(int32) etc

In :sk:, you’d only be able to create a single typed_print method, regardless of any difference in parameters. That said, this may be a somewhat self-imposed limitation since :sk: is able to call any template generated strongly typed methods.

The type system doc page is a good reference: https://skookumscript.com/docs/v3.0/lang/typesystem/
I’d say as close as you’d get might be using Union Types if you’re looking for a :sk: only solution.

But, why not just write that piece in C++? That’s really what it’s meant for while :sk: would be more of the master stage manager.

I can see how SkookumScript’s implementation might complicate this or make it not possible; I thought it might be doable, though, since SkookumScript can provide static type checking on List, which is generic.

I guess C++ could be an option, I just saw generic programming mentioned in SkookumScript’s documentation, and with List apparently exhibiting the behavior I wanted my own code to have, figured it must be possible to achieve a similar result.

About as close as I can get is something like the following REPL example code using Union Types:

!handlers : List{(<Boolean|Integer|Real> type)}!
!test : (<Boolean|Integer|Real> type)[println("You did it ", type.class)]
!test2 : (<Boolean|Integer|Real> type)[println("Bools are the best") when type.class = Boolean]
handlers.append(test)
handlers.append(test2)
handlers.do[item(3.14)]
handlers.do[item(true)]
handlers.do[item("this fails")]

Results in output:

You did it Real
You did it Boolean
Bools are the best
ERROR: The argument supplied to parameter named `type` was expected to be of type `<Real|Boolean|Integer>` and it is type `String` which is not compatible.

The REPL syntax is a bit different with the variable and list initializers !, let me know if I can clear up any of it.

Interesting, I’ll have to see if I can adapt this to my needs.

My ultimate goal is to have an Event class, that all events are based on; this class would contain:

  • a list of handlers
  • a method to add a handler
  • a method to invoke the handlers (taking and using the correct parameters)

Then I could create classes that have Event members, with types specified for the Events to act on.

That way, I could avoid repeating the same code for appending methods to handlers and invoking the handlers for every type of Event I use, while also having static type checking so I don’t have to remember the parameters for every type of Event.

If I understand what you’ve posted correctly, I could create a list that handles every type of handler that might be desired, and prevents using any that are not; that would allow ensuring that Events can only use certain types, but the problem is that every Event will be able to use every one of those types, when they should each only be able to use one.

It’s possible I have misunderstood what you’ve posted, though; I’ll have to look into this further.
Thank you for the suggestion! :slight_smile:

Exactly this. It’s not really desirable, but like I showed in the bool implementation, you can check the class type in the handler. If there are alternatives in the language, I’m not aware of them, @Noolarch would be the one that really knows.

This is also a great but slightly hard to find post on doing handlers in :sk:

Thanks for the link, though funny enough that’s the first thing I tried. :slight_smile:

IIRC, my complaints about it were:

  1. I wanted to be able to have events be members of the classes handling them, not only the EventManager.
  2. I’d either be directly using the handler lists, or else forced to wrap every one of them.

I’ll take another look at it, though, maybe it’ll give me some ideas. It is a pretty nice system overall.

1 Like