A recent exchange on the JavaFX Forums (see here and subsequent messages) has prompted me to write a note about function return types and type inference in JavaFX Script.
JavaFX Script is a strongly-typed language. You don’t have to declare types everywhere; usually the compiler can infer the right type based on usage. For example, in the following:
var msg = "Hello";
The msg variable is of type String. Occasionally the compiler won’t be able to infer a type, in which case it will issue an error message, and you’ll have to declare the type explicitly.
Now, what’s the return type of a function? If you don’t declare the return type in the function declaration, the function’s return type will be the type of the last expression in the function body. (Also, the return value will be the value of that expression.) This is often just fine. For example,
function getMessage(count:Integer) {
def verb = if (count == 1) "is" else "are";
def suffix = if (count == 1) "" else "s";
"There {verb} {count} item{suffix}."
}
The return type is clearly String. You can use the return statement to return a value from a function, but you don’t have to.
Unfortunately this isn’t documented very well at all in the JavaFX Script Language Reference. The section on function return type inference is extremely short, and I’ve quoted it below in its entirety.
Type inferencing can generally determine the return type of a function. In most cases the parameter types can be determined by type inference as well.
However, functions which, by virtue of their access modifiers, are visible outside the script should be explicitly typed.
[To do: detail on type inference]
The advice about explicitly declaring the return type is a good one, but it doesn’t explain why. I’d prefer a stronger rule and say, always declare the return types of functions.
Functions are used for different purposes. Sometimes they’re used to compute a value and return it to the caller. Other times they contain sequences of statements that mutate state, but that do not return any value. Some languages, such as Pascal, distinguish between these cases by calling them different things: a function must return a value, whereas a procedure cannot return a value. In Java, all methods are declared the same way, but the return type must always be declared. A Java “procedure” returns no value and has a return type of void.
If you want to write a “procedure” in JavaFX Script, you can just declare its return type to be void. But why bother, since the compiler will infer the type for you? The last expression in the procedure might have some value. The compiler will return that value, and use its type, even if you didn’t want to return a value. Consider a banking application. You might want to allow someone to have deposit-only access to an account, but not allow them to make withdrawals or to check the balance. You might code up the interface like this:
function makeDeposit(amount:Number) {
balance += amount;
}
Whoops! The compiler inferred a return type of Number, and you just leaked the account balance. The obvious fix is:
function makeDeposit(amount:Number):Void { ... }
Function return type inference can have some surprising effects, especially in the presence of subclassing and overriding. Consider the following example:
class A {
var count:Integer;
var status:String;
function update() {
count++;
status = "update() was called {count} times";
println(status);
}
function doSomething() {
// other stuff
update()
}
}
class B extends A {
var subCount:Integer;
override function doSomething() {
// more stuff
super.doSomething();
subCount++;
}
}
This works fine. Oh, you left a debugging println in A.update()… just take it out. The compiler now gives you an error, complaining about incompatible types at the end of B.doSomething(). Whut??
The return type of B.doSomething() should be Integer, since the last expression in the body is an Integer. Since it’s an override, it has to match the return type of A.doSomething(). What’s that? Well, it’s inferred from the return type of A.update(). Now, in the original code, the last expression in A.update() is a println(), which is one of the few things in JavaFX Script that has a return type of void. So that’s the return type of A.update(). When you removed the println, the last expression became a String and thus the return type of A.update(). That also affected the inferred return type of A.doSomething(), which also became String, which caused the mismatch with B.doSomething().
The fix here is to declare the return types for A.update() and A.doSomething() explicitly to be Void. It might also be reasonable to declare the return type for B.doSomething(). In this case it’s less important, though. The return type of an overriding function is determined by the type of the function being overridden, so the type won’t be inferred from the last expression in the body. Even though the last expression in B.doSomething() is an Integer, its return type must always match that of A.doSomething(). It’s good documentation to put in the return type, so you don’t have to go hunting through other source files to find out what it is. The compiler will flag an error if there’s a mismatch.
Like I said above, always declare the return types of functions.
Great blog post Stuart!
Did I ever mention that in my opinion dynamic typing is a really really bad thing? Everyone is so happy with it, but if one thing is of the utmost importance in computer code, it’s predictability. And predictability means that you have to be exact and verifiable. And this blog is the perfect demonstration of why.
But “exact and verifiable” also means strong typing all the way; you need to specify what type your algorithm is expecting to work with. Even in local variables. If something suddenly changes from int to float, you need to modify -for example- the calculating, because suddenly something as “precision of a number” needs to be taking into account by the algorithm when doing compares.
Thanks! I don’t have enough experience with dynamically typed languages to say they’re a “really really bad thing” but I can see advantages and disadvantages to them. There are also definitely places where strong typing gets in the way. See javafx.scene.layout.LayoutInfo for instance. (Hm, this probably deserves a blog post of its own.)
In a dynamically typed environment the type of a variable can change during runtime. In the second example I gave in the blog post, the type of something only changed during program maintenance or modification… and the compiler caught it. But there are cases (like the first example) where the compiler infers something unexpected for you and creates a bug. Bottom line is that being explicit here is a good idea.
It might be that type inference for function return values is a misfeature of the language. I can see someone making a case that what it adds (mainly convenience?) is outweighed by the potential problems that can arise.
I understand the advantages (or should I say lazyness
of dynamical languages. PHP has such a great examples; “Here, an object. And no, I’m not going to tell you what you just got. Figure it out yourself. Yeah, I would try the documentation. Oh, wait, I’m not even sure what properties it has until execution time.”
The question for LayoutInfo is… Shouldn’t there be a specific implementation for it for each layout? It’s like Swing’s layout constraints; MigLayout allows for specific classes; LC, CC, etc. A much better approach than just a string. Maybe slightly less convenient, but compiler checkable! How about a container that specifies its layout using generics; “new JPanel()”?
Last week I had a great “strong typed” moment; I reverse engineer a database scheme into a business model (JPA) and I had to make a big change to the database (insert a new layer somewhere). After the reverse engineering I had hundreds of errors, but after solving them, all my code ran again. (I also reverse engineer field names into final strings, so even errors in queries are detected – until the improved query language in JPA2.)
So I believe dynamic typing is more lazyness instead of good engineering. But each his own.
The panel example lost the pointy brackets.
new JPanel[MigLayout]();
[...] Marks has posted a very useful blog post about function return type inference in JavaFX. The basic premise: always declare the return type of all functions, and indeed the type of all [...]