Is the actual logic different in Java vs Kotlin examples?
In Java: when `nullableVariable` is not null and someMethodCall() return `false` then we return what `fallbackIfNullMethodCall()` returns right?
and in Kotlin in that case we just return `false` from `someMethodCall()` because this is not null and `fallbackIfNullMethodCall()` is not even evaluated?
I leave it to the reader to decide whether the advantages are clear or not.
For instance, in Kotlin you can perform operations on collection without converting them to a stream first, and collecting them with a certain collector afterwards. So in order to perform a single filtering operation on a collection, you need to do three steps in Java and just one in Kotlin.
Another advantage is coroutines, which not only allow you to write asynchronous code in a common, synchronous manner, but also get rid of shared mutable state, since coroutines can send/receive shared state through channels.
It is, however, even in the scope of the "Hello world" app, Kotlin performs much better than Java in terms of bringing out business logic to the front.
> You are right, but you still have to catch the checked
> IOException.
Rather than which alternative? There are tonnes of IO errors that can occur and are captured in Java that you may not ever even consider. IO is exceptionally tough and programmers should be aware that there could be 1 of a possible million reasons for failure, even if they don't care exactly why.
It's not a massive ask either:
try{
/* Perform some IO that 99.999999% of the time */
}catch(Exception e){
/* Something went wrong and we don't know the state of the disk */
}
Personally I believe applications should have their own wrapper around IO and handle it accordingly. I.e. not caring, re-trying, show-stopper, etc, etc.
A lot of code I've written will have the following if I really don't care if it works or not:
try{
/* IO code */
}catch(Exception e){
/* Do nothing */ // <-- Let others know that this was on purpose
}
The problems are that (a) you need to add an extra type parameter for exceptions all over the place, (b) exception unification (sum type) doesn't work generically - you can say E1 | E2 in a catch block but it's not a real type, and (c) it composes poorly with existing libraries that expect Consumer<T> throughout.
For one level deep callbacks (for resource handling and the like) it works reasonably well though.
For a lot of scripts you want the program to crash with a stack trace if you get an IOException, which is the default behavior if it's uncaught. The user is the developer, and if there's an error the developer fixes it by munging something on their filesystem.
Java was designed for "production" software, which in the late 90s and early 00s meant software that ran for long periods of time as a server, servicing thousands of mission-critical customers. Crashing was not acceptable behavior for that. But now a lot of production software often requires a lot of ancillary one-off tasks that are just done by the developers - testing, data-munging, exploratory code, migrations, demos, etc. That's a very different environment from where you code to spec, the spec never changes, and once the software is done it's supposed to run for years without crashing.
Early Java was designed for set-top boxes and then web browsers. Large-scale servers came later; in fact a lot of libraries still don't support the async futures that were officially added five years ago.
I actually like it. I also create my own exceptions for rare (exceptional) cases, just not to forget to handle them.
But some Java SDK exceptions drive me crazy. For example `URL.parse("http://example.com")` over hard-coded strings that I know 100% won't throw exceptions, but I still to catch 3 of them.
After URI appeared in 1.4, the only reason to use URL was to create a URLConnection from a URI. Since openConnection() throws IOException, it's not a big deal that toURL() throws a MalformedURLException - just catch it along with all the other IOExceptions.
Since 11, there's no reason to use URL at all, because you can use HttpClient to actually do HTTP.
Ok I'm a veteran Java developer that understands exactly what you're saying but try explaining that to people unfamiliar with Java or junior engineers and watch their brains glaze over.
The JDK is filled with a ton of dumb "once upon a time we thought this was okay..." things that don't properly encode the correct modern idiom
For a long time I've wanted a sort of "local deprecation" tool where we could have a list of things in the JDK that shouldn't be used, and any direct reference to them would cause our build to fail.
Yes, it absolutely is! But then so are all other languages of its vintage. Part of learning a programming language is learning that.
Newer languages definitely have an advantage here, in that they haven't been around long enough for people to have figured out which bits of them are dumb.
It's worse. In 1.4 they added URI because URL is very flawed. You should never use URL.
The equals method actually does a DNS resolve and compares the ip addresses. This means that comparing two URL's on the same server will always return true when compared. A lot of URL's you compare will give you true as there are a lot of sites run on the same machine with the same ip address.
Had this happen in production in a third party library we used many moons ago. Sysop came and asked us why we did six figure DNS lookups over a short period of time (24h? Less? Don't remember).
Would probably have gone unoticed at most AWS/GCP/Azure shops today.
Worse was when I had somebody adding the ip address as an Inet4Address on every message passed between machines in a production environment that explicitly didn't have DNS (banks have occasionally very odd ideas about securing subnets). Every single message was doing a reverse DNS lookup and then timing out. And there were a _lot_ of messages.
Wait, seriously? Did whoever designed that have any idea what URLs actually were? Even without DNS, comparing the hosts is no way to compare URLs.
I can't think of a single example of two 'equal' URLs whose strings don't match (up until the parameters, at least)...
Yes - bear in mind the URL class has been around since 1995, when the landscape was somewhat different!
At that point, URLs resolving to the same IP were considered equal, even if the host names were different. Even now, there’s no real difference between say “http://example.xn--com-9o0a and “http://example.com:80”; it would have been reasonable to consider these equal.
Those are certainly not equivalent, as the 1st would not even resolve in the majority of environments. I'm assuming the 2nd host is a reverse proxy for the 1st? Unless that fact is part of your design assumptions (which it certainly can't be for a general URL class in a standard library), those should be considered different.
If the two hostnames resolve to the same IP address, they are equivalent. This could happen if initech.com was a default domain. This is a very standard situation in internal networks which i am surprised you are not familiar with.
I am quite familiar with that kind of setup and it's even further from equivalent than what I assumed you were referring to. Shared web hosting is the common example, but some other protocols are capable of distinguishing between hostnames.
google.com and maps.google.com resolve to the same address but are not equivalent. Even if your example and many others are equivalent, there are many that aren't. A general library should not make assumptions like that, unless they hold 100% of the time.
Looking back, I did phrase it ambiguously. What I meant was two URLs that are equal by definition, (no matter the server config, assuming standards compliance). A (general) library should make no more assumptions than the standard it implements. What I was asking about were cases, similar to http://h/p?x1=a&x2=b being equal to http://h/p?x2=b&x1=a since query parameter order is defined to not matter.
The spec I am mostly familiar with is, however, rather new, and whatever standard controlled URLs at the time (if any) might have made more assumptions.
Most languages with Either/Result have some form of monadic composition and type inference. This lets you basically ignore the error condition until you get to a level where it's appropriate to handle it, and the compiler will check the error types for consistency without you having to specify anything explicitly.
Either/Result has often been found to be untenable without these mechanisms, as well. Rust first added the try! macro and then the ? operator because it was so tedious to deal with raw Results otherwise.
This works for checked exceptions as well, just bubble it up to a place where you want to handle them by stating that your function might throw those exceptions.
I love both, but Java's implementation of checked exceptions cause harder and harder problems, especially when combined with tools such as lambdas, since there's no way to generically compose or handle checked exceptions inside them.
Proper Either/Result would fix some of the exception problems with lambdas in Java, though.
It also allows the exceptional behavior to be defined and to be controlled by the developer, one thing that you don't really have today when throwing RuntimeExceptions with say Stream APIs.
Java's checked exceptions are "proper Either/Result." The problem is that they can be of a more complex types (union types) combined with subtyping. In other words, what you see with Either is not a result of it being written as a return type, but a result of it being a much simpler type than Java's exceptions.
The problem boils down to the fact that you can have a disjunction of any number of checked exception types (including zero). No other party of the type system allows disjunctions, so it causes a lot of problems. The checked exception is conceptually part of the return type, but is split out.
I wish Kotlin had, instead of ignoring the existence of checked exceptions, instead translated them into part of the return type. I use Kotlin a lot these days, and one annoyance for me is dealing with code that throws exceptions. They fixed the annoying "(almost) anything can be null" problem of java and replaced it with an equivalent problem. Why can't nullability and failure results both be part of the static type?
(The workaround it to manually use an Either type yourself, but it doesn't help you with calling anyone else's code, since virtually everything throws exceptions on failure.)
Yes, but they are the only part of Java's type system that allows sum types.
How do you declare a method that generically takes a function that in turn takes a K, returns a V, and can throw whatever checked exceptions it wants, and you'll rethrow them? Last I checked, this wasn't possible in Java.
If checked exceptions were instead replaced with sum type return values, then it becomes trivial.
@FunctionalInterface
interface ThrowingFunction<T, R, E extends Exception> {
R apply(T argument) throws E;
}
static <T, R, E extends Exception> R apply(T argument, ThrowingFunction<T, R, E> function) throws E {
return function.apply(argument);
}
But not catch and rethrow it, because throw and catch aren't generic, and the type parameter isn't reified. You can emulate reified generics with the usual trick of passing a Class object:
Right, it's horrid, and it only works for exactly one checked exception type. So there's no way to define a single `Stream.map` (for example) that takes a function that throws whatever number of checked exceptions and just propagates them.
> Java's implementation of checked exceptions looks pretty minimal and sound to me.
>
> How would you implement them?
Java's checked exceptions aren't "minimal" and are problematic because they behave unlike the rest of the types system. I'm not saying that they should have ditched checked exceptions and left it at that, though. What they should have done was make sum types a first-class part of the type system, and then checked exceptions become redundant.
In languages that actually have general sum types rather than special-casing the way Java's exceptions do, you can get multiple types of errors with an Either by making a union of the different types of errors. You can alternatively use an N-way sum type as your return value in place of the 2-way Either.
Kotlin has sealed classes, which can be used to create a sum type, and you can even do the same in Java with enough boilerplate, but in either of those languages you run into another problem if you try this: it doesn't interoperate well with the vast majority of existing library code that throws exceptions.
It's certainly fair to say I have no alternative recommendation. I enjoyed using them prior to Java 8, and they gave the guarantees I was looking for. They have just had a much harder time integrating with newer features than could be hoped for. I'm not sure how much of that is intrinsic to checked exceptions, and how much is intrinsic to Java's implementation.
One example of this: there is no way to encode the type of a checked exception in a generic. I would like to be able to express something like the following:
But there is no way to express that 'throwing E' part. The type of a checked exception is firmly embedded in the interface. So I can't supply, say, an IO method as a Function<S, T> parameter, since the IOException causes a mismatch, and I'd have to write a handler specifically for methods that throw IOException, another for those that throw TimeoutException, a third for those that throw IOException AND TimeoutException, etc; a fairly fruitless goal without automatic code generation.
Interesting. I tried this years ago, and it never worked. Maybe that's changed in recent versions? Arity versions are still more feasible at least, and many languages are willing to pay the cost for them.
AFAICR this has always worked. But you rapidly run into limitations - as i say in another comment, catch and throw are not themselves generic. Plus, none of the interfaces used in the streams API have exception parameters.
> Plus, none of the interfaces used in the streams API have exception parameters.
That's partly because there's another problem. Potentially, you could have streams of the form, say, `...map(A::foo).map(A::bar).map(A::baz).collect(...)`, and each of foo, bar, baz adds another exception type to the set that can be thrown by collect.
Result/Either-based streams have exactly the same problem. An exception-supporting stream API would solve it the same way: a function can either throw no exception, or an exception of the same type as thrown by a previous operation. You could have a .mapException operation which could change the exception type, like Result::map_err in Rust.
In practice, you would end up with the stream throwing an exception of some general type (in 95% of cases, IOException!), but you could still write specific catch blocks for each possible subtype.
Let me put it this way: the message I got from the language team is that the problem is certainly acknowledged, and various alternatives to dealing with it are known, but none of them has so far been shown the clearly preferable, "good" choice, so until one presents itself, the decision is to do nothing rather than add/remove features that could later have other negative consequences.
Maybe that was it. That means there's no way to generically remove or translate the exceptions, e.g. by wrapping it into a runtime exception, or into a specific Either value based on the exception type.
Yeah, I'm pretty sure there were some compiler bugs involving generic exceptions around 1.5 - 1.6. (I don't know if they ever got fixed -- I switched to Kotlin.)
The big difference between the two is that Either/Result are trivial to compose and otherwise deal with in higher-order functions. Java has a massive problem whereby any API that invokes a callback has to either require that said callback not throw anything outside of a given short list of exceptions; or else declare both itself and the callback as "throws Exception", and force the API caller to deal with that - even though the specific callback that caller is passing in might not be throwing anything, or might be throwing a very specific exception only.
iirc scala literally began as just java with pattern matching from odersky's frustration from building javac in raw java. pattern matching is well on its way in modern java, with switch expressions and instanceof binding in jdk14 preview and more general destructuring (further enhanced by records) and guards up next.
in reality though aside from the rock/hardplace of language conservatism/fanatical back-compat i have to imagine a real reason modern java has yet to fully embrace Optional/Either over null/exceptions is lack of value types - yeah escape analysis lets you ~mostly~ not have to worry about the IdentityObjects you're returning everywhere, until you hit something it can't/won't inline or try to stuff those Optionals on heap and wind up making our already painful pointer chasing situation ten times worse. value types are also well on their way via project valhalla but have yet to actually land.
maybe ironically one of the things i recently got bitten by was the fact that MethodHandle .invoke* methods throw Throwable when used directly in plain java - the very machinery that enables the efficient composition of these kinds of functional programming styles is gated behind having to catch literally anything in the language itself. i guess the recurring theme here is java putting the horse before the cart, and that's frankly my favorite thing about the ecosystem.
I loved checked exceptions too, but the ergonomics of Either<Error, Result> works much better if you're writing code in a functional style. I even wrote my own Either which was a fun exercise.
Checked Exceptions are the main reason I switched to C#.NET. I hated checked exception. I know the designers wanted to save us from ourselves but they ended up making the language verbose.
Sure, but you have take into account that Android devs are limited to Java 6, most enterprise devs (based on my experience) are still on Java 8 and some are still on Java 5 or below.
Yeah this is nonsense and isn't an argument against the language. Embedded devs aren't allowed to use Rust, does that mean we shouldn't compare C++ 20 to Rust? Does that mean Rust is a bad language? If these said devs are limited to Java 6/7/8 does their company have the inertia to use a brand new programming language (Kotlin) that their devs don't know? Does this mean Kotlin is bad because they're not using it?
My goal is to compare Kotlin with the current version of Java along with any Java libs that are commonly used (such as Lombok). I have mentioned the fact that some teams are still on older versions of Java just to highlight that not every developer can access the features of the latest Java release.
You can compile Kotlin to target any of these JVMs, so of course it's relevant. If you're limited to using Java 6 every day, Kotlin is a game changer. If you can target Java 13, less so.
Thanks for the feedback.