Godot: Optional typing in GDScript

Created on 25 Aug 2017  ·  38Comments  ·  Source: godotengine/godot

I don't know if this would be needed or interesting to any developer, but I think it might be useful to have optional typing in GDScript and was hoping to have a discussion about it here.

I've been learning the programming language called "Nim" because it has a pretty friendly module for GDNative and I'm interesting in learning to contribute there (since C++ is difficult for me visually BUT unrelated to this, just some background). Nim is statically typed which I know GDSCript is dynamic but I came across a situation in which I might want to specify type to make things easier.

Mainly when writing a function, it'd be nice if I could specify the type of a variable that is allowed to be passed in so the code completion could know what I'm talking about, but I'm sure there are other advantages to this.

Something like

func myfunction(a, b : int, c : InputEvent , d : String = "Default Value"):
    (...)

Where a is dynamic, b-d are static but show what I think the syntax could look like.

I know you can specify type hint for the editor when using the export keyword, but you don't have to keep the variable at that type once the script executes. This idea could be against what GDScript is meant to be, or too much work for very little gain, but lets talk.

discussion feature proposal gdscript

Most helpful comment

This should be available in Godot 3.1.

All 38 comments

It looks like you can do

func myfunction(something = InputEvent):

and that will give you an empty input event if they don't pass anything, but doesn't enforce the variable to be that type

myfunction('string')

would work fine.

@Hinsbart
Any references to what it might look like or how it'll function with other parts of GDScript? Or is it just an idea on the road map?

Unfortunately no, there aren't any references for it yet, besides some irc discussions.

CC @reduz, to give some hints of what he has in mind for this.

@LeonardMeagher2 should look like what you wrote

What about:

func myfunction(something is InputEvent, something_else is String):

That's pretty good to me, makes the is keyword more useful and the code easier to understand

I disagree. I would be fine with either String something or something : String. something is String looks bad to me.

String something or something is String both fine for me. Using colon will make lots of colon signs in the code I think.

If is will not be used I would prefer String something because I am used to this notation from other languages and I think it is more common.

@neikeq
If you could declare the type when creating a var also, like var something:String = " " I'd agree

Otherwise I think it would be more confusing to use the semicolon in just function definitions.

"Is" is used to ask about inheritance in gdscript now, so why couldn't it require it?

C like type prefix would be better IMO. It is the shorter, faster to type and can replace var or func when using types.

Example:

int some_variable

int some_function(int some_argument):
    return some_variable + some_argument

My vote on that.

@juan-garcia-m
I don't think it makes it more understandable to someone who isn't familiar with c like typing. I think it might be easier to type, but much less readable.
I prefer at a glance to know what is a function or variable, and replacing those keywords with types would remove that ability for me; It's one of the reasons C and C++ are difficult for me to get into.

Obviously the language can't be made for me, but I'm sure I'm not the only one that feels this way.

One problem with "is", it's currently used in 3.0 for what used to be "extends", i.e. checking if a node belongs to a class.

@Zireael07
That's why it seems appropriate to check the type in a function declaration to me.
Extends is still used at the top of he script, but for type checking "is" is used (and typeof for literals I think).

C style prefix is more complicated in general, and for these types of languages the standard seems to be the variable : type convention, so that will be used

Why was this closed? It is still not implemented...

I closed it because it was just a discussion, but I'll reopen it to track the progress of the desired feature

I really do hope that the C style prefix gets reconsidered. From my experience with Typescript, I feel less productive when initialising variables and I personally feel like it results in less readable code. Having to write this...

var foo: String = 'bar'

...instead of just replacing the var keyword with the type...

String foo = 'bar'

...drives me crazy. I'm aware that this sounds very small and nit-picky, but over the long term of working on a game this just results in way more keystrokes than what's necessary.

Here's how it should look:
godot windows tools 64_2017-12-12_17-21-27

Following the Python syntax (also similar to TypeScript and Haxe).

This is quite opinionated. For instance, I wrote a lot of TypeScript code and really like the syntax. Like tabs vs. spaces, everyone will have their own opinion of what's is best, usually related to their background. I find it easier to see var and know it's a variable instead of seeing String and not knowing if it'll be a function or a variable. I don't see "faster to type" as a good argument, "harder to make a mistake" and "easier to read" are quite better (which still is subject to opinion though).

Another point pro the Python way: it's much easier to parse. The current parser won't have to change much to accommodate this. For the prefixed types, it'll have to consider more stuff about the context to decide what to do.

Yeah code readability is very subjective. I rewrote your example in how it would look like with the C style syntax. _I_ find the code below more readable and I have no trouble differentiating between variables and functions.

extends Node

