3 ms·
> 1. Do you want the language to require the coder to provide type information (e.g. Java), or permit the coder to provide type information (e.g. Common Lisp) o
by munificent 4y ago
> 1. Do you want the language to require the coder to provide type information (e.g. Java), or permit the coder to provide type information (e.g. Common Lisp) or forbid the coder from providing type information (e.g. early versions of Python, Javascript).
Liberties are rarely created and destroyed. Instead, they tend to be trasferred from one place to another. So an equally valid way to phrase this question is from the compiler's perspective:
1. Do you want the compiler to be able to rely on the presence of type information (e.g. Java), or sometimes have access to type information (e.g. Common Lisp) or be forbidden from using type information (e.g. early versions of Python, Javascript).
In that equivalent formulation, the value proposition of static types is clearer: When the language specifies that everything has a computable static type, then the compiler can rely on types for useful language features (extension methods, overloading, static dispatch, etc.) and to generate smaller, faster code.
Dynamic types give the programmer some liberties (they can not worry about types) but takes some others away (they can't reach certain performance goals or use type-dependent features). Static types do the opposite. Pick your poison.
- habitue 4y agoAdding to this, there's a set of language features which use the type system to generate code. So, things like traits in Rust or Typeclasses in Haskell use the type system to infer which instance is being used and insert a dictionary of trait method implementations as the first argument to your polymorphic function. You can't really do this kind of thing if the compiler can't do `Type -> code` and is only verifying the types are consistent. A pseudocode example is something like: trait ToString for T { fn toString(self: T) -> String; } impl ToString for number { fn toString(self: number) -> String { // implementation here } } impl ToString for Point { fn toString(self: Point) -> String { return f"({self.x.toString()}, {self.y.toString()})"; } } fn printString<T: ToString>(t: T) { print(t.toString()); } Then when it generates code, it uses the inferred type information to generate different code: let p = Point(1.0, 2.0); printString(p); // translated into printString(implToStringForPoint, p); let n = 2.0; printString(n); // translated into printString(implToStringForNumber, n); And then type inference becomes implementation inference, which is really powerful
- kaba0 4y agoBeside performance impact, how is it different than what is possible with virtual method (dyn dispatch)?
- svieira 4y agoOne jump vs. multiple pointer dereferences.
- munificent 4y agoVirtual dispatch makes it much harder for an optimizing compiler to inline method calls, so there is a significant performance hit even for callsites that do end up always calling the same method.
- int_19h 4y agoOP also glossed over the important distinction between opt-in static types (TS, Python etc) and opt-in lack of them ("dynamic" in C#). When talking about what's permitted, these are functionally the same, but the difference has an immense effect on the actual idiomatic use. I would argue that something like "dynamic" is preferable in a sense that it gives you an escape hatch with minimal syntactic overhead for those cases where dynamicity is really that beneficial, yet defaults to a safer option that's usually the best choice.
- kaba0 4y agoIsn’t dynamic’s primary purpose is multiple dispatch methods? In an OOP language (not caring about primitives and non-objects), you can go “dynamic” typed by using Object, and casts.
- int_19h 4y ago"dynamic" literally behaves as if the variable declared such had the actual type of the object at runtime every time that it's referenced, and under the hood it is all implemented using casts and reflection (and a lot of caching to make it fast enough - that's the DLR bit). So method calls are still single-receiver, it just makes them behave as if all methods were virtual. I guess the part where it will also pick the correct overload at runtime depending on the actual type of the arguments is a bit like multiple dispatch, especially if the argument itself is "dynamic". I'd say that the difference is that the list of possible candidates is closed once the class is defined, so even if you declare a new type, you can't customize the behavior when passing it to an object of an existing class.