> 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
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. 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.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.
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.
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".
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).
> instance.print(); // prints Parent
why? the parent print should be shadowed/overidden by the child bruh
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.
if isinstance(foo, str):
# code lfoo = nonlocal_foo
if isinstance(lfoo, ...):
Since the checker is correct in these cases, I accepted the ugly and got used to it pretty quicklyIt 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)
This would be more an example of CFA.
const a = “string”;
if (typeof v === a) if (obj instanceof String s) {
// s is a String here, but obj is not
} 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. 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.) 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
}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
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. 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. case eqT @a @String of
Just Refl -> ...
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
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.