gdlisp
gdlisp copied to clipboard
Multimethods
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.
- We can use a custom file type (rather than
.gd
, use.gdlsp
, for example) and write a customResourceFormatLoader
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 implementclass_name
as that seems to only work on.gd
files. - 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). - Like (2), but have
.gdlsp
usecall_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. - 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.
Built-in functions which are good candidates for becoming multimethods.
Already implemented:
-
len
- Could work on lists and arrays -
instance?
- Could dispatch on int or GDScriptNativeClass rather than doing the check inGDLisp.gd
-
elt
- Currently blindly indexes with[]
; could make it work on lists too
Unimplemented but planned:
-
map
/filter
/ etc. - Lists and arrays
Just to make it perfectly clear, #43 blocks this issue.