Godot: Digitação opcional em GDScript

Criado em 25 ago. 2017  ·  38Comentários  ·  Fonte: godotengine/godot

Não sei se isso seria necessário ou interessante para algum desenvolvedor, mas acho que pode ser útil ter digitação opcional em GDScript e esperava ter uma discussão sobre isso aqui.

Tenho aprendido a linguagem de programação chamada "Nim" porque tem um módulo bastante amigável para GDNative e estou interessado em aprender a contribuir lá (já que C ++ é visualmente difícil para mim, MAS não relacionado a isso, apenas alguns antecedentes). Nim é digitado estaticamente, o que eu sei que GDSCript é dinâmico, mas me deparei com uma situação em que talvez queira especificar o tipo para tornar as coisas mais fáceis.

Principalmente ao escrever uma função, seria bom se eu pudesse especificar o tipo de variável que pode ser passada para que o autocompletar de código possa saber do que estou falando, mas tenho certeza de que há outras vantagens em isto.

Algo como

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

Onde a é dinâmico, bd são estáticos, mas mostram como eu acho que a sintaxe poderia ser.

Eu sei que você pode especificar a dica de tipo para o editor ao usar a palavra-chave export, mas você não precisa manter a variável nesse tipo depois que o script for executado. Essa ideia pode ser contra o que o GDScript deve ser, ou muito trabalho para muito pouco ganho, mas vamos conversar.

discussion feature proposal gdscript

Comentários muito úteis

Isso deve estar disponível em Godot 3.1.

Todos 38 comentários

Parece que você pode fazer

func myfunction(something = InputEvent):

e isso dará a você um evento de entrada vazio se eles não passarem nada, mas não impõe que a variável seja desse tipo

myfunction('string')

funcionaria bem.

@Hinsbart
Alguma referência à sua aparência ou como funcionará com outras partes do GDScript? Ou é apenas uma ideia no roteiro?

Infelizmente não, ainda não há referências para ele, além de algumas discussões sobre irc.

CC @reduz , para dar algumas dicas do que ele tem em mente para isso.

@ LeonardMeagher2 deve ser parecido com o que você escreveu

A respeito:

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

Isso é muito bom para mim, torna a palavra-chave is mais útil e o código mais fácil de entender

Discordo. Eu ficaria bem com String something ou something : String . something is String me parece ruim.

String something ou something is String são bons para mim. Usar dois pontos fará muitos sinais de dois pontos no código, eu acho.

Se is não for usado, eu preferiria String something porque estou acostumado com essa notação de outras línguas e acho que é mais comum.

@neikeq
Se você pudesse declarar o tipo ao criar uma var também, como var something:String = " " eu concordaria

Caso contrário, acho que seria mais confuso usar o ponto-e-vírgula apenas nas definições de função.

"Is" é usado para perguntar sobre herança em gdscript agora, então por que não poderia exigir isso?

C como prefixo de tipo seria melhor IMO. É o mais curto, mais rápido de digitar e pode substituir var ou func ao usar tipos.

Exemplo:

int some_variable

int some_function(int some_argument):
    return some_variable + some_argument

Meu voto sobre isso.

@ juan-garcia-m
Não acho que isso torna mais compreensível para alguém que não está familiarizado com C, como digitar. Acho que pode ser mais fácil de digitar, mas muito menos legível.
Prefiro saber rapidamente o que é uma função ou variável, e substituir essas palavras-chave por tipos removeria essa capacidade para mim; É uma das razões pelas quais C e C ++ são difíceis para mim.

Obviamente, a linguagem não pode ser feita para mim, mas tenho certeza de que não sou o único que se sente assim.

Um problema com "é", atualmente é usado no 3.0 para o que costumava ser "extends", ou seja, verificar se um nó pertence a uma classe.

@ Zireael07
É por isso que me parece apropriado verificar o tipo em uma declaração de função.
Extends ainda é usado no topo do script, mas para verificação de tipo "is" é usado (e typeof para literais, eu acho).

O prefixo de estilo C é mais complicado em geral, e para esses tipos de linguagens o padrão parece ser a variável: convenção de tipo, de modo que será usado

Por que isso foi fechado? Ainda não foi implementado ...

Fechei porque era apenas uma discussão, mas vou reabri-lo para acompanhar o progresso do recurso desejado

Eu realmente espero que o prefixo do estilo C seja reconsiderado. Pela minha experiência com o Typescript, me sinto menos produtivo ao inicializar variáveis ​​e, pessoalmente, sinto que isso resulta em um código menos legível. Tendo que escrever isso ...

