I do think my example code demonstrates why Go is easier to reason at scale than Python. Python is conflating assignment and type declaration. Go has = and :=, so it's crystal clear what's going on.
I like the way Rust makes clear what are the different possibilities:
In Rust we use a macro to handle variadic arguments, but if it was just a case of supporting different types, not of supporting an arbitrary number of arguments we would've had three options for the type signature:
1. Monomorphic duck typing: `fn print(arg: &impl Debug)`. Here the compiler simply generates multiple print functions, one for every type the function gets called on. The exact concrete type is known at compile time.
2. Polymorphic dynamic dispatch (with dynamic sizing) duck typing: `fn print(arg: Box<dyn<Debug>>)`. Here the compiler generates a vtable and allocates on the heap. Only a type approximation is known at compile time, not the exact concrete type, but it still counts as static typing.
3. Dynamic typing: `fn print(arg: Box<dyn<Any>>)`. Note `Any` in stead of `Debug`. Full dynamic typing with the type completely unknown at compile time. Juck! But occasionally useful for prototyping or for FFI with dynamically typed languages.
Additionally, you could perfectly well have constants, or require variables to have a fixed type, in a dynamic language. You would just pay a cost at runtime to check the type on assignment.
I would argue that you pay the cost at runtime but you also pay a cognitive overhead cost while writing in a dynamically typed language. Refactoring in particular is a lot more difficult.
I do think my example code demonstrates why Go is easier to reason at scale than Python. Python is conflating assignment and type declaration. Go has = and :=, so it's crystal clear what's going on.