159 karma · joined December 20, 2011
As a further sort of clarification: we're Java developers, not JavaScript guys. We recognize the importance of the JavaScript VM. But Java is where our hearts are at ;-)
We're not crazy about this; of course compromises are necessary; hell, we just added use-site variance to the language purely to make Java interop better.
So to clarify: ceylon.language and ceylon.collection are designed the way they are because they're like way better and cleaner than java.lang and java.util. Not because they represent a "compromise" with JavaScript.
Likewise, the main reason that Ceylon only has 3 numeric types (Integer,Float, and Byte) is because that seems to me the Right Thing from the point of view of the goals of the Ceylon type system. It just so happens that this is also the right thing if you want cross-platform execution.
Hope that answers the question.
Well it's certainly _a_ top concern, and one we've invested a huge amount of development effort in! Indeed, we could have delivered Ceylon literally a year or more earlier if we had not decided to jump through so many hoops to make the Ceylon compiler generate generic type info that the Java compiler understands.
>> This might be (and I don't know enough about it, so this is conjecture) one of the reasons why writing Kotlin on Android is pretty simple, while Ceylon not so much. <<
Well, no, I don't think so. It's not that _Ceylon_ doesn't have great Java interop; it's that _Android_ is not quite fully Java compatible! I can't just say to you: "oh, it's Java, so it will work". No; we can't depend on that.
But really the only reason that we don't have a welldefined story for running Ceylon on Android is simply that we haven't had time (yet) to invest the effort into trying it out and finding any sources of discomfort and fixing them and making it work, whereas perhaps the Kotlin team has done that already.
But we _will_ work on this, and I'm certain that we'll be able to make it work.
>> Kotlin also compiles to JavaScript, but it's much more a "hosted" language than one that tries to abstract away the host's native APIs. <<
Well the problem is that if you don't abstract away from the VM with your language module, and instead try to treat java.lang+java.util+whatever as providing your basic language types, or as dependencies of the module that provides your basic language types, then you simply _don't have a well-defined foundation for cross-platform development_.
* First, because the Java SDK itself is not modular, and the "boundaries" of what bit of the SDK is Java's "language module" is ill-defined. This is a real concrete problem for people who develop on GWT, so it's not some theoretical concern that I'm inventing! * Second, because even if you _could_ clearly discern the boundaries of java.base, there are plenty bits of it which _simply can't be implemented in JavaScript_. Seriously, there are a bunch of operations that I would love to add to ceylon.language, but can't, because they are simply unimplementable in JS. * Third, because _the basic types of the Java language_ are only meaningful on the JVM! You have types like long, and double whose semantics are 64-bit precision, and int and float whose semantics are 32-bit precision. But on a JavaScript VM, all numbers have 53 bits of precision! So the basic semantics of the type simply can't be satisfied. On the Dart VM, the situation is a lot better, since at least the two numeric types they have are both 64 bit, but that still leaves int, short, float as basically completely meaningless types.
So if you're truly serious about JavaScript VMs or the Dart VM or whatever as a real target platform for your language, you need to design your language and language module for that.
>> adopting Ceylon today, I think, is a much bigger risk than adopting Kotlin. <<
I'm not arguing, nor I would I ever argue, that there are no sources of discomfort that arise from having a cross-platform language module. There surely are, but they're minor, they're well-defined, and they're essentially very easy to work with. We've already written quite a lot of code that makes heavy use of Java libraries, and we've been identifying sources of discomfort and fixing them when/where necessary as we go along.
Nor would I suggest that there were no bugs/limitations in Ceylon 1.0. There were! And we fixed the ones we found. And surely we'll find a couple more in Ceylon 1.1. But we remain deeply committed to fixing such bugs.
Given that, calling it a "risk" is, I think, a bit of an unfair characterization. A "source of occasional discomfort", would be fairer.
Having the compiler produce an actual error in this case seems wrong and excessive, since the compiler does _not_ know better than you whether javaMethod() can return null. It simply doesn't have that information when calling Java code.
String str = javaObject.javaMethod();
or: String? str = javaObject.javaMethod();
In the first case, we insert a runtime type check that throws an `AssertionError` if `javaMethod()` returns `null`. In the second case we don't.That is to say, the Ceylon compiler recognizes that it's dealing with a Java library where information about null is simply missing from the method signature and adjusts accordingly.
Indeed, the Ceylon compiler's Java backend is actually built on Open JDK - that is, it is essentially `javac` itself that generates the bytecode - so we know that the bytecode we generate is extremely close to what Java itself would generate, including full information about generic types that the Java compiler can understand.
So, sure, we have the language module, and `ceylon.collection`, `ceylon.locale`, etc, which abstract away from the JVM, but that's necessary, since Ceylon also runs on JavaScript VMs. That _doesn't_ mean you can't easily use `java.lang`, `java.util`, and the rest of the Java SDK if you need it.
So it's not quite right to think that Ceylon is lacking in this area. It's actually a strong point of the language.
The reason for that is that a local variable could be captured by an anonymous function or nested function, and escape from the local scope, so it's exactly the same situation with a local variable as for an instance variable of a class: both kinds of variable break referential transparency.
You can't declare mutually-recursive methods before
initialization logic in the body of a class. You have to
declare them _after_ the fields of the class are fully
initialized.
That's not so hard to understand.To see why it's like this, you need to consider :
- Unlike most OO languages Ceylon statically _guarantees_ that a field of an object is initialized before it is accessed. (If we didn't, then we would have to have something conceptually equivalent to a NullPointerException when you access a field before initialization.) - We've always hated the amazingly repeaty syntax of constructors in C++/C#/Java.
What I mean is, in Java I have to write stuff like:
class Point {
final float x;
final float y;
Point(float x, float y) {
this.x = x;
this.y = y;
}
}
In Ceylon, it would be: class Point(Float x, Float y) {}
And even with all that verbosity in the Java version, I _still_ don't get a static guarantee that `x` and `y` are initialized before they are used by a method. This code prints `0.0`: class Point {
final float x;
final float y;
Point(float x, float y) {
fun();
this.x = x;
this.y = y;
}
void fun() { System.out.println(x); }
public static final void main(String[] args) {
new Point(1.0f,2.0f);
}
}
So Ceylon wants to solve _both_ these problems: eliminate the verbosity, and provide the static checking for initialization. Now, this is an _almost_ completely transparent process. I almost never find myself thinking about declaration section vs initialization section. I just write classes so that fields are initialized before the methods that use them are declared. Which is basically super-intuitive. In >95% of classes that's all you need to know. Very rarely, I mix this up, and the compiler complains and I need to move a member up or down. Which takes seconds to fix. Very, very rarely, I have a class with some kind of self-recursive initialization logic and then only in that case do I need to start holding the mental model of declaration-vs-initialization sections in my head. In practice, I think I've only run into this situation _once_, and it took me a few minutes to restructure the code. (FTR, it was `ArrayList` in `ceylon.collection`.)Sure, the language spec _mentions_ Null, in order to define the syntax sugar X? and exists x. But that doesn't mean that Null has any special place _in the type system_.
> My main programming activities are using OCaml and Haskell, both of which have very few ad-hoc language features, if any.
OK, great, sorry for making assumptions. In my defense, I just didn't quite understand why you were having such a hard time believing that Ceylon could do things like this without adhoc special cases.
No, not at all, I'm talking about the ability to abstract over things. Can I write a single higher-order function that operates over functions with any -arity? In most languages, no. In Ceylon, yes.
> Again, this discussion was very nice, resulted in learning a few new and interesting things and I'm taking nothing but good impressions with me!
In general, the quality of discussion here is better than in other places.
> I think if both communities took more care to explain the different reasoning, design decisions and philosophies behind the languages when giving talks or on conferences in the future
Sure, but when you have nasty and idiotic twitter stuff poisoning the air, it's hard to expect people to adopt a fair-minded and technical approach to the topic. People should just get off twitter. It's a community-wrecker. There's almost nothing you can say on twitter that rises above the level of insulting.
Sure there is. The second type parameter of Iterable encodes that, see the definition of Iterable.
> does "Nothing" satisfy "Null"?
Of course. Nothing is the bottom type, it satisfies every type.
> If so then I would imagine that it becomes harder to enforce the constraint, likely because the return type can still be Null regardless of input restrictions.
I don't know what you mean by this. An Iterable<X,Nothing> cannot return Null from its first attribute. Check the declaration of Iterable.first.
> Does this use extra type inference on the expressions inside the function to do the enforcement, for instance using the "exists" operator to perform type set subtraction between {Value|Null} and {Null} on the true branch.
Approximately correct. But "exists" is not type subtraction, it's intersection with the class Object. Cool, huh?
> This implies that if I re-write the max function to have a single exit point (`ret = Null; if (exists values.first) { ...; ret = ...; } return ret;`) then I don't get the same guarantees?
I don't follow. You can't rewrite the function to do anything unsound because the compiler won't let you. Our type system is sound.
> Also the fact that Null is referenced by the "exists" operator also implies that it is part of the language specification, and not actually definable in any meaningful way as a user type.
That's not correct. Go and check the definition of Null. It's just a regular class, as advertised. There is no special behavior defined in the language spec. Sure, the exists operator is defined by the language spec to mean "is Object", but that's just trivial syntax sugar.
I guess the problem here is that you're used to languages full of weird special cases and ad hoc behavior. Ceylon is not like that. Ceylon's type system is very simple and these fancy constructs are layered over it as pure syntax sugar. It's a very different approach to other languages. But in the end it's much more satisfying. You'll just need a little mental adjustment to get used to it.
> If I read correctly, that means that I cannot implement the same behaviour/enforcement with completely user defined types.
Wha?! Nothing is the bottom type! The empty set. You want to define your own bottom types? That's not possible (nor desirable) in any language I know of. There's just no reason why you would want to write a class with no instances.
Surely. Not merely comforting, but useful.
But it entails deep compromises in the design of the type system that we're just not willing to make, especially since Ceylon is primarily a cross-platform language, not just a JVM language.
> OTOH I have concrete cases where I need existential types for compatibility, much as I wish I didn't
Right. Whether we'll eventually have to throw in the towel on this one and add use-site variance to Ceylon, purely to get better interop with Java, is a very interesting and bothersome topic.
I'm saying that it's cool as hell that the following is all well-typed:
{Float*} zeroOrMore = ... ;
{Float+} oneOrMore = ... ;
Float? maybeNull = max(zeroOrMore);
Float definitelyNotNull = max(oneOrMore);
Null definitelyNull = max({});
And that this behavior makes the slightly cryptic signature of max() worthwhile.Sure, sure, this is not the most general possible signature for max(), since it is only for types with a natural order. We could and should add some form of max() for comparator functions, which we already have for sorting.
P.S. By the way, there are examples of functions where type classes are nice, for example sum(), where the type class lets you correctly express the sum of zero elements. (But max() is not one of these functions.) So in fact, we would definitely like to experiment with type classes in a future version of Ceylon, especially since the idea probably meshes very nicely with our reified generics.
> What am I missing?
You're missing that the definition of compose() is abstracted over the arity of the second argument function in Ceylon. It's _one_ function, not a different method defined for each Fi type. Which means that I can write similarly-abstracted functions that use compose().
> Is there also stuff for filtering elements by type, mapping, grouping?
Sure. Tuple is a subtype of List, which is a subtype of Iterable, which defines all stream-processing functions. FTR, an [Integer,String,Float] is a List<Integer|String|Float>.
Maybe. I have never heard of HList until today.
> Do you avoid instantiating a JVM object for each entry?
Yes, a tuple, at the VM level is just a wrapped array. Our compiler backend does magic with certain language module types.
> Are you sacrificing Java compatibility, or is the tradeoff somewhere else?
Well, I dunno, a tuple looks like a regular Ceylon list to a Java client, but I don't think that really breaks _compatibility_ as such. Depends how you define "compatible".
> Having now paid attention to who you are, how do you handle ORM in what I presume is a more immutability-oriented language?
No clue. That's something we'll have to think about in 2014.
> hibernate seems to expect sessions to belong to a single thread, objects to belong to a single session, and updates to be performed by mutating an object rather than copying it.
Yes. That's definitely what Hibernate expects. It was never designed for copy-on-write. Because at the end of the day the database contains a bunch of mutable state and Hibernate reflects that in memory. I can't imagine that the solution to this problem is going to be straightforward.
> Anyway, to get back to the point, does Ceylon have a good story for this problem?
Not yet.
> Is it going to include a next-gen hibernate replacement?
I hope so, but I can't promise because I have not spent time thinking about it.
I certainly hope so, since we always planned to replace JBoss Modules with Jigsaw when that becomes possible. But we still don't know if the Jigsaw module system will be made available for all consumers or just for the JDK itself.
> If your type was written by someone who didn't care to implement Comparable, bad luck! - If the type has a Comparable implementation which differs from what you need, bad luck, too! - You need the type to be comparable in multiple ways? Again, bad luck!
Um, you've totally missed the point. Go and have another look.
And sure, since you bring it up, we can certainly add a version of max() that accepts a comparator function. It would be a method of Iterable, probably.
And sorry, but I can't imagine why I would want or need typeclasses to solve that problem. Sure, I could use typeclasses, if I were a solution in search of a problem ;)
> I'm not sure that there is a large difference.
You can't abstract over tuple and function -arity in Scala. So yes, there's a big difference. Look at the definition of compose() in Ceylon.
> and performance (no pointer chasing, all values are just a method call away).
At the VM level, tuples are represented as arrays. So there is no real performance penalty.
> Anyway, if one wanted tuples-as-linked-lists, one could just use Shapeless' HList library, which has a few benefits over Ceylon's tuples, too, as far as I see:
In Ceylon, without any macro library, you can write:
value tuple = [1, "str", 42.31];
String str = tuple[1]; //totally possible in Ceylon
Seriously dude, _just try it out_, you're desperately searching for reasons to not like this thing! But it really has cool stuff to offer you. Give it a fair chance!Sure, _of course_ you're going to find stuff in Scala that you can't do in Ceylon. Scala is one of the most huge and complex and feature-rich languages _ever_! But you'll also find plenty of stuff you can do in Ceylon that you can't do in Scala, if you care to look.
Brrrrr.
> What is the problem I'm supposed to be having that OSGi would be solving? Because I've never seen it.
The problem OSGi solves is having two versions of the same module as part of the same assembly. This is quite possible if you're using third-party libraries that in turn depend on another third-party library.
But I think you're missing something here. Ceylon's module system supplants both OSGi _and_ Maven. It's not just an alternative to OSGi, it's a whole architecture for modularity.
> I tried to do that, but it looks like you don't have an equivalent to javadoc/scaladoc with the standard library docs publicly available?
https://modules.ceylon-lang.org/modules/ceylon.language/1.0....
> I don't think you can do that without higher kinded types.
Correct, we don't have type constructor polymorphism in the official language, and even if we did, we wouldn't use it for this particular problem. Our equivalent APIs are based on (lazy, non-copying) stream-processing.
Now, I do have a working implementation of type constructor polymorphism in a branch of the Ceylon typechecker, but I probably won't merge it because:
1. we don't have a really convincing use for it, and
2. I see it as essentially useless without type constructor argument inference, and Ross and I doubt that a robust and decidable inference algorithm exists for a language with subtyping.
> Point, but I find it hard to care.
Speaking for myself, I don't like unnecessary distracting complexity, even trivial unnecessary distracting complexity.
> Shapeless 2 makes it painless to treat tuples as HLists (using implicit macros - a concept which admittedly would have utterly horrified me six months ago) which means you can work around it in practice
Brrrrr. And then you Scala folks tell the rest of us that Scala is not complex and that we're full of FUD or worse ;-)
FTR, the way Ceylon models this is the way this is the same way it is modeled in mathematics. That is to say, in mathematics, a function is a set of ordered pairs, where the first element of each ordered pair is a tuple. The notion of a tuple is itself defined by recursion. Therefore, in Ceylon, a function type is Callable<AType, ATupleType>, and Tuple is is a class with a recursive definition. Clean, intuitive, abstractable.
Of course. That's the whole point. I really think you've missed the nature of Ceylon. Don't imagine, that just because it has a syntax that looks friendly and harmless and familiar, that it doesn't have a really killer type system under the hood.
> i.e. is the following a runtime, or compile time error?
A compile-time error, of course.