const int MY_CONSTANT = 0
String my_member_variable = "Value"

void my_empty_func():
    pass

float my_other_func(bool arg1, Node2D arg2, bool default = false):
    Vector2 local_variable
    int other_variable = 0

    return float(other_variable)

I have to disagree with "faster to type" not being a good argument as it directly affects a developers productivity. The Python style is slightly more cumbersome to read and write than C's is, even more so now seeing we will have to type -> just to define a return type. The only arguments I can see for the Python style syntax is 1. to appeal more to those who are familiar with Python and 2. because it's easier to implement, and I don't think that either are particularly good arguments. I'm not suggesting that it's an easy thing to implement, I'm sure it's more difficult and most likely beyond my skill level, but I think it would be worth it in the long run.

Anyway, that's just my two pennies' worth :smile:. I'm enjoying developing in GDScript so far and I'm looking forward to it having a sound type system.

Python already supports this, so I think the best we can do is copy from there

For people who hate the var keyword it could be possible to add the Nim like syntax who allow something like this.

  a:int
  b:float
  c:char

const
  PI:float = 3.1416
  MAX:int = 100

I wonder why return types are needed, if you are returning and that output is going to a variable of a specific type and they don't match, it'd throw then.
I guess you'd get the editors ability to find that mistake for you before you run your code.

The return type void doesn't make much sense to me, I imagine It'd be easier to just omit the return type definition in that case.

Finally the -> does seem weird to me if we were going to have return types, but I get that you might not do something nim-like func somefunc(): String: (maybe?) but I'm not a fan of that arrow like syntax since it also doesn't indicate much to me, seems directional but it has nothing to do with that, reminds me of bit-wise ops >> and << but those are directional in a way.

__Did not mean to close the issue__

I can see return types being known to be important in some cases since it will now be possible to read every function for a Node, its variables, and its return type.

It may not make a lot of sense now, but people will be able to write plugins and even game code that can be extremely dynamic in how it works. IE, a plugin could have this warning message: "This Node requires a function with x return type, but the Node you're trying to reference has no method that has the required method signature."

I wrote a plugin for Unity called AI Behavior that uses quite a bit of this type of stuff (via System.Reflection) for user friendliness. I literally have a drop down menu I populate with available method names and let them select which method they want to use as a callback based on all the methods that have the correct signature, then when the game runs, that's the method that gets called under the circumstance it's made for. If no method exists on the object/script they're trying to access it on then it will show sample code in the inspector so they can just make a script via copy/pasting the code and renaming the class. :)

So, my thought is that being able to have as much script info as possible will make Godot that much more extensible for plugin writers.

I also think TypeScript style syntax (using var name: String = "Godot") is preferable as it has become somewhat of a standard syntax for optional typing added to dynamic languages.

Also, following python syntax is a good idea:
https://www.python.org/dev/peps/pep-0484/
https://docs.python.org/3/library/typing.html

This will help people coming to GDScript from similar dynamic languages with optional typing.

BTW, when / in what release can we expect this to be implemented? I've seen 3.1 mentioned?

This should be available in Godot 3.1.

About the arguments against C style prefix being more complicated or less understandable/readable and it being more welcoming to go with python syntax or similar—aren't the docs all using that C style sort of prefix with all methods and such? Like, just to pull one off the Camera page really quick set_orthogonal ( float size, float z_near, float z_far ) I didn't quite understand it myself at first, but it only really takes a moment to learn I think, and it totally makes sense after that.

Hi, proposal here.

Rationale:
I think we need static typing but I would avoid forcing the user to specifing types if possible.
All major compiled languages are moving toward static type inference.
I know that @reduz doens't really like type inference for the Godot source code, and I can understand it for a clarity point of view. However I think this compromise may work really well for GDScript as it doen't hinder coding speed that much, with most of the advantages of static type checking.
Not sure if the implementation is more complicated though.
This would require some additional costructs as algebraic data types.
Here is a language that uses this principle

https://crystal-lang.org/docs/

to keep backward compatibility some new keyword will be needed for defining functions and variables. Or maybe we could have some magic comment to enable/disable static type checking.

Here is an example of what this kind of type system can do (for the ones who know a bit of ruby)

def test(x)
    return true if x
    return "This is false"
end

puts test(false).upcase
puts test(true)

output:

Error in test.cr:6: undefined method 'upcase' for Bool (compile-time type is (Bool | String))

puts test(false).upcase
                 ^~~~~~

Thoughts?

I do want to improve the type inference, especially for the code completion but also for checking non-existing properties/methods and invalid argument count at editing time. I think this part of a second step after optional typing is done, so I'm not fretting myself over it for now.

