Remix.run Logo
vips7L 2 days ago

>but at this point, the only big technical features that still put Kotlin over Java for me is the handling of nulls by the type system

Soon (tm): https://openjdk.org/jeps/8303099

The one feature that keeps me in Java, albeit not popular, is checked exceptions. I far prefer checked errors over checked nulls if I have to make a choice.

Defletter a day ago | parent | next [-]

While I am definitely impatiently waiting for null-restricted types, what I feel Java really needs overall is ergonomic handling syntax, notably: "safe calls" (https://kotlinlang.org/docs/null-safety.html#safe-call-opera...), "elvis operator" (https://kotlinlang.org/docs/null-safety.html#elvis-operator), and inline catching (https://ziglang.org/documentation/0.14.1/#catch).

Relevant: https://news.ycombinator.com/item?id=44672787

vips7L a day ago | parent [-]

Yeah I’ve writen about error handling syntax here a bit: https://news.ycombinator.com/item?id=44551088

https://news.ycombinator.com/item?id=44432640

It’s actually my #1 issue. I hate not knowing about error conditions and no one in Java uses checked exceptions because the language syntax for dealing with them sucks. Brian had a proposal for handling exceptions in switch but it seems to have died in the water.

Part of me secretly hopes Swift takes over the world because they have a typed throws that works and handling errors is a breeze.

Defletter a day ago | parent [-]

While I am certainly a fan of Swift's error handling and think it'd be an improvement to Java's current state of affairs, I do think that using null as an error analogue is... unwise. What happens when a function is throwable but also may return null? How do you determine whether the null is a coerced error or a valid return? Or rather, how do you do this without returning to the un-ergonomic try-catch? Zig solves this by having errors and nulls be separate parts of the type system which you can deal with separately and inline.

vips7L a day ago | parent [-]

You don't coerce it. You don't care in that situation. That's the whole point. You're saying you don't care and are going to use null. If you care, you don't use try! you do the more verbose catching:

    let i: Int?
    do {
        y = try someThrowingFunc()
    } catch SomeError.err {
        y = nil
    } catch SomeError.youCareAbout {
        //
    }
Defletter a day ago | parent [-]

You misunderstand. In the question given, we don't care about the error, only that an error having occurred is detectable. In Rust, this would be represented as: Result<Option<ExampleType>, ()>, whereas using try? would reinterpret the error as a null, so there's no way to tell the difference between an error-as-null and a returned-null.

This has consequences for config parsing, for example, where a particular subsection (sub-object? keyed struct?) may be optional, so it being missing is perfectly legal. But if you use try?, then there's no way to distinguish between it simply being missing and it being present but malformed. It unfortunately seems like the only other options in Swift is to propagate the error, or revert back to verbose try-catch blocks.

Whereas, in Zig, you can do inline catching and care about the error only as much as you want to, for example:

    // This is equivalent to Swift's try
    const i: ?i32 = try someThrowingFunc();

    // This is equivalent to Swift's try?
    const i: ?i32 = someThrowingFunc() catch null;

    // This is a yielding inline catch block that does not care about the error
    const i: ?i32 = someThrowingFunc() catch brk: {
        logger.warn("Something went wrong!");
        break :brk null;
    }
It's not perfect, I don't love the block-label-break thing, but I much prefer this if only because then the variable can be defensively const, which I do not believe is possible with the kind of code snippet you provided. Also, instead of breaking out, it could capture and return the error or return a new error. It's incredibly versatile.
vips7L a day ago | parent [-]

I’m not misunderstanding. You weren’t making a point about _verbosity_ originally. You were talking about distinguishing errors you care about; which is perfectly possible, you don’t use the construct that says “I don’t care what error it is”.

Defletter 12 hours ago | parent [-]

I did ask "Or rather, how do you do this without returning to the un-ergonomic try-catch?" but I don't want to die on this hill. Can I just assume then that this distinction isn't possible in Swift without returning to try-catch blocks?

ragnese 19 hours ago | parent | prev [-]

I'm with you when it comes to checked exceptions. I have several ranty comments on this site about how everyone has been wrong to cargo-cult hatred of the feature for the last decade or so.

But, I'd still rather have safe/correct null type checking, because at the end of the day, I can always write MY code to return a Result/Try/Either type instead of throwing unchecked exceptions for expected business logic "failure" paths.