Python is strongly typed. Now it has static types enforced at runtime. It’s good to be precise when there are so many quirks to expressing and checking types.
I'm sorry to break it to you, but that's an oxymoron. The whole idea of static types is that they don't need to be validated at runtime.
Python has a static type system which is gradual (ie. allows for untyped code) and is separate from the strongly but dynamically typed semantics of Python at runtime. Now, the static type system tries to mirror the dynamic semantics wherever it can, but it's still a completely separate beast. In other words, you don't enforce static types at runtime - you enforce dynamic types at runtime as usual and have an option of additionally using static type system. By the time you run the code, the static types are mostly gone. The fact that the static types reflect the dynamic type system produces an illusion that the static types remain, but they don't.
If what you said was true, the following code would not work:
x: int = 0
x = "0"
print(x)
but it's still a valid Python code which runs just fine.
The Python VM does nothing with the computed type hints. It's only useful for 3rd party tools that want to check the type.s PyCharm does it natively. mypy and pyre are command line tools you can use stand alone or plug into an editor (VSCode integrate very well with mypy).
That's not how it works. Type annotations are ignored at runtime. And to be pedantic, even if they were evaluated at runtime, they would be dynamic types, not static types. :p
The type annotations exist to make the code easier to read and to enable certain kinds of static analysis (including a type checker).
There's disagreement on that point and I think it's worth pondering. Python has more typing than some similar languages but less than popular static languages. I think there's an argument that the value curve for type checking has thresholds and Python hits a point where a fairly large percentage of people feel that going further starts costing more in overhead than it saves, with many projects being small enough that it's not an especially significant source of problems.
Python is not "strongly typed". Please don't repeat this phrase near management folk. Python just gives good error messages when it crashes at runtime, e.g. "expected an Int but got a String". This is better than a segfault, but it's still a crash.
Call it "strongly tagged" if you like, but "type" unqualified should mean static type. Even "dynamic type" is a marketing hijack of the word type.
"getting the two confused" implies that your terminology is standard, which it isn't. It is also quite common to use the term "type" to refer exclusively to properties that are checked statically.
Arguably "strong dynamic typing" is a property of libraries, not languages -- e.g. int conceptually has a method like:
def __add__(self, other):
if not isinstance(other, int):
raise TypeError(...)
return _unchecked_add_ints(self, other)
It's implemented in C for efficiency, but that's basically the semantics. Critically, this doesn't magically extend to user-written libraries, so unless you actually write all of that boilerplate nothing but a handful of methods in the standard library can be claimed to be "strongly typed". I've written code with a bunch of asserts in methods like that after determining that a large percentage of bugs were type errors that were silently doing the wrong thing. Without something like mypy, Python is untyped by any reasonable definition; it provides no support for users to actually work with types in any meaningful way.
I think you got the user-written part the wrong way around. It is strongly typed because unless you define the operators/methods you explicitly want to enable, they don't exist. For example if you try "object() + object()", you get an exception. You can implement the addition in a subclass, but that's explicit then.
Compare to JS where "new Object() + new Object()" results in a string by default.
You only get an exception if and when something in the bowels of your implementation bangs into a low-level operation that has one of these checks in it. For example:
>>> import subprocess
>>> subprocess.call([2])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python2.7/subprocess.py", line 172, in call
return Popen(*popenargs, **kwargs).wait()
File "/usr/lib/python2.7/subprocess.py", line 394, in __init__
errread, errwrite)
File "/usr/lib/python2.7/subprocess.py", line 1047, in _execute_child
raise child_exception
AttributeError: 'int' object has no attribute 'rfind'
In the case of `object() + object()`, that check is in `object.__getattribute__`
My point is that the checks are a property of a definition of those (possibly built-in) classes; the language doesn't provide any facility to talk about types as such. Nothing in the language knows that the argument to subrpocess.call should be a list of strings, it just happily executes until it hits what is essentially:
>>> (2).__getattribute__('rfind')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'int' object has no attribute 'rfind'
And `__getattribute__` throws an exception. This is the best case scenario. Worst case it never hits something that these primitive types are aware is not ok, and it just does the wrong thing. There's no way to specify typing invariants in a way the language understands -- you just have to put in the manual check yourself. This is what I mean when I say strong types aren't part of the language.
But if you add static types to the mix (say via mypy):
If it were actually checking what should be the types involved in the code you wrote you'd get something like this:
error: List item 0 has incompatible type "int"; expected "Union[bytes, str, _PathLike[Any]]"
...which is what mypy reports when run on that code. Python isn't checking the type of the argument subprocess.call ever, not even at runtime. If you're lucky, eventually you hit some code that has an explicity sanity check in it and it raises an exception.
There's an interesting point in the design space that you see with julia[1], and also with mechansims like racket's contracts[2], where the types aren't checked statitcally, but they are checked at runtime, unlike the example above with subprocess, where you only get the error deep in the implementation when something actually explodes. I think you could sensibly say that the "strength" is actually a property of Julia, rather than its libraries, but not in Python, the "strength" isn't part of the language per se.
The only differece with a "weakly typed" language is that those basic libraries have a much more footgun-like design -- again, not really about the language per se.
In a weakly typed language, that 2 would be implicitly converted to a string. So yes, python is absolutely strongly typed, as opposed to js which is not.
Indeed, but you have to explicitly do that. The language doesn't for you.
Implicit type coercion is the factor that defines weak typing. Python doesn't do implicit type coercion. Therefore it is not weakly typed. Libraries have nothing to do with it.
Yes you could, but you could do the same in Java by having a method take in Object and cast. Which by the way is exactly what's happening in the python code.
I am interpreting the term "library" somewhat broadly -- my notion is that int isn't conceptually any more core to the language than e.g. the numpy matrix classes. It's built-in for speed, but (apart from some syntactic sugar for literals), it's semantically just another class. In this light, the distinction is happening in the code that defines the int class, not in something deep in the language semantics.
The call to str isn't really a cast (which doesn't really have a counterpart in a language without static types); you're calling the constructor to the str class, which somewhere in it has a code path where it formats an int as a string (probably by way of calling the argument's __str__ method).
Sure, but int being core or not is totally irrelevant to whether or not the language attempts to convert things for you.
If int was auto coerced to strong that would be one thing, but in weak languages, everything is coerced to everything when it might make a modicum of sense, even in user defined types.
Literals become strs, objects become strs, strs become ints, arrays and objects can be combined Willy nilly leading to unintuitive results.
And since your object inherits from one of those things, you are stuck with that too. The language forces you into weakness.
Yeah okay you can jump through hoops to make your api kind of weakly typed in Java or python, but well it probably won't work in general, because the attempts to coerce will probably fail because the language doesn't know how to understand them, because the two types are incompatible. And unfortunately for your argument, not every type can be a library. Object has to be provided by the language itself.
So the question becomes, can you freely coerce between the base object type and another without loss of information. If yes, weak. If not, strong. Python: strong. Js: weak.
> Object.prototype.valueOf = function() { throw "I will not be coerced!" }
> 4 + {}
Thrown: I will not be coerced!
This doesn't seem fundamentally different from overriding getattr. What you've got in Js is basically a handful of pre-defined functions with some syntactic sugar, which are bone-headedly written to go out of their way to not report problems. But this really isn't any different than higher level libraries like nodemailer, whose attempts to be smart have been the source of vulnerabilities before:
>This doesn't seem fundamentally different from overriding getattr.
Right, but you can't override Object.__getattr__ in python. That's basically where the difference lies.
In python, `object`s don't naturally coerce, and you cannot force them to coerce. In js they do and you can.
Of course in any language you can write a badly behaved object that implements an interface that it really shouldn't, or in a way that is unexpected, but that isn't weak typing.
>Also, fwiw, Python has some questionable decisions in the basic "libraries" too:
Right, but even this isn't weak typing. It might be an interface you disagree with, but its very explicitly written to work this way. Compare that, again, to a weakly typed language like JS where you can multiply a string by anything, and you'll get NaN instead of a type error. In python on the other hand, `4.3 * '5'` gets you a type error, because you can't have a 4 and 3 tenths character string.
Basically, the difference between what python does and what js does is that python says
`* ` is an operator implemented on a left hand and right hand object. So for `l * r`, I first attempt to see if the left hand object knows how to multiply itself by the right hand object (`l.__mul__(r)`), and if that works, great. Otherwise, I check if the right hand can multiply itself by the left (`r.__rmul__(l)`), and if that works, great. Otherwise, these don't match, so throw an exception. This is strong typing, where every object decides, for itself, which other objects it is compatible with. You can certainly write an object which is compatible with anything, but up to you. You get to decide which objects you are compatible with, you're not forced into an all or nothing situation.
JS on the other hand says `* ` is an operator defined over things of type number, so to make `* ` work, I will implicitly convert the left and right hand sides to numbers (by repeatedly calling `valueOf`, and then multiply whatever I get from that. The language decides what you are compatible with, and how. You can't just be compatible with numeric types, if you want that, you're gonna be compatible with arrays and objects too, and there's nothing you can do about it.
The difference, to put it simply more simply, is that python (and strongly typed languages in general) doesn't have valueOf. You're confusing strong typing + operator overloading with weak typing, and those aren't, at all, the same.
The difference becomes clear when you take your example (with the no-coercion object) and try to add an array and an int. The Object's error gets thrown. In python, it would be possible to have a list's __add__ handle an int by appending it. But by extending for another list. In JS, that's impossible. That's because what python and Js are doing are different. Python defers to each type for how it wants to handle other types, but allows them to say "nope, this isn't valid". Js doesn't do that, either you are incompatible with anything ever, or you are compatible in ways neither you nor the other object you are working with can control.
The key thing I'm arguing is that + * / etc. aren't special, beside some very superficial syntactic support. Your description of python's `` basically boils down to:
Besides the syntax (and, again, the performance implications of the implementation), there really is nothing special about '+', '', '/' etc. They're just functions. And the implemenetation above is no more or less a design decision that the implementation of int.__mul__, which does the the aformentioned thing with strings. They very much could have implemented `multiply` as:
def multiply(l, r):
while not isinstance(l, [dict, list, int, float, str, bytes, ...]):
l = l.value_of()
while not isinstance(r, ...):
r = r.value_of()
if type(l) is dict:
l = 'I love buggy software'
...
Thankfully the designers of the python builtins had a bit more taste than that. But, ultimately, just like any stand-alone function in a library, if you don't like its behavior, you can't just override a method on your own classes to make it operate differently on them, unless the function specifically makes use of that method. Your only real option is to call a different function.
And unfortunately, the design decisions made for those operators don't extend to anything else written in the language. By default, if I define that I intend to only make sense to call on with an argument of requests.Session, and somebody (possibly me) passes it an etree.xml.ElementTree.Element, Python will happily chug along, with no way of knowing that this makes no god damn sense, until something goes wrong who knows where. And there is no declarative way for me to tell it otherwise; I have the same set of options available to me as in javascript.
I think it's at least coherent to claim that the built-in syntax makes these operators more than just functions, but... meh? It seems like a fairly trivial difference at that point. Even if you grant that these are really part of the language in a non-trivial way, it feels rather like saying that Go has generics because of the built-in slices, maps channels etc, or early Java, because of arrays. It's just not the same thing as a mechanism that actually extends to the rest of language in a meaningful way. If "strong typing" is to mean something about the language, it should it should apply to more than a handful of built-in operators. Python just doesn't have a mechanism that could be considered language-level support for any kind of typing (again, ignoring mypy and related stuff).
> And unfortunately, the design decisions made for those operators don't extend to anything else written in the language.
Yes, it applies to a number of built in functions and other operators beyond the mathematical ones.
And there's no reason they can't to even non-built-ins. When you implement a method or function, you can adopt a similar special-method-based protocol for it's arguments, and define the appropriate special methods (monkey patching existing classes, if necessary) for the acceptable types.
> If "strong typing" is to mean something about the language, it should it should apply to more than a handful of built-in operators.
I suppose there is a difference between a language with a weakly typed core (where you cannot avoid the risks of weak typing without isolating the core), one with a strongly typed core that doesn't force user code to be strongly typed (where Python sits), and one that forced strong typing (which probably had to be static, as well).
> I suppose there is a difference between a language with a weakly typed core (where you cannot avoid the risks of weak typing without isolating the core), one with a strongly typed core that doesn't force user code to be strongly typed (where Python sits), and one that forced strong typing (which probably had to be static, as well).
Yeah, fwiw I do think that the distinction being made with javascript vs. python is meaningful, it just isn't really about the language per se, but rather the design of "built-in" functions, which often doen't need to be built in as far as their semantics are concerned.
In my comment to the sibling, reiterate the reference to julia/racket -- you could have a language feature that does dynamic checks at the call site. IIRC, Haskell's GHC has a flag to basically compile down any time errors to exceptions (I don't know that anybody uses it though).
This doesn't look that different, but it absolutely is hugely different than the example you provide. Consider two types, `int` and `MyCustomType`. `int` has no concept of `MyCustomType`, given that I just came up with `MyCustomType`, and `int` has been a part of the standard library for over a decade.
If multiply were implemented as you suggest, we could do `MyCustomType * int` and it would succeed, but `int * MyCustomType` would fail. This again, because `MyCustomType` knows how to deal with an `int`, but the reverse isn't true, so if you only rely on the lhs implementation, you get weirdness.
As a language designer you have a few options for handling the `int * MyCustomType` case.
1. You have the language semantics say "when your type is multiplied by, we implicitly convert it to something compatible with multiplication, first by calling toString, then by taking the length of the resulting string. We also do the same to the rhs to make sure it can multiply. In other words, `multiply` is
This is weak typing. Note that while `* ` will always work (because everything gets a toString method (or valuOf)), the library writer has very little control over what happens.
2. You defer to each object. Each object says "this is what I can do". If either object believes itself to be compatible with the other object, great! If not, you raise some sort of error. This can either be done via capabilities/interfaces (which gets you duck typing), or via strict inheritance/class names (which python does in the standard library perhaps more than it should).
Multiply in python can't work the way you suggest because multiply is both more powerful and less broken than your suggestion. What you suggest cannot handle the example of MyCustomType unless multiply already understands how to deal with something that emulates what I want MyCustomType to do.
If, for example, multiply were implemented the way you suggest, something like
def multiply(l, r):
if isinstance(l, int) and isinstance(r, Iterable):
return [x for x in r for y in range(l)]
that works well and good, until I define a type `ndarray`, which is absolutely an iterable, but for which I actually want multiply to broadcast. The language doesn't know that, so with weak typing I end up getting out a much longer list. But with strong typing, I can control how things work.
So to be very clear, I think if you believe that `multiply` could have been implemented the way you believe, you have a core misunderstanding about how python works, because python absolutely could not work the way you describe.
>And unfortunately, the design decisions made for those operators don't extend to anything else written in the language. By default, if I define that I intend to only make sense to call on with an argument of requests.Session, and somebody (possibly me) passes it an etree.xml.ElementTree.Element, Python will happily chug along, with no way of knowing that this makes no god damn sense, until something goes wrong who knows where.
Right, and as strong typing suggests as soon as you attempt to treat an Element like a Session, you'll get an error (either type or Attribute, depending). The language won't attempt to make things work, or try to hold over for a while by for example make attribute access return `undefined` instead of blowing up as soon as something goes wrong.
The difference is that strong typing makes the error happen as soon as you misuse an object (but not before, because that's not possible). Weak typing delays that as long as possible.
Strong typing works the same way with any function. `len` is a good example. The basis of strong vs. weak typing is essentially that the language doesn't allow things that don't intend to comply with (implicit or explicit) interfaces comply with them in unexpected ways. You can always make something that intends to comply with an interface badly. But if I make something, in a strong language, you can't make it behave in ways that it "shouldn't". Another way of putting this might be simply that weak(er) languages have Object implement a number of interfaces (such as Comparable, Number, String, Iterable, which are all implemented, badly, by everything in js), whereas stronger languages do not (in python, object implements only String and Hashable). Further, stronger languages have more interfaces. For example, python separates "number" from "multipliable" in a way that JS does not.
But "strong" typing is totally and completely independent of "static" typing. Strong vs. weak is totally and completely independent of static vs. dynamic. Many people consider C to be a statically typed, but weakly typed language, because you can do things like cast a string to a struct willy nilly. You can't do that in python or Java. And note that what this means is that you can take an existing object and cast it to be something totally unlike what it should be. I can give you "Hello world" and you can pass that to a function that expects an struct{int, int, int}[4]. Something will happen. Similarly, in js you can pass a string into a function that expects an int, and something will happen. You try that in Python and you'll get a type error. You try that in Java, even if you do bad evil things like cast everything up to object so that it compiles, and you'll get runtime errors.
> This doesn't look that different, but it absolutely is hugely different than the example you provide.
The salient point about the example I provided was that it's implemented in Python -- I appreciate the correction regarding the actual semantics, and I agree that's a somewhat better design, but I also think the difference is irrelevant to the point I as making: that these operators aren't magic beyond their syntax.
> The difference is that strong typing makes the error happen as soon as you misuse an object (but not before, because that's not possible). Weak typing delays that as long as possible.
Sure, but my complaint is that if the contract for a function says it only accepts a Session, then passing it an Element is in and of itself an attempt to use an Element like a Session. This is the key point -- Python has absolutely no knowledge or understanding of the intended type of the function's arguments, and so as you say, it is impossible for it to know to signal an error here. For strong typing to be a language feature there would have to be some way of capturing that sort of thing. Instead, we have specific methods like int.__mul__ etc. in classes that ship with python that check their arguments and raise exceptions. That's good library design, but it's not a property of the language itself.
Note that catching the error at the call site is not as ambitious as catching it at compile time -- see the references I made in earlier comments to racket and julia, which can do some level of dynamic checking for user-defined functions/types.
The semantic difference between your multiply and mine is vital. Yours can only get generic through coercion (weak types), mine can be generic without coercion (strong types).
What you're describing in Julia is more static types, not stronger types.
The more static a language, the earlier it will raise a type error. The weaker a language, the easier it is to violate an interface with no error at all.
Catching the type error earlier is being static. Having a type error at all is being strong. That's the difference.
Python had design decisions, like no undefined, explicit instead of implicit varags, operator overloading via interface, not inheritance, etc. Which make the language more likely to have typeerrors instead if allowing g the successful misuse of types.
You're trying to make strong typing another flavor of static typing, and it's a totally independent concept.
When you talk about "Strong types," what actually constitutes a type in this terminology?
Re: the semantic difference, my point is that whatever the semantics of this multiply are, it's not really "part of the language" in any deep sense -- it is just another function. Given that, the semantics of multiply is entirely irrelevant to questions about the semantics of the language.
Re: "trying to makes strong typing another flavor of static typing" I absolutely am not. The "static" in static typing very much means "before the entire program is run" not just a vague "early."
>Re: the semantic difference, my point is that whatever the semantics of this multiply are, it's not really "part of the language" in any deep sense -- it is just another function. Given that, the semantics of multiply is entirely irrelevant to questions about the semantics of the language.
Well but that's only half true. That's like saying that the semantics of `int` aren't relevant to JS because its just an object and you can implement it however you want. 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.
>When you talk about "Strong types," what actually constitutes a type in this terminology?
The types that can be strong or not are exactly the same types that can be static or not. They're just two independent dimensions upon which a language can vary its semantics.
>I absolutely am not. The "static" in static typing very much means "before the entire program is run" not just a vague "early."
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.
I really think that thinking in terms of interfaces is the easiest way to conceptualize this. Object in js implements a whole host of interfaces that everything then inherits from. The same is untrue in python. This leads to implicit coercion according to those (wrongly implemented interfaces).
Much like you can consider dynamic typing to simply by static typing where every object is of type Any, you can consider weak typing in the limit to be strong typing where every object implements every interface (not, claims to, but actually does, for some value of actually). So these arguments about "well the implementations don't matter" don't hold water. The implementations (specifically of Object) are the difference.
We seem to have hit some upper limit on the nesting depth in a thread on hackernews; there's no reply button for me on your latest comment[0] so I'm replying here.
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.
>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."
Sure, but all you've really done is defined a new type system distinct from that of JS or python [1]. I'll agree your type system is different from JS's. Again, the fact that you can write a new language, with different semantics, within JS is not and should not be surprising.
>Run-time checking is a requirement to avoid coercion (assuming you also don't have static checking)
Ish. Duck typing avoids both of these, unless you consider the extreme that the existence or lack of any specific method defines an interface, and every object implements some subset of these interfaces, but that's not a particularly useful abstraction.
That is to say `try: lhs.method(rhs.value), except: TypeError` avoids both coercion and type checking.
>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.
Huh? I'm going to challenge you to give an example, because I don't think you'll be able to.
>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.
To be clear, (a) is a valid interpretation, but only if you consider the base `Object` type to be a library (or more broadly, the base type). This isn't a realistic assumption. As soon as you write you're own object, you've created a new language with similar syntax but distinct semantics. Basically yes, its absolutely possible to implement a strongly typed language within a weakly typed language. But its also possible to implement a statically typed language within a dynamically typed one. That doesn't mean that the outer language isn't weakly typed. It is. You're just able to implement a stricter DSL in it, which again.
As a similar example, you can write Java code where everything is declared as Object. This doesn't make Java dynamically typed. It does however make your bit of code not statically checked. Similarly, you can write stuff within JS that is, within a fence, strongly typed, ish. You have to stop using a lot of the language's built in syntax and methods, so are you really writing JS any more? The syntax is similar, but the semantics are not.
But there are a lot of languages with similar (or even identical) syntax and different semantics. Python2 and Python3 are a good example.
> But its also possible to implement a statically typed language within a dynamically typed one.
Not as a simple set of library functions that you import. You need to actually write an offline tool, because if the checking is done by library code then it's already too late -- the program is already running. Contrast the implementation above which is just a function, and still composes with the rest of the language without any assistance, and doesn't do anything that's terribly out of the ordinary for a function to do. Static typing is a language property, strong typing is a library property.
> As soon as you write you're own object, you've created a new language with similar syntax but distinct semantics
This seems like a uselessly broad criterion for what constitutes a language. If you're just using the same everyday mechanisms you do writing any program, I don't think you can claim you're using a different language, without diluting the meaning of the term beyond all utility.
> I'm going to challenge you to give an example, because I don't think you'll be able to.
The trivial example is keeping units straight. Merely having an attribute __mul__ does not not adequately capture the interface of multiplication in the presence of units. Unless I specifically write the kind of boilerplate with a bunch of checks like my JavaScript example, it will just silently do the wrong thing.
In some sense Python's object does satisfy every interface, with a default implementation of throw AttributeError/TypeError. This may seem trivial, but it is important in that overriding one of these isn't different from overriding valeOf, in order to get a different implementation for your object (one that throws).
>Not as a simple set of library functions that you import. You need to actually write an offline tool, because if the checking is done by library code then it's already too late -- the program is already running. Contrast the implementation above which is just a function, and still composes with the rest of the language without any assistance, and doesn't do anything that's terribly out of the ordinary for a function to do. Static typing is a language property, strong typing is a library property.
You're, probably unintentionally, setting your requirements a bit differently. As an example, Tensorflow is a statically checked DSL within python. You create a tensorflow graph, and (unless you're using the relatively recent dynamic graph features), that graph is statically checked for validity. Python is the virtual machine in which Tensorflow is written, but within the dynamic python VM, Tensorflow is a statically checked language. Basically, what happens is
1. python runs, parses the tensorflow graph and creates an AST
2. the AST is statically checked by tensorflow. This happens before tensorflow really does anything else. Errors like tensor shape mismatches are raised here, instead of the first time you actually attempt to multiply tensors of mismatched shape. It doesn't use runtime information about the types, it isn't dynamic checking. Its very much static checking, but static checking that happens after python runs, but before tensorflow runs.
3. You execute the now checked graph. Errors can still appear here, but you have avoided some via static analysis.
Similarly, you can create a strong language in the VM of a weak language. That doesn't suddenly make the weak language strong.
Neither static typing nor strong typing is solely a property of the language or ecosystem. It comes as a combination of both. I personally like to keep this distinction clean by calling DSLs that break core properties of the parent language different languages, because that's what they are.
>If you're just using the same everyday mechanisms you do writing any program, I don't think you can claim you're using a different language, without diluting the meaning of the term beyond all utility.
When you define a new object system with distinct mechanics, you aren't using the same mechanisms you do to write any program. They're no longer compatible with the rest of the language.
>The trivial example is keeping units straight. Merely having an attribute __mul__ does not not adequately capture the interface of multiplication in the presence of units. Unless I specifically write the kind of boilerplate with a bunch of checks like my JavaScript example, it will just silently do the wrong thing.
This has nothing to do with strong or static typing. Its yet a third axis on which a language can vary. But to be clear, if you define things correctly, managing units like that doesn't actually require much boilerplate. You create a Unit interface and 7 BaseUnits impls for each of the SI base units. Then within the bounds of Unit * Unit (or Unit * number) multiplication, the interface does indeed correctly capture multiplication in the presence of units. There's never any need to do the casework like you're describing, and the more you go down this tangent the more it seems like you have misunderstandings about core Object Oriented principles.
Meter = Unit(['m'], 1)
...
Kilometer = Meter * 1000
KiloNewton = Kilogram * Kilometer / Second ** 2
f = KiloNewton(12)
f.unit # kg * m / s ** 2
f.magnitude # 1200
The only "cases" are handling Units, which have a magnitude and a unit, and non-unit numbers, which have only a magnitude. As long as the base Unit class knows how to do that, you have one bit of casework, which has two options:
class Unit:
def __mul__(self, other):
if isinstance(other, Numeric):
return super(self.units, self.magnitude * other)
if other.units:
return super(self.units + other.units, self.magnitude * other.magnitude)
raise TypeError
I'm simplifying self.units + other.units, but that's mostly irrelevant.
The point here is that you're defining a new interface, Unit, which is able to do what it wants. But in a JS world, if I tried to multiply my Unit by 12, I'd lose the unit.
>In some sense Python's object does satisfy every interface, with a default implementation of throw AttributeError/TypeError. This may seem trivial, but it is important in that overriding one of these isn't different from overriding valeOf, in order to get a different implementation for your object (one that throws).
Well, no. If you're claiming that the action of saying "I explicitly do not satisfy this interface" is, in a sense, a way of satisfying an interface, we've reached the point where you're not interested in a dialogue and are instead only interested in arguing a wrongheaded perspective, and at this point a perspective that it seems you clearly recognize is wrongheaded, but wish to defend anyway.
The difference between overriding a method and overriding valueOf is that with a method, I control which specific interfaces I implement. With valueOf, its all or nothing. I cannot pick and choose which interfaces to implement. I've said this or a variant of it in nearly every post so far, and you've yet to respond to this point.
So I'll try it once more: In a strong language, each object controls which specific interfaces it implements. In a weak language, each object does not. Thus, in python, I choose individually if I want to be iterable or multipliable or a container. In JS, I do not get to decide those things. My object is a container, my object is a number (which means that it can be added and multiplied and such in ways I don't intend), and my object is a string all at once.
While the ability to overload specific operators is a symptom of Python being stronger (each operator is bound to an interface, instead of all of the operators being bound to the number interface which everything implicitly implements), it is a symptom, and not the cause.
> at this point a perspective that it seems you clearly recognize is wrongheaded, but wish to defend anyway.
I don't appreciate the accusation. At this point your own position is baffling to me as well, but I'm not going to make attacks; if it comes to that better to just walk away (I did this for a few days, because I was getting frustrated).
> Well, no. If you're claiming that the action of saying "I explicitly do not satisfy this interface" is, in a sense, a way of satisfying an interface
What I'm saying is that the built-in 'object' takes just this approach -- so if it does count as a way of satisfying an interface then (in both Python and JavaScript) every value satisfies every interface. If it doesn't, then user defined classes doing it isn't different from classes that ship with Python. I agree it's kindof weird to call that satisfying an interface; my claim is only that the explicit opting-out that `object` doesn't isn't any different than explicit opting-out in user code.
> The difference between overriding a method and overriding valueOf is that with a method, I control which specific interfaces I implement. With valueOf, its all or nothing. I cannot pick and choose which interfaces to implement. I've said this or a variant of it in nearly every post so far, and you've yet to respond to this point.
I similarly feel like I've been repeating myself with my response, which is not being heard:
This only matters if you're willing to treat the functions with special syntax (, / etc) as privileged. Otherwise `valueOf` is just another interface (representing coercion), and while it is definitely more poorly designed than the Python approach of having separate interfaces for each of ``, `/` etc, this only has any implications for a property of the language if you're willing to treat those interfaces (both `valueOf`/coercion and ``/multiplication, '+'/addition etc) as special. Some arbitrary function I write (in either language) doesn't (necessarily) use these interfaces. There are a fixed set of functions, imported by default, with special syntax, that use these interfaces (and of course code that calls those functions).
I think it absolutely is* defensible to claim the fact that these operations have specialized syntax makes them special in a way that's really "part of" the language, and if you take that as given, I think the rest of your argument is valid. But if you take those functions to be not really special, then behavior that only pertains to them doesn't inform questions about the language proper, just the libraries.
You've said that you agree the operator syntax is unimportant, but I'm having a hard time groking how else this is relevant.
I should also reiterate that this is a really nitty-gritty technical point I'm making; the ecosystem and libraries are the best thing about Python, so saying "it's just a library" should not be construed as "it's unimportant."
Re: Tensorflow, the resulting Tensorflow program/AST is statically typed, but it isn't a Python program, by any means. You can't put a Python while or for loop inside a Tensorflow program, call a python function, or basically do anything that's not encoded in the AST. You can use those when constructing the AST, but that's different. (Do correct me if I've misunderstood the design of Tensorflow). To use your own words
> [Tensorflow programs a]re no longer compatible with the rest of the language.
But this just isn't true of my examples where I forgo using javascript's built-in operators in favor of my own `mul`, `add` etc. functions. In the latter case, it's still JavaScript -- it can still be used with other JavaScript code in the full generality of any other functions I write.
I have some things to say about the Unit example as well, but I'm running late, so am going to stop here.
There is no such thing as "strong typing", so no language has that feature. Perhaps you mean it's type safe in some dynamic sense, but strong and weak are not qualifiers that have any kind of rigourous definition in type systems.
Usually what is meant by weak and strong typing is that there are a lot or few implicit conversions between types which undermine the typesystem.
Imagine an esoteric language which automatically converts any type into any other type. For the conversion the language will pick the default value for that type or if there is no default value it will pick null. How useful would the typesystem be in this case?
This statement wouldn't be helpful even if wasn't incorrect. You clearly don't like Python but accusing other people of malice for not sharing your personal taste is not going to accomplish whatever you believe it will.
This is about being open and honest to stakeholders about Pythons strengths and weaknesses. Sure, I won't ever get the terminology used in your community to change, but at least you will know that there are people who find it rather uncomfortable and disingenuous.
Disingenuous is lying about people’s motives to make your opinion seem more like a truth.
It’s okay not to understand Python or typing as well as you think but that should be a chance to learn rather than your cue to accuse people of malice for giving the world free software.
Go read the link I posted by Professor Robert Harper before accusing me or anyone else of not understanding type theory.
Whether you like it or not, technology does compete for mindshare. Haskell and OCaml are also free software.
Prof. Harper didn’t mention Python. He definitely didn’t accuse people of dishonest advocacy. Stop using his name to support your comments.
You could try to do something positive like talk about how Haskell does something neat which Python doesn’t support. I can think of several examples which are far more interesting than arguing over whether your personal definition of “strong typing” is universally shared.
You really aren’t willing to avoid personal attacks, are you? It’s not doing anything productive.
The main thing I took away from that post was his belief that the static / dynamic divide was a false dichotomy. I do agree, however, that I was incorrectly remembering it too charitably: he and you are both willing to distract by accusing other people of acting in bad faith without bringing evidence to support such a strong accusation.
>This is better than a segfault, but it's still a crash.
Strong vs weak types is orthogonal to whether there's a crash or not. The crash is because the types are dynamic (vs static) which is a different axis than strong/weak.
There is no static/dynamic axis for types. A dynamic "type" as you call it, is really a runtime tag. All languages are static languages. "Dynamic" and "dynamically typed" are IMHO, marketing terms. There's nothing that "dynamic" Python can do that I cannot do in a typed language using runtime tags, string keys and variants.
Static/dynamic and weak/strong typing is about the language semamtics and the runtime. You can't reduce the description the way you do, because for every language we end up with: there's nothing I can do in X that I can't do in untyped ASM, therefore everything is untyped. Or go down to either lambda calculus or logic gates. Either works.
What the language provides matters. For code like "1"+1 you have 4 main options:
- strong static - doesn't compile/start due to type error
- weak static - returns "11" and expression is known to result in a string at compile time
- strong dynamic - crash/exception at runtime
- weak dynamic - returns "11" at runtime, but the type is attached to the value, not the original expression
Your classification is based on a trivial expression involving primitive types. What would most Python code do if one tried to append a SQL query to a filepath? Since both will likely be represented by strings, it will return garbage, "weak" in your definition. Whether it crashes or not in any particular implementation is not a feature of the language.
>Your classification is based on a trivial expression involving primitive types.
It's not "his classification", just his example.
The classification is established terminology in Computer Science.
>Since both will likely be represented by strings, it will return garbage, "weak" in your definition.
It's not his definition, and it's not weak in this case. Weak is not about getting garbage or not, it's about the semantics of a behavior. It could be that the result of the weak behavior is exactly what one wanted (e.g. to add a string to an SQL statement -- I might be adding together SQL statements and text about them to produce a book on SQL with my script).
In any case, the SQL statement is a string in Python, not its own type, so the result will not be weakly checked, it will be strongly checked as what it is (a string) -- Python will do what it should.
That looks like a random list of papers. Which one are you claiming defines or even discusses "strong" or "weak" typing? AFAIK those terms have no real definition in type theory.
You said academics don't use the terms - you were given examples that do so.
None of them need to define it - if you're interested, you can do that research.
Those terms don't have definitions in type theory and that's ok. Systems defined in the type theory deal with very explicit, theoretical analysis that usually maps well only to static-strong implementations.
Yes goal posts have been moved, but not by me. Some of those papers may informally use these terms for static types, but it is certainly not to describe characteristics of dynamic languages. AFAIK, there is no classification in computer science that describes dynamic languages in this way. This has come from the dynamic language communities. None of those papers demonstrate that it is acceptable to call Python "strongly typed", in fact quite the opposite, as the technology discussed is not available in Python. Well, not yet anyway...
Perhaps filepath was a bad example for Python, as it has assertions. Zenhack actually made the same point and explained it better than me. Your runtime assertions (what you call "strong dynamic types") are a feature of the libraries you are using and a few built-in primitives. Python does not really have any facilities to talk about types. Using strings as in my example, would be "weak typing" in your terminology, but as you said, this is possible in any language, even Haskell. Therefore I fail to see how strong/weak is a language property. We are therefore left with static and "dynamic types", which Robert Harper argues should be statically typed and statically unityped.
"Generally, a strongly typed language has stricter typing rules at compile time, which imply that errors and exceptions are more likely to happen during compilation"
Even Java, which introduced generic types long after the language itself had become standardized and popular, cannot keep generic type information after compilation due to backwards compatibility.
Don't quote me on it, but I believe C# doesn't have this problem.
There are libraries that do magic to do this. Annotations are accessible at runtime, so you can do things like add an implicit import hook that decorates every function or class with a decorator that validates the annotations.