←back to thread

517 points bkolobara | 4 comments | | HN request time: 0.357s | source
Show context
misja111 ◴[] No.45049536[source]
Come on, the author is comparing Rust to Typescript, Zig and Python, and then concludes that Rust becomes more productive in larger codebases compared to 'some other language'? Could it be that those 3 languages were not really designed with large (backend) codebases in mind? To do a fair analysis the author should have compared with a language like Java, Scala or C#.

About the article itself: the part of wrapping a structure in a mutex because it is accessed concurrently is a bit of a red flag. If you are really working in large codebases, you'd like to avoid having to do that: you'd much rather encapsulate that structure in a service and make sure all access to the structure is queued. Much simpler and less chance for nasty deadlocks.

replies(1): >>45049561 #
Ygg2 ◴[] No.45049561[source]
> To do a fair analysis the author should have compared with a language like Java, Scala or C#.

Wouldn't that be unfair to them? They still use null or Nullable. They don't have ADTs, their thread-safe invariant is maintained by docs, etc.

replies(2): >>45050052 #>>45050252 #
pjmlp ◴[] No.45050252[source]
Java and Scala certainly have ADTs, while Scala and C# have nullable types support.
replies(1): >>45050332 #
Ygg2 ◴[] No.45050332[source]
Ok, I should have been more specific. By ADT I meant sum types, or, to be even more exact, discriminated union. Everyone and their mother has product types.

Java doesn't have a discriminated union for sure (and C# as of 8.0). It does have a `|` operator that can cast two objects to the nearest common ancestor.

Having nullable support is the issue. I've played around with it in C#. Nullables are horrible. And I say this as someone who was formerly in Option<T> is horrible.

You can easily cause type confusion, and if the number of times a non-nullable has been nullable (or at least has appeared as that in the debugger) was greater than zero. To be fair there was reflection and code generation involved.

replies(1): >>45050735 #
pjmlp ◴[] No.45050735[source]
I advise you to update your language knowledge to Java 24, C# 13, Scala 3.

From another comment of mine,

        type Exp = 
          UnMinus of Exp
        | Plus of Exp * Exp
        | Minus of Exp * Exp
        | Times of Exp * Exp
        | Divides of Exp * Exp
        | Power of Exp * Exp
        | Real of float 
        | Var of string
        | FunCall of string * Exp
        | Fix of string * Exp
        ;;

Into the Java ADTs that you say Java doesn't have for sure,

    public sealed interface Exp permits UnMinus, Plus, Minus, Times, Divides, Power, Real, Var, FunCall, Fix {}

    public record UnMinus(Exp exp) implements Exp {}
    public record Plus(Exp left, Exp right) implements Exp {}
    public record Minus(Exp left, Exp right) implements Exp {}
    public record Times(Exp left, Exp right) implements Exp {}
    public record Divides(Exp left, Exp right) implements Exp {}
    public record Power(Exp base, Exp exponent) implements Exp {}
    public record Real(double value) implements Exp {}
    public record Var(String name) implements Exp {}
    public record FunCall(String functionName, Exp argument) implements Exp {}
    public record Fix(String name, Exp argument) implements Exp {}

And a typical ML style evaluator, just for the kicks,

    public class Evaluator {
        public double eval(Exp exp) {
            return switch (exp) {
                case UnMinus u -> -eval(u.exp());
                case Plus p -> eval(p.left()) + eval(p.right());
                case Minus m -> eval(m.left()) - eval(m.right());
                case Times t -> eval(t.left()) * eval(t.right());
                case Divides d -> eval(d.left()) / eval(d.right());
                case Power p -> Math.pow(eval(p.base()), eval(p.exponent()));
                case Real r -> r.value();
                case Var v -> context.valueOf(v.name);
                case FunCall f -> eval(funcTable.get(f.functionName), f.argument);
                case Fix fx -> eval(context.valueOf(v.name), f.argument);
            };
        }
    }
replies(1): >>45051386 #
Ygg2 ◴[] No.45051386[source]
> I advise you to update your language knowledge to Java 24, C# 13, Scala 3.

I'll update it when I need it :P I'm pretty sure I'll never need Scala 3.

> Into the Java ADTs that you say Java doesn't have for sure

That still seems like casting to a common object + a bunch of instanceof. But I guess if it looks like a duck and walks like a duck.

> C #13

Wait. You mentioned C# has ADTs via sealed classes IIUC. Why the hell do they have a https://github.com/dotnet/csharplang/issues/8928 ticket for discriminated unions then?

It implies that there are some differences between sealed interfaces and discriminated unions. Perhaps how they handle value types (structs and ref structs).

replies(1): >>45051465 #
pjmlp ◴[] No.45051465[source]
In no such way I mentioned C# has ADTs,

> Java and Scala certainly have ADTs, while Scala and C# have nullable types support.

replies(1): >>45052409 #
1. Ygg2 ◴[] No.45052409[source]
Then why did you tell me to update my knowledge of C#? It wasn't wrong for the discussion at hand.

Also, you failed to mention that the generics ADT in Java is still abysmal (it's relevant, because the topic started as boosting productivity in Java with ADT, I don't find ClassCastException as really boosting anything outside my blood pressure):

    sealed interface Result<T, E> permits Ok, Error { }
    
    record Error<E>(E error) implements Result<Object, E> {}
    record Ok<T>(T value) implements Result<T, Object>  {}

    //public <T, E> Object eval(Result<T, E> exp) {
    //    return switch (exp) {
    //        case Error<E> error -> error.error;
    //        case Ok<T> ok -> ok.value;
    //    };
    //}

    public <T, E> Object eval(Result<T, E> exp) {
        return switch (exp) {
            case Error error -> (E) error.error;
            case Ok ok -> (T) ok.value;
        };
    }
Guess I'll wait for Java 133 to get reified generics.
replies(2): >>45054368 #>>45055071 #
2. pjmlp ◴[] No.45054368[source]
Because your English reading comprehension missed the fact I was talking about the nullability improvements.

Given the expert level, you are certainly aware that most ML languages don't have reified polymorphism on their implementations, right?

replies(1): >>45056145 #
3. lock1 ◴[] No.45055071[source]
Yeah, Java's generics still kind of suck. There's a rumor after project Valhalla, Java language maintainers might add reified generics in the future. However, I think the current state of Java ADT & generic isn't that bad for most purposes.

Though due to its nullable-by-default type system and backward compatibility, there's a decent amount of footguns if you're trying to mix Java's FP & ADT with code that utilizes nulls.

About your code example, you could just do something like this to avoid explicit casting

  sealed interface Result<T,E> {
      record Ok<T,E>(T value) implements Result<T,E> {}
      record Error<T,E>(E error) implements Result<T,E> {}
      public static <T,E> Object eval(Result<T,E> res) {
          if (res instanceof Error<T,E>(E e)) // Rust's if let
              System.out.println(e);
          return switch (res) {
              case Ok(T v) -> v;
              case Error(E e) -> e;
          };
      }
  }
The new "pattern matching" feature of `instanceof` is certainly handy to avoid stupid ClassCastException.
4. Ygg2 ◴[] No.45056145[source]
> Because your English reading comprehension missed the fact I was talking about the nullability improvements.

Tank you :P Never claimed to be native English speaker.

Were there any meaningful changes in nullability semantics between C# 8.0 and C# 14.0? The issue I encountered was related to a complex game engine doing some runtime reflection, dependency injection, code gen and so forth.

I also never claimed to be ML expert. But them being reified or not, doesn't change my point that ADT in Java much like generics look like a design afterthought.