var foo: String = 'bar'

... em vez de apenas substituir a palavra-chave var pelo tipo ...

String foo = 'bar'

... me deixa louco. Estou ciente de que isso soa muito pequeno e minucioso, mas no longo prazo de trabalho em um jogo, isso resulta em mais toques no teclado do que o necessário.

Veja como deve ser:
godot windows tools 64_2017-12-12_17-21-27

Seguindo a sintaxe Python (também semelhante a TypeScript e Haxe).

Isso é bastante opinativo. Por exemplo, escrevi muito código TypeScript e gosto muito da sintaxe. Assim como as tabulações e os espaços, todos terão sua própria opinião sobre o que é melhor, geralmente relacionada ao seu histórico. Acho mais fácil ver var e saber que é uma variável em vez de ver String e não saber se será uma função ou uma variável. Não vejo "mais rápido para digitar" como um bom argumento, "mais difícil de cometer um erro" e "mais fácil de ler" são muito melhores (o que ainda está sujeito a opinião).

Outro ponto a favor do caminho do Python: é muito mais fácil de analisar. O analisador atual não terá que mudar muito para acomodar isso. Para os tipos prefixados, será necessário considerar mais coisas sobre o contexto para decidir o que fazer.

Sim, a legibilidade do código é muito subjetiva. Reescrevi seu exemplo em como ficaria com a sintaxe de estilo C. _Eu acho o código abaixo mais legível e não tenho problemas para diferenciar entre variáveis ​​e funções.

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)

Eu tenho que discordar de "mais rápido digitar" não ser um bom argumento, pois afeta diretamente a produtividade do desenvolvedor. O estilo Python é um pouco mais complicado de ler e escrever do que o C, ainda mais agora vendo que teremos que digitar -> apenas para definir um tipo de retorno. Os únicos argumentos que posso ver para a sintaxe de estilo Python são 1. para atrair mais aqueles que estão familiarizados com Python e 2. porque é mais fácil de implementar, e não acho que sejam argumentos particularmente bons. Não estou sugerindo que seja uma coisa fácil de implementar, tenho certeza que é mais difícil e provavelmente está além do meu nível de habilidade, mas acho que valeria a pena a longo prazo.

De qualquer forma, isso vale apenas meus dois centavos: sorriso :

Python já suporta isso, então acho que o melhor que podemos fazer é copiar de lá

Para pessoas que odeiam a palavra-chave var, pode ser possível adicionar a sintaxe do tipo Nim, que permite algo assim.

  a:int
  b:float
  c:char

const
  PI:float = 3.1416
  MAX:int = 100

Eu me pergunto por que os tipos de retorno são necessários, se você está retornando e essa saída está indo para uma variável de um tipo específico e eles não correspondem, seria lançado então.
Acho que você conseguiria que os editores encontrassem esse erro para você antes de executar o código.

O tipo de retorno void não faz muito sentido para mim, imagino que seria mais fácil simplesmente omitir a definição do tipo de retorno nesse caso.

Finalmente, o -> parece estranho para mim se tivéssemos tipos de retorno, mas entendo que você não pode fazer algo como func somefunc(): String: (talvez?), Mas eu não sou um fã daquela seta como a sintaxe, pois também não indica muito para mim, parece direcional, mas não tem nada a ver com isso, me lembra de operações bit a bit >> e << mas aqueles são direcionais de uma forma.

__ Não pretendia encerrar o problema__

Posso ver os tipos de retorno sendo conhecidos como importantes em alguns casos, pois agora será possível ler todas as funções de um Nó, suas variáveis ​​e seu tipo de retorno.

Pode não fazer muito sentido agora, mas as pessoas serão capazes de escrever plug-ins e até mesmo códigos de jogos que podem ser extremamente dinâmicos em seu funcionamento. Ou seja, um plug-in pode ter esta mensagem de aviso: "Este Nó requer uma função com tipo de retorno x, mas o Nó que você está tentando fazer referência não possui nenhum método com a assinatura de método necessária."

Eu escrevi um plugin para o Unity chamado AI Behavior que usa um pouco desse tipo de coisa (via System.Reflection) para facilitar o usuário. Eu literalmente tenho um menu suspenso que preencho com os nomes dos métodos disponíveis e os deixo selecionar o método que desejam usar como retorno de chamada com base em todos os métodos que têm a assinatura correta, então, quando o jogo é executado, esse é o método que é chamado em a circunstância para a qual foi feito. Se nenhum método existir no objeto / script em que eles estão tentando acessá-lo, ele mostrará o código de amostra no inspetor para que eles possam fazer um script copiando / colando o código e renomeando a classe. :)

