This seems to be the crux of the disagreement:
> Except that strong vs. weak typing is quite literally a question of "what are the semantics of int in JS". You can't discuss weak vs. strong types without discussing the language's actual semantics, and not the semantics of some DSL you can defined on top of the language. They're all Turing complete.
To some extent it's a definitional issue; like I said in an earlier comment, I think you can sensibly argue that because + / * etc are privileged with special syntax they are "part of the language." If you chose to define it that way, then yes, the semantics of int, +, etc. matter. If you're willing to treat the syntactic sugar as unimportant, then you can just wrap the bad library api with a better one:
var TypeErr = {};
var AttrErr = {};
var mul = function(l, r) {
if(typeof(l) === 'number' && typeof(r) === 'number') {
// both are built-in numbers; use built-in *.
return l * r;
}
try {
if(typeof(l) !== 'object' || !('__mul__' in l)) {
throw AttrErr
}
return l.__mul__(r)
} catch(e) {
if(e !== TypeErr && e !== AttrErr) {
throw(e)
}
if(typeof(r) !== 'object' || !('__rmul__' in r)) {
throw AttrErr
}
return r.__rmul__(l)
}
}
// Javascript doesn't actually have ints, just floats, but there's a
// common trick with the bitwise operators to get them; let's abstract it out:
var int = function(n) {
return {
_value: n,
__mul__: function(r) {
return int(mul(this._value, r._value)|0)
},
__rmul__: function(l) {
return int(mul(l._value, this._value)|0)
},
}
}
console.log(mul(7, 2))
console.log(mul(int(4), int(2)))
console.log(mul(4, "hello")) // this thorws AttrErr.
It's not really any differrent (again, if you dscount the privileged syntax) than using requests instead of the mess that is urllib, or any other instance of "that API is terrible, let's use a different library."> Fine, stop confusing "strong vs. weak typing" with "runtime type checking". Strong vs. weak typing is about coercion. Runtime type checking is about type checking.
Run-time checking is a requirement to avoid coercion (assuming you also don't have static checking) -- everything is ultimately just bits, so if the check never actually occurs, it just "does something." Granted, you don't get active, willfull coercion like in Javascript, but ultimately if the implementation of int.__mul__ didn't do some kind of run-time checking, you would just get some garbage number when you called it. If you don't have any checking, you have coercion. Probably not object -> string, but quite likely object -> god-knows-what-memory-corruption. This is the nature of much of what you describe as C's "weak-typing" -- no runtime checks. The only difference between dereferencing a NULL pointer in C and doing None.foo in python is a run-time check.
> I really think that thinking in terms of interfaces is the easiest way to conceptualize this.
I agree. And my argument as to why strong-typing is not a feature of python-the-language is that it doesn't provide any declarative way for me to extend that property to my own interfaces; if I have some higher-level interface that doesn't fall right out of the existing Python libraries' interfaces, I have to do all of the same kind of work that I did in the above snippet of Javascript.
I think the concept of strong vs. weak typing that you're describing is coherent, but only (a) as a property of libraries, not languages, which is my argument, or (b) you assert that the built-in syntax is central. I think the latter is defensible.
Note that I am not saying that design choices of libraries that ship with the language don't matter, or even that they don't matter more than something used less often. People use these libraries every day, because they are there. The best thing Python has going for it is its ecosystem, and if you're e.g. evaluating it as a tool to use, splitting hairs like this over part-of-language vs. library probably doesn't make a ton of sense.