-
Yorick Peterse authored
The Any trait is a trait implemented by all objects automatically, and replaces all use of the Dynamic type. This means that Inko is now statically typed, instead of being gradually typed. The Any type is introduced as it can be cast to any other type. Using "Object" to represent something that could be anything does not work, as objects can't be casted to other objects. Any being implemented by all objects does allow one to cast it to an object. Argument types must now be known, meaning you either have to specify a default value, and explicit type, or both. In other words, this is no longer valid: def foo(number) { } For closures and lambdas the compiler still tries to infer the argument types based on how these blocks are used. The behaviour of omitting method return types also changed. Prior to this commit, leaving out a return type would result in it being inferred as Dynamic. Starting with this commit, omitting a method's return type results in: 1. The compiler inferring the return type as Nil 2. The compiler inserting a `return` at the end of the method, unless a `return` is already present Take for example this method: def init { @Number = 10 } Prior to this commit, the return type of `init` would be `Dynamic` and the value returned would be `10` (an `Integer`). In this commit this is changed so that `init` is inferred to return `Nil`, and the actual return value is also `Nil` (not `10`). This matches how dynamic return types were used in the standard library so far: to signal we don't really care about the return value. For closures and lambdas, the compiler still tries to infer the return type based on the body; defaulting to Nil if no other type could be determined. == Reasons for moving to static typing Inko was gradually typed for a few reasons. First, when I started working on Inko I was leaning more towards making it a dynamic language similar to Ruby or Python. As I spent more time working on it, I realised I was starting to prefer a statically typed language more and more. I also felt that gradual typing would strike a balance between the rapid prototyping capabilities of a dynamic language, and the safety of a statically typed language. But in practise I found this not to be the case. For example, in dynamic languages (e.g. Ruby) I spend a lot of time jumping between running the code, and fixing silly errors such as the use of undefined local variables; something a statically typed language could easily detect. Inko using statically typed code as a baseline (instead of dynamically typed code) also made using dynamic types frustrating. Take this code for example: def add(a, b) { a + b } let result = add(10, 20) Here `a` and `b` are dynamically typed, so is the return type. Now imagine we want to print the results: import std::stdio::stdout def add(a, b) { a + b } let result = add(10, 20) stdout.print(result) This will fail to compile, as `stdout.print` expects a `ToString`, but `result` is a `Dynamic` and the compiler does not know if the runtime type implements `ToString`. This inevitably leads to a lot of cast expressions like so: import std::stdio::stdout def add(a, b) { a + b } let result = add(10, 20) stdout.print(result as Integer) If we're going to require developers to cast dynamic types almost every time they use them, they won't be all that useful. And if they're not useful, we should just get rid of them. This doesn't mean gradual typing is a bad idea. In fact, I think it's a great way of making a dynamically typed language more safe. TypeScript is a good example: it takes JavaScript (dynamically typed), and adds static typing on top. But supporting gradual typing in a statically typed language just doesn't bring any benefits, and makes certain optimisations more difficult or even impossible. And thus dynamic typing is no more. Fixes #194