Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optional typing in GDScript #10630

Closed
LeonardMeagher2 opened this issue Aug 25, 2017 · 38 comments · Fixed by #19264
Closed

Optional typing in GDScript #10630

LeonardMeagher2 opened this issue Aug 25, 2017 · 38 comments · Fixed by #19264

Comments

@LeonardMeagher2
Copy link
Contributor

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.

@LeonardMeagher2
Copy link
Contributor Author

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.

@27thLiz
Copy link
Contributor

27thLiz commented Aug 25, 2017

It's on the roadmap :)
https://github.com/godotengine/roadmap/blob/master/ROADMAP.md

@LeonardMeagher2
Copy link
Contributor Author

@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?

@27thLiz
Copy link
Contributor

27thLiz commented Aug 25, 2017

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.

@reduz
Copy link
Member

reduz commented Aug 25, 2017

@LeonardMeagher2 should look like what you wrote

@bojidar-bg
Copy link
Contributor

What about:

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

@LeonardMeagher2
Copy link
Contributor Author

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

@neikeq
Copy link
Contributor

neikeq commented Aug 25, 2017

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

@hubbyist
Copy link

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.

@LeonardMeagher2
Copy link
Contributor Author

@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?

@juan-garcia-m
Copy link

juan-garcia-m commented Aug 25, 2017

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.

@LeonardMeagher2
Copy link
Contributor Author

@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.

@Zireael07
Copy link
Contributor

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.

@LeonardMeagher2
Copy link
Contributor Author

LeonardMeagher2 commented Aug 25, 2017

@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).

@reduz
Copy link
Member

reduz commented Aug 25, 2017

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

@bojidar-bg
Copy link
Contributor

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

@LeonardMeagher2
Copy link
Contributor Author

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

@stwupton
Copy link

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.

@vnen
Copy link
Member

vnen commented Dec 12, 2017

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.

@stwupton
Copy link

stwupton commented Dec 13, 2017

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 😄. I'm enjoying developing in GDScript so far and I'm looking forward to it having a sound type system.

@reduz
Copy link
Member

reduz commented Dec 13, 2017

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

@DriNeo
Copy link

DriNeo commented Dec 19, 2017

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

var 
  a:int
  b:float
  c:char

const
  PI:float = 3.1416
  MAX:int = 100

@LeonardMeagher2
Copy link
Contributor Author

LeonardMeagher2 commented Jan 12, 2018

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

@NathanWarden
Copy link
Contributor

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.

@chanon
Copy link
Contributor

chanon commented Jan 29, 2018

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?

@vnen
Copy link
Member

vnen commented Jan 29, 2018

This should be available in Godot 3.1.

@vnen vnen added this to the 3.1 milestone Jan 29, 2018
@Jay-deFox
Copy link

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.

@akien-mga akien-mga changed the title Optional typing in GDScript? Optional typing in GDScript Mar 22, 2018
@m4nu3lf
Copy link
Contributor

m4nu3lf commented Apr 11, 2018

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?

@vnen
Copy link
Member

vnen commented Apr 11, 2018

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.

@m4nu3lf
Copy link
Contributor

m4nu3lf commented Apr 12, 2018

@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

@vnen
Copy link
Member

vnen commented Apr 13, 2018

@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.

@Zireael07
Copy link
Contributor

Why is this closed?

@LeonardMeagher2
Copy link
Contributor Author

Misread a bit, my bad.

@programaths
Copy link

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")

@bojidar-bg
Copy link
Contributor

@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 😃

@LeonardMeagher2
Copy link
Contributor Author

Should I close this?

@vnen
Copy link
Member

vnen commented Jun 10, 2018

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

@programaths
Copy link

@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 ? ;-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.