Então, meu pensamento é que ser capaz de ter o máximo possível de informações de script tornará Godot muito mais extensível para os criadores de plug-ins.

Eu também acho que a sintaxe de estilo TypeScript (usando var name: String = "Godot" ) é preferível, pois se tornou uma espécie de sintaxe padrão para digitação opcional adicionada a linguagens dinâmicas.

Além disso, seguir a sintaxe Python é uma boa ideia:
https://www.python.org/dev/peps/pep-0484/
https://docs.python.org/3/library/typing.html

Isso ajudará as pessoas a chegar ao GDScript de linguagens dinâmicas semelhantes com digitação opcional.

BTW, quando / em que versão podemos esperar que isso seja implementado? Eu vi 3.1 mencionado?

Isso deve estar disponível em Godot 3.1.

Sobre os argumentos contra o prefixo de estilo C serem mais complicados ou menos compreensíveis / legíveis e ser mais acolhedor para ir com a sintaxe python ou similar - todos os documentos não estão usando esse tipo de prefixo de estilo C com todos os métodos e tal? Tipo, apenas puxar um da página da Câmera bem rápido set_orthogonal ( float size, float z_near, float z_far ) Eu não entendi muito bem no começo, mas só leva um momento para aprender, eu acho, e faz totalmente sentido depois disso.

Olá, proposta aqui.

Justificativa:
Acho que precisamos de tipagem estática, mas eu evitaria forçar o usuário a especificar tipos, se possível.
Todas as principais linguagens compiladas estão se movendo em direção à inferência de tipo estático.
Eu sei que @reduz realmente não gosta de inferência de tipo para o código-fonte Godot, e posso entendê-lo para um ponto de vista mais
Não tenho certeza se a implementação é mais complicada.
Isso exigiria alguns costructs adicionais como tipos de dados algébricos.
Aqui está uma linguagem que usa este princípio

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

para manter a compatibilidade com versões anteriores, alguma nova palavra-chave será necessária para definir funções e variáveis. Ou talvez pudéssemos ter algum comentário mágico para habilitar / desabilitar a verificação de tipo estático.

Aqui está um exemplo do que este tipo de sistema pode fazer (para aqueles que conhecem um pouco de rubi)

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

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

saída:

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

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

Pensamentos?

Eu quero melhorar a inferência de tipo, especialmente para o autocompletar de código, mas também para verificar propriedades / métodos não existentes e contagem de argumentos inválidos no momento da edição. Acho que essa parte de uma segunda etapa após a digitação opcional é feita, então não estou me preocupando com isso agora.

O exemplo de @ m4nu3lf vai muito longe, na verdade está inferindo o tipo resultante com base na ramificação. Não consigo ver como isso é feito sem simular a execução do código, onde pode ficar bastante complexo. Não tenho certeza de até onde @reduz deseja ir com GDScript.

Considere que Godot tem um sistema de tipos bastante restrito, mas todas as variáveis ​​são Variant (ou seja, podem armazenar qualquer tipo). Já que isso não tem o objetivo de quebrar a compatibilidade, eles devem permanecer assim. Portanto, fazer var value = 42 não assumirá automaticamente que a variável é um número inteiro, especialmente para as de nível de classe que podem ser definidas à vontade por scripts externos. Isso dificulta muito o que a inferência de tipo pode fazer (embora possa ser usada dessa forma para fins de conclusão).

Uma possibilidade, conforme mencionado, é tornar isso opcional. Talvez uma configuração de projeto, já que não vejo muito valor em ser habilitado / desabilitado por script. Ainda assim, essa é uma proposta separada a ser pensada em uma próxima etapa.

@vnen Em teoria, usando alguma nova palavra-chave para parâmetros / tipos de retorno / variáveis ​​como 'auto' ou 'let' e usando o mesmo algoritmo de crystal-lang, você poderia ser capaz de inferir o tipo em tempo de compilação .
É uma tipagem estática com inferência de tipo completa.
Digamos que você tem
auto my_field = 42 em um script, você pode simplesmente inferir que o tipo é int e restringir a variante subjacente apenas a ints.

