Note that "cast" and "coerce" are different. Python supports coercion but not casting. Casting is an operation done in static type systems where you tell the compiler "I know this has type X, but pretend it has type Y". Coercion is a run-time operation which moves data from a box of one type into a box of a new type (i.e., a new object is allocated).
> Part of the problem is that we've lost a whole quadrant of languages in the static-dynamic weak-strong graph: Weak dynamic typing is nonexistent these days.
I'm not sure what you mean by "the problem" here. Weak dynamic type systems are, in my opinion, the worst of all worlds. They provide neither compile-time checks nor runtime checks that really amount to much. I don't know why you would ever prefer a weak dynamic type system in a language.
JavaScript is essentially weak and dynamically typed. The weak part comes from all the implicit coercions the language inserts to ensure your code nearly always produces some result. This is terrible because it makes identifying errors exceptionally more difficult than in a strong or static type system.
Right. I know this, I just call coercion "autoconversion" because the data is typically given a new form as well as a new box. Having autoconversion without casting is a sign of a strong type system, because you can't turn off the type system, you are only allowed to use it. If there's no way to autoconvert from type A to type B, that's it: You can't turn a value of type A into one of type B by hook or by crook.
> I'm not sure what you mean by "the problem" here.
"Problem" in the sense of our discussion being incomplete, nothing more.
> Weak dynamic type systems are, in my opinion, the worst of all worlds. They provide neither compile-time checks nor runtime checks that really amount to much. I don't know why you would ever prefer a weak dynamic type system in a language.
I agree with all of this.
> JavaScript is essentially weak and dynamically typed.
Wrong, for the reasons I went over in other posts.
> I know this, I just call coercion "autoconversion" because the data is typically given a new form as well as a new box.
Hmm interesting. I think I dislike this because coercion is not always automatic, so the "auto" part of "autoconversion" is a hang-up for me. For example, doing `int("3")` in Python is an explicit coercion, but I don't think it would qualify as an "autoconversion" (unless I'm not quite understanding your definition of that term). In the interests of clarity until I understand better, I'll continue using "coercion" and I'll specify implicit vs. explicit as needed.
> Having autoconversion without casting is a sign of a strong type system, because you can't turn off the type system, you are only allowed to use it.
I agree for explicit coercion, but not with implicit.
If your coercion is implicit, then the user has no control over what's going on. The type system is essentially transforming values' types behind the curtain in an effort to prevent errors. This means that errors which the user might believe should be raised may not be. The type system is "weak" because it's not holding types rigidly with respect to the user's perspective. The user's perspective is key, because if we look at things from the language's perspective then almost any language could be viewed as strongly typed given the right theoretical framework.
> "Problem" in the sense of our discussion being incomplete, nothing more.
Totally fair!
Is JavaScript not considered to be at least partially on the weak side of the spectrum? Things like using the `+` operator on arrays and objects and getting back numbers and integers seems to be to be weak typing.
I might be in the minority on this, but I draw a distinction between stuff which gives odd results but is defined and controlled, and stuff which is unspecified and only 'defined' to the extent the machine does... something when you do it.
Everything Javascript does is in the first camp. Autoconversion is specified, it's part of the language design, and the implementation handles it. What C does when you use casts to turn a string of chars to a double and add the result to some other value is undefined, and whatever the hardware happens to do is what happens.
So doing a lot of autoconversion to the point you can feed any value to any function and no errors happen greatly reduces the value of dynamic typing in making testing easier. However, the implementation is still keeping track of types, because it's doing the autoconversions for you.
The Javascript equivalent to Bliss is using stringly typing to the extent your only language-level datatype is the string, and every other type only exists in the programmer's head. But that's not a property of the language as much as it's a property of the specific codebase.
Whoa, this is totally orthogonal to the strong/weak typing distinction.
Traditionally, strong/weak typing refers to how easy it is to "trick" the type system into doing something it wouldn't otherwise want to do. C is weakly typed because you can cast things all over the place and do whatever you want with the actual values, regardless of what type they're "supposed" to have. This is completely unrelated to undefined behavior.
Right, and autoconversion according to spec is something the type system does want to do. It uses type information to do it, in fact.
The C type system is perfectly happy with casts. The behavior of casting is well specified. And yet the fact that the type system supports casts to the extent that it does means that C's type system is weak.
But this is not the only metric to judge a type system's "strength", and it was poor word choice on my part that I phrased my statement in a way that made it seem so.
JS's type system is weak because of implicit coercion. The fact that values of type X can be coerced into values of type Y at runtime without any notification to the user is indicative of a weak type system, regardless of how well-specified this behavior is. Weak type systems do not lack specification; they lack a feeling of types being "rigid" or "strong". If your values can be converted to different (semantically incompatible) types, then your type system is weak because it isn't telling you anything.
A strong type system is one which stands up to you and says "No, I won't let you do that because I don't think that's what you meant to do." A weak type system is one which does its best to let you do whatever you want. Again, the specification of the behavior of various operations within the type system (such as casting or coercion) is not the determining factor in assessing the "strength" of the type system.
... or plain doesn't care?
Python does no runtime checks of types. The type system employed by Python is effectively a structural (not nominal) system. So long as you provide things with the right structure (i.e., your objects provide the correct fields/methods), everything will work. But many people would like say that Python just "doesn't care" what you want because of the lack of any nominal type checking at any point.
UB just means that the language specification makes no assertion about how to handle a given case — the behavior is literally just not defined. Any implementation of the language could choose to raise an error for such cases and that would conform correctly to the standard.
Python is strongly typed because you cannot use a value of type X in a position where a value of type Y is expected. But Python doesn't check in advance — it just tries to go and do the thing. The trick to this is that Python's type system is actually structural instead of nominal; it doesn't care what the name of anything is, so long as it's got the right fields. (This is where we get "duck typing" from.) But you can't, for example, have Python do `"3" + 2`. A runtime error will result, because the language does not perform any implicit coercions.
JS is different. There are some native implicit coercions from certain types to other types. The presence of these makes JS's type system weaker than Python's, so in general I see it grouped as "weakly typed".
> for (const x of [1,2,3]) { console.log(x); }
1
2
3
undefined
> for (const x of {a:1,b:2,c:3}) { console.log(x); }
TypeError: {(intermediate value)(intermediate value (intermediate value)} is not iterable
So, sometimes JS automatically performs type conversions. But it also has the same kind of type-related runtime errors as Python. It just depends what you're doing.
To the extent this is true, it's true because modern dynamic type systems are fairly strong. They enforce a contract about what operations are valid on a given type, and you can't usually get out of the contract. If you can cast in Python like you can cast in C, I don't know about it.
Part of the problem is that we've lost a whole quadrant of languages in the static-dynamic weak-strong graph: Weak dynamic typing is nonexistent these days.
We used to have it. B and Bliss were both weakly dynamically typed languages. Extremely so, in that they only had one datatype: The machine word. Since literally any operation was valid to perform on a machine word, there was no way for the type system to disallow anything, so it faded into irrelevance. Much like Bliss, in fact.
That's taking it to an extreme, shoving the "weak" part of weakly-typed so far that the language is untyped, like assembly language is usually regarded as being.