A language with keyword arguments helps against this. Crystal has a great compromise IMO where you can call the same arguments positionally and as keywords based on the argument name.
def foo(x=None, y=None):
...
foo(x=1, y=2)
foo(1, y=2)
foo(1, 2) def add(*, l, r):
return l + r
add(l=1, r=2)
>>> 3
add(1, r=2)
>>> [...]
>>> TypeError: add() takes 0 positional arguments but 1 positional argument (and 1 keyword-only argument) were given
I believe 3.8 is adding / (I think) to do the same but for positional arguments, i.e., arguments you cannot give by keyword.edit: forgot that flake8 doesn't check for mutable arguments by default – need to add https://github.com/PyCQA/flake8-bugbear
CopyFile(from: loaded_file, to: report_file)
Now, somewhere else off in the code someone does something like: // loaded_file is now the report - so no worries.
loaded_file = report_file
And the compiler then happily propagates that. At no time does it throw an error saying something which would be quite informative such as "Cannot assign report_file (type: outputFile) to loaded_file (type: sourceFile).Which is really what we want to have happen because - at the very least it forces the developer to go inspect the usage sites to figure out if what they're doing makes sense.
Java has InputStream/OutputStream. C++ has istream/ostream. If you use a statically typed language that doesn't have this distinction, blame that specific language's library design. This is orthogonal to keywords.