Thinking from the POV of starting out in an existing codebase that uses these features, I’m not sure I agree with all your points.
Extension methods: I absolutely agree that it’s an inconvenience to always have to add libraries like Apache Commons to projects, and having to divert to those for a large part of the use cases. What seems to me like the disadvantage of extension methods though, is that it would be less explicitly clear what is actually happening and more difficult to port to other codebases or even parts of the same codebase.
If now, I remember a call to StringUtils.leftPad, I can check which classes named StringUtils are on the classPath, or if that fails google which classes called StringUtils offer leftpad functionality and go from there. In the extension methods scenario, I wouldn’t really explicitly know where the leftPad method came from (if I only remembered that line), and especially junior developers might think it’s just part of the standard library, not realizing extension methods were being used. Maybe this can be worked around with good IDE support, but I’m not sure.
Strict null-ness: How does this work with implicit nulls? For example: I have an object called Person, which I retrieve from database (for example via Hibernate). Person has firstName, lastName, and age. For one person I might retrieve, the age could be null. What if I then pass in person.age to some method which accepts an Integer argument? I would assume that Ceylon can’t guard against this case at compile-time?
Additionally, there’s also the difficulty of backwards compatability. Given this syntax, this seems to be the one feature on your list that breaks that.
Compiler-checked reflection: That is a very nice feature!
All in all, nice overview, hadn’t heard of Ceylon or Xtend yet.
Thanks for the response! I agree that some of the features I described in this blog post (in particular strict null-ness) are only intended for new projects. Then again, this is a long-term wish list, we all know that Java isn't exactly the fastest-moving language out there when it comes to the introduction of new features.
Extension methods: I can only talk about the Xtend-case here, I've yet to see this feature in another language working in this fashion. In Xtend, you still have to import the utils classes you want to use; that alone limits the room for misunderstandings. In the Xtend IDE, the call to an extension method is highlighted slightly differently than a regular method call, so it's always clear what you are calling. If in doubt, a quick "jump to declaration" will give a definitive answer to what is being called here. Here's another example why extension methods make life so much easier:
// Java 8 (ok, but super verbose)List<String>lowercased=myStrings.stream().map(String::toLowerCase).collect(Collectors.toList());// Java 8 with static utilsList<String>lowercased=CollectionUtils.map(myStrings,String::toLowerCase)// with Xtend-style extension methods:List<String>lowercased=myStrings.map(String::toLowerCase);
Strict null-ness: The way this works in Ceylon under the hood is by using so-called Union Types. null is not assignable to any class in Ceylon, other than Null (i.e. it has it's own class). When you define a parameter as User?, it is a shorthand for User | Null, i.e. "this paramter has to be a User or null". A method call on a variable of a union type is only valid if all type members of the union support the method. Any method call, e.g. getUsername(), is invalid on a parameter variable of type User?, because Null defines no methods. So how do you transform User? into User? Flow typing has you covered:
User?user=userService.loadUserById(1234);// user.getUsername() is illegal here because of union type with NULLif(user!=null){// flow typing narrows union type down from (User | Null) to just User...user.getUsername();// ... so this is ok here}
Similar concepts were introduced e.g. in TypeScript. And yes, this check can break existing code bases. It's an opt-in compiler feature and migrating an existing code base is not always easy. However, I've seen an interesting behaviour in programmers. Let's say method a(obj) calls b(obj), and b(obj) in turn calls c(obj). c cannot deal with null parameters. Therefore, b will get a compiler error if it tries to pass it's nullable parameter into c. As a consequence, programmers tend to forbid the parameter of b to be nullable. Then a has an error, and so on. The treatment of nulls is shifting automatically and intuitively to where it belongs: towards the I/O handling at the system boundary.
For further actions, you may consider blocking this person and/or reporting abuse
We're a place where coders share, stay up-to-date and grow their careers.
Thinking from the POV of starting out in an existing codebase that uses these features, I’m not sure I agree with all your points.
Extension methods: I absolutely agree that it’s an inconvenience to always have to add libraries like Apache Commons to projects, and having to divert to those for a large part of the use cases. What seems to me like the disadvantage of extension methods though, is that it would be less explicitly clear what is actually happening and more difficult to port to other codebases or even parts of the same codebase.
If now, I remember a call to StringUtils.leftPad, I can check which classes named StringUtils are on the classPath, or if that fails google which classes called StringUtils offer leftpad functionality and go from there. In the extension methods scenario, I wouldn’t really explicitly know where the leftPad method came from (if I only remembered that line), and especially junior developers might think it’s just part of the standard library, not realizing extension methods were being used. Maybe this can be worked around with good IDE support, but I’m not sure.
Strict null-ness: How does this work with implicit nulls? For example: I have an object called Person, which I retrieve from database (for example via Hibernate). Person has firstName, lastName, and age. For one person I might retrieve, the age could be null. What if I then pass in person.age to some method which accepts an Integer argument? I would assume that Ceylon can’t guard against this case at compile-time?
Additionally, there’s also the difficulty of backwards compatability. Given this syntax, this seems to be the one feature on your list that breaks that.
Compiler-checked reflection: That is a very nice feature!
All in all, nice overview, hadn’t heard of Ceylon or Xtend yet.
Thanks for the response! I agree that some of the features I described in this blog post (in particular strict null-ness) are only intended for new projects. Then again, this is a long-term wish list, we all know that Java isn't exactly the fastest-moving language out there when it comes to the introduction of new features.
Extension methods: I can only talk about the Xtend-case here, I've yet to see this feature in another language working in this fashion. In Xtend, you still have to import the utils classes you want to use; that alone limits the room for misunderstandings. In the Xtend IDE, the call to an extension method is highlighted slightly differently than a regular method call, so it's always clear what you are calling. If in doubt, a quick "jump to declaration" will give a definitive answer to what is being called here. Here's another example why extension methods make life so much easier:
Strict null-ness: The way this works in Ceylon under the hood is by using so-called Union Types.
null
is not assignable to any class in Ceylon, other thanNull
(i.e. it has it's own class). When you define a parameter asUser?
, it is a shorthand forUser | Null
, i.e. "this paramter has to be aUser
ornull
". A method call on a variable of a union type is only valid if all type members of the union support the method. Any method call, e.g.getUsername()
, is invalid on a parameter variable of typeUser?
, becauseNull
defines no methods. So how do you transformUser?
intoUser
? Flow typing has you covered:Similar concepts were introduced e.g. in TypeScript. And yes, this check can break existing code bases. It's an opt-in compiler feature and migrating an existing code base is not always easy. However, I've seen an interesting behaviour in programmers. Let's say method
a(obj)
callsb(obj)
, andb(obj)
in turn callsc(obj)
.c
cannot deal withnull
parameters. Therefore,b
will get a compiler error if it tries to pass it's nullable parameter intoc
. As a consequence, programmers tend to forbid the parameter ofb
to be nullable. Thena
has an error, and so on. The treatment ofnull
s is shifting automatically and intuitively to where it belongs: towards the I/O handling at the system boundary.