The example from @m4nu3lf goes quite far, it's actually inferring the resulting type based on the branching. I can't see how that's done without simulating the execution of the code, where it can get quite complex. I'm not sure how far @reduz wants to go with GDScript.

Consider that Godot has a quite strict type system, but all variables are Variant (i.e. they can store any type). Since this is not meant to break compatibility, they have to remain like that. So doing var value = 42 won't automatically assume the variable is an integer, especially for class-level ones that can be set at will by external scripts. That hinders a bunch what type inference can do (can be used like this for completion purposes though).

One possibility, as mentioned, is to make this optional. Maybe a project setting, since I don't see much value of this being enabled/disabled per-script. Still, that's a separate proposal to be thought in a next step.

@vnen In theory by using some new keyword for parameters/return types/variables like 'auto' or 'let' and using the same algorithm of crystal-lang you could be able to infer the type at compile time.
It is static typing with full type inference.
Let's say you have
auto my_field = 42 in a script you could simply infer the type to be int and restrict the underlying variant to ints only.

However that language goes beyond simple variables. It can restrict the type to a subset of types depending on the branches.
like in the example above the return type can only be a String OR a Bool.
But in order to do this you must

  • Instantiate functions for each new combination of argument types and infer the internal types for each return expression, save the list of possible return types as the type of the return value.
  • For branches expore all the possible branches where a variable is assigned to, to infer all the possible types of the variable at a given time. At any given time, restrict the type of the variant to those

Furthermore you must check for "instanceof" conditions and like in

if my_var instanceof MyClass:
...

then you know that in the body of the if condition my_var can only be 'MyClass' so the type is restricted even more.
For this to have any advantage you must only be able to call methods or access fields that have names shared across all the possible types.
So let's say

auto x = MyClass.new()
if (...):
   x = "hi"

Then after this x is either a MyClass or a String. you will only be able to call methods that are common to both to avoid runtime errors.

I give that this is a lot more work of what I originally thought.
So I want to go back on this. Maybe it can be implemented in the future, or maybe I can hope for someone to write binding for crystal-lang.
Either way I think it is a really cool feature

@m4nu3lf I understand what your request is, TypeScript is very similar (which is a language I'm more familiar with).

However, there's a few problems to consider for GDScript:

  • First, it's not trivial to implement that. I can look at how other languages make it happen, but still it would take a long time. That's why I want to go one step at time and think about this later. TypeScript was made by the same guy who made Turbo Pascal and C#, so there's a lot of experience there that I don't have.
  • GDScript is compiled one file at time. Crystal and TypeScript both requires you to compile the whole project before using it. That means it's a bit cloudy to check interaction with other scripts and singletons.
  • The scene tree. With get_node() (which is used a lot) it's simply impossible to know what node type you'll get. You only know it's a Node but it can be of any subtype. Same applies to Resources when you call load(), especially if you're calling it with a dynamic name. _input(event) has the same problem, since event can be any InputEvent subtype.
if my_var instanceof MyClass:
    ...

This is already inferred for code completion. Could be reused for actual type-checking. This case in particular is trivial, but it can get quite involved. For instance, if you add a second condition: if some value == 42 and my_var is Sprite, then code completion won't work anymore.


I also want to hear feedback from users, so have something available soon is more important (to me) than have everything perfect after a long wait time.

Summing up: I do want to improve type inference, but it has to be made step by step, with small improvements. Once optional typing is available, the rest is uphill.

Why is this closed?

Misread a bit, my bad.

Back to prefix and postfix: UML and Kotlin both use ": type".
Basic did too "DIM a as INT" (and used "as" instead of ":" or "is")

@programaths It has been a long time since, and it has been discussed over and over. What's more, there is a PR which implements postfix, so it is a bit out of the question now :smiley:

Should I close this?

@LeonardMeagher2 it'll be closed automatically when my PR is merged.

@bojidar-bg I just wanted to add few places where postfix are used because it's interesting by itself. UML is meant for analysis and non-technical people too. Kotlin is a language which was developped to be more comfortable and more modern than Java while implementing "effective java".

So, if you need to write down why postfix has been chosen, these can be included too. Sometimes, people want to have reasons and easily dismiss technicals ones unless you relate them to the user.
(e.g. "Parser can resolve ambiguïties earlier and has a speed up of 100x meaning the same program can be compiled faster and permits more testing)

Always good to have material, isn't it ? ;-)

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mefihl picture mefihl  ·  3Comments

gonzo191 picture gonzo191  ·  3Comments

rgrams picture rgrams  ·  3Comments

nunodonato picture nunodonato  ·  3Comments

blurymind picture blurymind  ·  3Comments