>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.