No entanto, essa linguagem vai além de variáveis ​​simples. Ele pode restringir o tipo a um subconjunto de tipos dependendo dos ramos.
como no exemplo acima, o tipo de retorno só pode ser String OU Bool.
Mas para fazer isso você deve

  • Instancie funções para cada nova combinação de tipos de argumento e deduza os tipos internos para cada expressão de retorno, salve a lista de tipos de retorno possíveis como o tipo do valor de retorno.
  • Para ramos, expore todos os ramos possíveis onde uma variável é atribuída, para inferir todos os tipos possíveis da variável em um determinado momento. A qualquer momento, restrinja o tipo da variante àquelas

Além disso, você deve verificar as condições "instanceof" e como em

if my_var instanceof MyClass:
...

então você sabe que no corpo da condição if my_var só pode ser 'MyClass', então o tipo é ainda mais restrito.
Para que isso tenha alguma vantagem, você só deve ser capaz de chamar métodos ou acessar campos que tenham nomes compartilhados entre todos os tipos possíveis.
Então vamos dizer

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

Depois disso, x vem MyClass ou String. você só poderá chamar métodos comuns a ambos para evitar erros de tempo de execução.

Eu admito que isso é muito mais trabalho do que eu pensava originalmente.
Então, eu quero voltar nisso. Talvez ele possa ser implementado no futuro, ou talvez eu possa esperar que alguém escreva o binding para crystal-lang.
De qualquer forma, acho que é um recurso muito legal

@ m4nu3lf Eu entendo qual é a sua solicitação, o TypeScript é muito semelhante (que é uma linguagem com a qual estou mais familiarizado).

No entanto, existem alguns problemas a serem considerados para GDScript:

  • Primeiro, não é trivial implementar isso. Posso ver como outras línguas fazem isso acontecer, mas ainda assim levaria muito tempo. É por isso que quero dar um passo de cada vez e pensar sobre isso mais tarde. TypeScript foi feito pelo mesmo cara que fez Turbo Pascal e C #, então há muita experiência lá que eu não tenho.
  • GDScript é compilado um arquivo por vez. Tanto o Crystal quanto o TypeScript requerem que você compile todo o projeto antes de usá-lo. Isso significa que está um pouco nublado para verificar a interação com outros scripts e singletons.
  • A árvore da cena. Com get_node() (que é muito usado) é simplesmente impossível saber que tipo de nó você obterá. Você só sabe que é Node mas pode ser de qualquer subtipo. O mesmo se aplica a Recursos quando você chama load() , especialmente se estiver chamando com um nome dinâmico. _input(event) tem o mesmo problema, pois event pode ser qualquer subtipo de InputEvent.
if my_var instanceof MyClass:
    ...

Isso já foi inferido para o autocompletar de código. Pode ser reutilizado para verificação de tipo real. Este caso em particular é trivial, mas pode ser bastante complicado. Por exemplo, se você adicionar uma segunda condição: if some value == 42 and my_var is Sprite , o auto-completar de código não funcionará mais.


Também quero ouvir o feedback dos usuários, então ter algo disponível logo é mais importante (para mim) do que ter tudo perfeito depois de um longo tempo de espera.

Resumindo: eu quero melhorar a inferência de tipos, mas isso tem que ser feito passo a passo, com pequenas melhorias. Uma vez que a digitação opcional está disponível, o resto é difícil.

Por que isso está fechado?

Interpretou um pouco mal, meu mal.

Voltar para o prefixo e o postfix: UML e Kotlin usam ": type".
O Basic também fez "DIM a as INT" (e usou "as" em vez de ":" ou "é")

@programaths Já se passou muito tempo desde então, e isso tem sido discutido

Devo fechar isso?

@ LeonardMeagher2 será fechado automaticamente quando meu PR for mesclado.

@ bojidar-bg Eu só queria adicionar alguns lugares onde o postfix é usado porque é interessante por si só. A UML é destinada a analistas e também a pessoas não técnicas. Kotlin é uma linguagem que foi desenvolvida para ser mais confortável e mais moderna do que Java ao implementar "java eficaz".

Então, se você precisar escrever porque o postfix foi escolhido, eles também podem ser incluídos. Às vezes, as pessoas desejam ter motivos e dispensam facilmente motivos técnicos, a menos que você os relacione ao usuário.
(por exemplo, "Parser pode resolver ambigüidades mais cedo e tem uma velocidade de 100x, o que significa que o mesmo programa pode ser compilado mais rápido e permite mais testes)

É sempre bom ter material, não é? ;-)

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

blurymind picture blurymind  ·  3Comentários

mefihl picture mefihl  ·  3Comentários

SleepProgger picture SleepProgger  ·  3Comentários

testman42 picture testman42  ·  3Comentários

rgrams picture rgrams  ·  3Comentários