News for hackers. Stuff that matters.
Re:Java record pattern matching in JDK 19 by hn_throwaway_99 on Saturday May 14, 2022 @12:23PM (#31379521)
Cool to see Java getting these features. After having been a Java dev most of my life, and then moving to Typescript years ago, it's hard to believe I went so long without this.

Related, Typescript is the only language I know of that through flow analysis does not require you to redefine the variable. E.g. you can do something like

  if (typeof foo === 'string') {
    //Typescript knows foo is a string here
  }
All the other languages I know of, like this proposal for Java, require the variable to be redefined. I personally find the Typescript way, as VSCode will give you appropriate type warnings in the right places, easier to use in practice. Just curious if there are any other languages that do it like TS.
Re:Java record pattern matching in JDK 19 by SemanticStrengh on Saturday May 14, 2022 @01:20PM (#31380164)
Don't be mistaken, Typescript do not has proper pattern matching support, The proposal is far from being adopted https://github.com/tc39/proposal-pattern-matching

> Related, Typescript is the only language I know of that through flow analysis does not require you to redefine the variable. E.g. you can do something like

This is called smart casting and is widely used in Kotlin

Re:Java record pattern matching in JDK 19 by viewfromafar on Saturday May 14, 2022 @02:59PM (#31381188)
The PL crowd calls that type system feature "occurrence typing."
Re:Java record pattern matching in JDK 19 by SemanticStrengh on Saturday May 14, 2022 @03:04PM (#31381230)
yes or flow typing https://en.wikipedia.org/wiki/Flow-sensitive_typing It probably overlap with the research on gradual typing too
Re:Java record pattern matching in JDK 19 by swaranga on Saturday May 14, 2022 @12:47PM (#31379783)
There are some edge cases because of which this had to be done, I think. For example calling static methods via the instance variables, the actual method called would be the static type of the variable at compile time and not the actual type at runtime:

    public static void main(String[] args) {
        Parent instance = new Child();

        if (instance instanceof Child p) {
            instance.print(); // prints Parent
            p.print(); // prints Child
        }
    }

    static class Parent {
        static void print() {
            System.out.println("Parent");
        }
    }

    static class Child extends Parent {
        static void print() {
            System.out.println("Child");
        }
    }
Hence, with flow typing, existing code could break in subtle ways.
Re:Java record pattern matching in JDK 19 by smarks on Saturday May 14, 2022 @07:37PM (#31383787)
Several folks have pointed out the poor style in calling static methods using an instance. I think you have a good point, but not a good example. Perhaps a better example is with overload resolution. Consider this Kotlin code:

    fun foo(i: Int) = ...

    fun foo(n: Number) = ...

    fun main() {
        val n: Number = 12;
        foo(n); // calls foo(Number)
        if (n is Int) {
            foo(n); // calls foo(Int)
        }
    }
It's as if smart casting causes the static type of `n` to change in different parts of the main function. As such it also seems to affect overload resolution. Maybe this is exactly what you want. On the other hand it seems like it could lead to some very subtle errors. I'm not a Kotlin programmer, so Kotlin experts please feel free to correct me on details.
Re:Java record pattern matching in JDK 19 by yarg on Saturday May 14, 2022 @06:23PM (#31383240)
Does java provide warnings against calling static methods on Objects?
Re:Java record pattern matching in JDK 19 by smarks on Saturday May 14, 2022 @06:34PM (#31383345)
Yes, if javac is given the `-Xlint:static` option.
Re:Java record pattern matching in JDK 19 by hilbertseries on Saturday May 14, 2022 @06:50PM (#31383483)
If you’re writing code calling static methods on instances of classes, you deserve to have that code broken.
Re:Java record pattern matching in JDK 19 by quantified on Saturday May 14, 2022 @08:37PM (#31384160)
Probably didn’t get written that way but arrived there when that class was refactored somehow.
Re:Java record pattern matching in JDK 19 by recursive on Saturday May 14, 2022 @07:07PM (#31383583)
I've always wondered why that's even allowed.
Re:Java record pattern matching in JDK 19 by _Nat_ on Saturday May 14, 2022 @09:49PM (#31384542)
It's actually the default in C#.

This is, in C#, all methods are called according to an object's apparent-type, not its actual-type, by default. To get the Java-behavior, a C#-method would need to be declared `virtual` (or `abstract`), and then more-derived methods would need to choose to `override` them (rather than hide them, often via `new`).

Part of the advantage might be performance. This is, methods that go with the apparent-type don't need to do a virtual-lookup-table resolution, which can save some work in method-calls.

Another advantage is that it can help provide more flexibility in class-hierarchies, since more-derived classes can "hide" less-derived classes' methods without overriding them. It's probably not something that folks really need to do too often, but it's nice to have an easy solution when such a case occurs.

Re:Java record pattern matching in JDK 19 by recursive on Sunday May 15, 2022 @01:42AM (#31385511)
You're talking about virtual vs non-virtual. static is a whole separate thing. static calls are always resolved at build time. You don't even need an instance at all. And probably shouldn't use one.
Re:Java record pattern matching in JDK 19 by _Nat_ on Sunday May 15, 2022 @03:19AM (#31385859)
Right -- it's an issue of virtual vs. non-virtual, not static vs. non-static.

For example, [this comment](https://news.ycombinator.com/item?id=31379783 ) provided code showing the issue in this branch of the thread using `static` in Java. Here's the same thing in C# without using `static` (except for `Program.Main()`):

    public class Program
    {
        public static void Main()
        {
            A a = new B();
            
            if (a is B b)
            {
                a.Print();  // Prints "A".
                b.Print();  // Prints "B".
            }
        }
    }
    
    public class A { public void Print() { System.Console.WriteLine("A"); } }
    public class B:A { public new void Print() { System.Console.WriteLine("B"); } }
`static` can work too because it implies non-virtual, but it's not necessary.

Generally speaking, virtual-methods resolve with dependence on the object they're called on since they consult a [virtual-method table](https://en.wikipedia.org/wiki/Virtual_method_table ). Non-virtual methods can resolve without considering the object they're called on (whether static or not) because they call the method that belongs to the apparent-type.

Re:Java record pattern matching in JDK 19 by recursive on Sunday May 15, 2022 @01:48PM (#31389388)
> it's an issue of virtual vs. non-virtual, not static vs. non-static.

That's what you started talking about, but that's not what I'm talking about.

> "calling static methods on instances of classes"

That's from an ancestor, somewhere upthread. Here it is as a c# expression.

    "abc".Format("size: {0}", 7)
Sensibly, c# doesn't allow this, but some languages do. I don't recall which, but I've definitely seen this.

Maybe I'm not understanding a word or something, but the description of the problem is definitely about "static".

Re:Java record pattern matching in JDK 19 by _Nat_ on Wednesday May 18, 2022 @12:32PM (#31424764)
In C#, basically all methods are `static` by-default. Just, if a programmer doesn't explicitly mark a method as `static`, then the compiler implicitly inserts an extra argument into the call-signature for `this`.

For example:

    public class A
    {
        //  If a programmer writes this:
        public void DoSomething(int x) { /* ... */ }
    
        //  ...then C# sees this:
        public static void DoSomething(A this, int x)
        {
            if (this == null) { throw new NullArgumentException(); }
            /* ... */
        }
    }
So if a programmer then writes `a.DoSomething(7);`, C# basically automatically converts that into `A.DoSomething(a, 7);` for them. It's basically syntactic-sugar.

C# even lets programmers write methods that can be called on an instance OR as `static`: [extension methods](https://docs.microsoft.com/en-us/dotnet/csharp/programming-g... ).

Then there's a difference between C# and Java: in C#, non-static methods can be `virtual` if marked as such, whereas in Java, all non-static methods are automatically `virtual`.

---

Anyway, my point about the perspective from [this comment](https://news.ycombinator.com/item?id=31383483 ),

> If you’re writing code calling static methods on instances of classes, you deserve to have that code broken.

, was that that's basically what C# does by-default, ignoring syntactic-sugar.

Because, in C#, methods aren't `virtual` by-default, so when C#-programmers call a default method on an instance, they're calling it non-virtually, much like a Java-`static` method -- it may look a little different in C# due to the syntactic-sugar, but it's basically the same thing.

To demonstrate that same-ness, I took the Java code that used `static` from [this comment](https://news.ycombinator.com/item?id=31379783 ), then showed the same without `static` in C# in [this comment](https://news.ycombinator.com/item?id=31385859 ).

Of course, I don't mean that they look exactly the same, due to the syntactic-sugar. Just that they're conceptually the same in terms of logical-structuring and behaviorally the same in terms of what they actually do (e.g., how they printed the same responses in those examples).

Re:Java record pattern matching in JDK 19 by pjmlp on Sunday May 15, 2022 @04:39AM (#31386194)
Influence from OOP languages that preceded Java.
Re:Java record pattern matching in JDK 19 by SemanticStrengh on Saturday May 14, 2022 @01:22PM (#31380200)
How does Kotlin solve those use cases?

> instance.print(); // prints Parent

why? the parent print should be shadowed/overidden by the child bruh

Re:Java record pattern matching in JDK 19 by spullara on Saturday May 14, 2022 @01:38PM (#31380361)
statics don't override
Re:Java record pattern matching in JDK 19 by SemanticStrengh on Saturday May 14, 2022 @01:57PM (#31380552)
oops my bad
Re:Java record pattern matching in JDK 19 by moomin on Saturday May 14, 2022 @02:34PM (#31380912)
Refinement typing (the TS way) is relatively common in type systems that sit on top of dynamic languages because basically they have no choice. Binding to a new variable, however, is a heck of a lot easier to analyse so tends to be done in languages that don’t need to implement refinement typing.

A couple of interesting exceptions: explicit interfaces make it impractical in C#, and there’s a project called liquid Haskell that adds refinement typing on top of an already strong type system.

Re:Java record pattern matching in JDK 19 by saghm on Saturday May 14, 2022 @03:26PM (#31381458)
In my compilers class, this is how we did static downcasting for the toy language we created (I think it was called Oat?). I think it's clever, but I find that I prefer mechanisms that rebind the variable for clarity purposes. It's super easy IMO to accidentally gloss over something as being just a regular if statement rather than something that's actually statically changing the type of the variable compared to having different syntax than just a regular conditional. I also tend to prefer being able to look at a variable's usage and then look back to the time it was initialized and know that it's still the same type; having the type change only within a given scope without any explicit binding seems like something I'd mess up a lot, especially when reviewing code I didn't write myself.
Re:Java record pattern matching in JDK 19 by pkulak on Saturday May 14, 2022 @01:16PM (#31380120)
Kotlin does that as well, and it's a JVM language.
Re:Java record pattern matching in JDK 19 by PartiallyTyped on Saturday May 14, 2022 @01:21PM (#31380176)
Mypy (Python) does this as well. VSCode and PyCharm provide typehints after

   if isinstance(foo, str):
        # code
Re:Java record pattern matching in JDK 19 by dehrmann on Saturday May 14, 2022 @04:05PM (#31381825)
Sometimes. If foo isn't local scope, this won't work because it could be modified in another thread.
Re:Java record pattern matching in JDK 19 by cwalv on Sunday May 15, 2022 @01:11AM (#31385418)
I used to find the workaround pretty jarring:

  lfoo = nonlocal_foo
  if isinstance(lfoo, ...):
Since the checker is correct in these cases, I accepted the ugly and got used to it pretty quickly
Re:Java record pattern matching in JDK 19 by dwaite on Saturday May 14, 2022 @02:53PM (#31381113)
Some of this is a consequence of how typescript works - it carries any structural constraints it can find forward in an advisory capacity, because it is somewhat decoupled from the quite-dynamic runtime behavior of the javascript engine.

It isn't even block-based scoping in the flow analysis above - if in your block there was the code foo = 1; the typescript engine would then expect that foo will behave like a Number at runtime.

I haven't dived in deep enough, but I suspect foo could even have different structural type information within the same expression, e.g. a ternary (typeof foo === 'string') ? something(foo) : somethingelse(foo)

Re:Java record pattern matching in JDK 19 by ygra on Monday May 16, 2022 @01:20AM (#31393864)
It's expression-based as well This doesn't only apply to conditional expressions, but also things like typeof foo === 'string' && ...
Re:Java record pattern matching in JDK 19 by Squarex on Saturday May 14, 2022 @12:30PM (#31379592)
Kotlin is the same as typescript.
Re:Java record pattern matching in JDK 19 by danachow on Saturday May 14, 2022 @01:33PM (#31380303)
That’s not dataflow analysis. It looks like a cast similar to something like in C# ie: if (foo is string as foo) where the “as foo” scoped variable is implicit. It’s basically implicitly redefining the variable with limited scope. Type guards are a slick feature but it is just an expression match and some syntactic sugar. Even user defined type guards in typescript have to be explicitly declared as such.

This would be more an example of CFA.

  const a = “string”;
  if (typeof v === a)
Re:Java record pattern matching in JDK 19 by hn_throwaway_99 on Saturday May 14, 2022 @02:37PM (#31380941)
It's not just a simple cast, but I should have used the correct term which is "control flow analysis". It's not just redefining the variable, control flow analysis works in lots of different places besides just typeof checks, and is possible in many cases because TS is structurally typed, not nominally typed.
Re:Java record pattern matching in JDK 19 by papercrane on Saturday May 14, 2022 @01:05PM (#31379974)
The Typescript way would only work for local variables in Java. Since another thread might update a field between the instanceof check and the use of the field.
Re:Java record pattern matching in JDK 19 by peterkelly on Saturday May 14, 2022 @07:48PM (#31383842)
This is an issue in TypeScript even with single-threaded code; it (always/sometimes?) assumes function calls won't affect the results of type narrowing tests: https://github.com/microsoft/TypeScript/issues/9998
Re:Java record pattern matching in JDK 19 by zarzavat on Saturday May 14, 2022 @02:38PM (#31380943)
You’re definitely holding it wrong if you have polymorphic fields in your lock-free shared memory. Or at least holding it weirdly.
Re:Java record pattern matching in JDK 19 by gizmo686 on Saturday May 14, 2022 @02:43PM (#31380993)
Thats the problem with language design. If the language allows something, it needs to correctly support it; even if actually doing it is a bad idea.
Re:Java record pattern matching in JDK 19 by SemanticStrengh on Saturday May 14, 2022 @01:29PM (#31380262)
those cases are rare. Defensive copying should be optin, not the default. Java really deep copy objects for even the most basic instanceof checks??
Re:Java record pattern matching in JDK 19 by kaba0 on Saturday May 14, 2022 @02:48PM (#31381055)
You can’t change the object’s type in Java, so if you hold on to the same reference it will be safe to use after the instanceof check.
Re:Java record pattern matching in JDK 19 by jung_j on Saturday May 14, 2022 @12:26PM (#31379552)
Java 14 does have something similar

https://openjdk.java.net/jeps/305

Re:Java record pattern matching in JDK 19 by hn_throwaway_99 on Saturday May 14, 2022 @12:33PM (#31379622)
No, Java requires you to redefine the variable, i.e.

  if (obj instanceof String s) {
    // s is a String here, but obj is not
  }
Re:Java record pattern matching in JDK 19 by Pet_Ant on Saturday May 14, 2022 @12:36PM (#31379659)
That I would say still counts as “similar” not “the same”
Re:Java record pattern matching in JDK 19 by danachow on Saturday May 14, 2022 @01:46PM (#31380462)
That’s the same, just that the scoped definition is implicit.
Re:Java record pattern matching in JDK 19 by hn_throwaway_99 on Saturday May 14, 2022 @02:33PM (#31380893)
No, that's the entire point of my question. In Typescript (and Kotlin, as others have noted), the type system knows within the scope of that if statement that obj is a string, so it lets you call string-specific methods on obj withOUT introducing the new variable s.
Re:Java record pattern matching in JDK 19 by danachow on Saturday May 14, 2022 @03:06PM (#31381258)
What I'm saying is that this:

  if (typeof obj === 'string') {
    // obj is a string
  }
is really not anything meaningfully different than this

  if (obj is string as obj) { // as in C#
    // obj is a string (shadowing the outer scoped obj)
  }
or this

  if (obj instanceof String obj) {
    // obj is a String here
  }
you can imagine a pseudo TypeScript language like this

  if (typeof obj === 'string' as obj) {
    // obj is a string
  }
Actual TypeScript merely allows you to elide the "as obj" and all it needs to trigger this is the "typeof obj === <string literal>" inside of an if expression. This can be done by a simple syntactic replacement, it doesn't require control flow analysis to get this specific feature. But yes, if you have a more general computed expression, that would apply, but that was not the case you were stating. Ie the variable isn't the issue (redefining in this case seems like a distinction without a difference)... However this does work in TS, which is a demonstration of CFA:

  const isStr = typeof obj === "string";

  if (isStr) {
    // obj is str
  }
For what it's worth I think CFA is useful in TypeScript based on it at its core being a structural typed bandaid over JS, but I think these specific CFA type narrowing features are redundant in stronger typed languages.
Re:Java record pattern matching in JDK 19 by pjmlp on Sunday May 15, 2022 @04:41AM (#31386202)
Java cannot do the same without breaking backwards compatibility, hence the idea to introduce a variable explicitly.
Re:Java record pattern matching in JDK 19 by SemanticStrengh on Saturday May 14, 2022 @01:30PM (#31380275)
but is it a deep copy??
Re:Java record pattern matching in JDK 19 by sebazzz on Saturday May 14, 2022 @01:37PM (#31380355)
It is not a copy. It still references the same object.
Re:Java record pattern matching in JDK 19 by Smaug123 on Saturday May 14, 2022 @01:00PM (#31379919)
F# also has:

  let foo : obj = failwith ""

  match foo with
  | :? SomeType as blah ->
      // use blah : SomeType
(You're allowed to just use the identifier `foo` in the match arm.)
Re:Java record pattern matching in JDK 19 by halfmatthalfcat on Saturday May 14, 2022 @02:11PM (#31380689)
As does Scala:

  match thing {
    case a: String => 
      // use a as string
    case a @ MyCaseClass(b: String, c, d) =>
      // use a as the instance of MyCaseClass or use b as a string
  }
Re:Java record pattern matching in JDK 19 by arthurcolle on Saturday May 14, 2022 @05:50PM (#31382936)
> All the other languages I know of, like this proposal for Java, require the variable to be redefined.

can you give a few examples? This makes no sense to me and I've touched quite a few ecosystems in my time. Maybe I am misunderstanding but my current understanding makes this seem not realistic

Re:Java record pattern matching in JDK 19 by gizmo686 on Sunday May 15, 2022 @12:21AM (#31385236)
Consider the following pseudo-Java:

    Object foo = ...
    if (foo instanceof String) {
        System.out.println(foo.toLowerCase())
    }
The above is not valid Java, because foo has type Object, which does not have a toLowerCase() method, so the compiler refuses to compile it. What you need to do is something like:

    Object foo = ...
    if (foo instanceof String) {
        String sfoo = (String) foo
        System.out.println(sfoo.toLowerCase())
    }
Granted, you don't necessarily need to explicitly redefine in, as Java allows anonymous casting:

    Object foo = ...
    if (foo instanceof String) {
        System.out.println(((String) foo).toLowerCase())
    }
Regardless, the point still stands. Java (and most strongly typed languages with an inheritance based type system) requires an explicit cast, even if a straight forward static analysis would show that the object in question is already the desired type.
Re:Java record pattern matching in JDK 19 by GavinBierman on Monday May 16, 2022 @05:20AM (#31394947)
This is all true, but with pattern matching for instanceof you don't need the explicit cast:

    Object foo = ...
    if (foo instanceof String s) {
        System.out.println(s.toLowerCase())
    }
In this case, the scope of the pattern variable `s` is just the then block, so are free to reuse it elsewhere in the block.
Re:Java record pattern matching in JDK 19 by TheMatten on Saturday May 14, 2022 @02:33PM (#31380900)
And of course, Haskell has equivalent too:

    case eqT @a @String of
      Just Refl -> ...
Re:Java record pattern matching in JDK 19 by skybrian on Saturday May 14, 2022 @01:23PM (#31380209)
Dart also does this.
Re:Java record pattern matching in JDK 19 by markdog12 on Saturday May 14, 2022 @01:40PM (#31380397)