Mercerenies / gdlisp

Lisp on the Godot platform
GNU General Public License v3.0
141 stars 1 forks source link

Multimethods #11

Open Mercerenies opened 3 years ago

Mercerenies commented 3 years ago

This is a big one. I aim to support generic multiple dispatch method resolution, a la CLOS. That is, I aim to allow you to define generic methods

(defgeneric method-name (a b))
(defmethod method-name ((a int) (b int)) "Integers")
(defmethod method-name ((a Node) (b float)) "Node and float")

And when you call method-name, it'll check the types against the known method implementations. The above syntax is just an example, based on CLOS syntax; I'm not committed to using the same names as CLOS.

Step 1: Storing Global Data (Mostly Possible)

To get this working, we need a mechanism to actually store (potentially mutable) data on a script instance. One (rather messy hack of a) way to do so is with a 1-element const array.

const Example = [null]

Then the element is modifiable. Another (probably more idiomatic) way to do so is with object metadata, which is a feature that seems to exist for almost exactly this purpose. I feel the latter is probably more appropriate.

Step 2: Running Code on Preload (Needs Work)

We need to be able to run code when a script is first loaded. There are a few options, and they all have downsides.

  1. We can use a custom file type (rather than .gd, use .gdlsp, for example) and write a custom ResourceFormatLoader for that type. This works, but it has the unfortunate side effect of killing any IDE help, such as autocompletion, that we would get, and it also makes it impossible to implement class_name as that seems to only work on .gd files.
  2. We can force the existing .gd file to preload a .gdlsp file that contains only the startup code. This would be the most ideal solution, but the .gdlsp file doesn't have access to the .gd file during the load process, which kind of defeats the purpose (attempting to load it results in a cyclic load error).
  3. Like (2), but have .gdlsp use call_deferred to pass control back to .gd. This works and gives us the access we want, but it runs one frame later than the load, which means there's a whole frame (and a lot of _ready calls) that runs before the initialization event.
  4. It looks like ResourceLoader has a _loaded_callback field we can register, but it's not exposed to GDScript. I haven't experimented with it at all, but it may be possible to, in C++, register a callback against the resource loader itself. This would require either modding the Godot engine itself or dipping down into GDNative, neither of which sounds particularly appealing.

Step 3: Multimethods Themselves (Not Started)

After all of that, we need the actual multimethod algorithm. I believe lexicographic ordering should suffice (so a multimethod with a more specific first argument is always "better" than one with a less specific first argument, regardless of later arguments). This needs to be fleshed out a lot, but basically I think the Python multimethod library has some good ideas: run through all of the options, but cache the exact types of the arguments so that, if we call with the exact same types again, it's a cache hit.

Mercerenies commented 3 years ago

Built-in functions which are good candidates for becoming multimethods.

Already implemented:

Unimplemented but planned:

Mercerenies commented 3 years ago

Just to make it perfectly clear, #43 blocks this issue.