
Be wary of functions which take several parameters of the same type - ngaut
https://dave.cheney.net/2019/09/24/be-wary-of-functions-which-take-several-parameters-of-the-same-type
======
thelazydogsback
KW's:

    
    
      CopyFile(from: 'foo', to: 'bar')
    

or JS:

    
    
      CopyFile({from: 'foo', to: 'bar'})
    

or as types:

    
    
      CopyFile(Source('foo'), Dest('bar'))
    

or as fluent:

    
    
      Copy.from('foo').to('bar')

~~~
jjoonathan
or as Objective C:

    
    
        [[NSFileManager defaultManager] copyItemAtPath:src toPath:target error:&error];
    

Inspired by smalltalk, of course. ObjC's verbosity is legendary, and I'm not
its hugest fan, but there's a key innovation here that I _am_ a huge fan of:
the language forces _other people_ to name their arguments, in much the same
way that python forces _other people_ to indent their code, even if they would
otherwise default to making a hot mess of things.

~~~
Scapeghost
That verbosity isn't even a big deal as you write once, but read many times.

One of the first things about Swift that I fell in love with was the
difference between things like DrawRect(10, 20, 30, 40) in languages like C#,
and DrawRect(at: origin, width: 30, height: 40) in Swift.

~~~
anonytrary
Named function parameters are nice, but you can accomplish something similar
with only two more characters in some languages:

    
    
          DrawRect({at: origin, width: 30, height: 40})
      vs. DrawRect(at: origin, width: 30, height: 40)
    

I can understand why this is considered a nice-to-have. The verbosity didn't
really change.

~~~
wruza
Two more characters and one more allocation per each LoC.

~~~
a13n
Most of us are just writing crud apps anyway so one more allocation is
extremely affordable

~~~
lonelappde
Maybe but this highly dynamic programming style is why Macs have always been
so much slower than Windows on equivalent hardware.

~~~
Scapeghost
Where did that come from? Apart from games that were obviously optimized for
Windows first then ported to macOS, has that claim been measured? Because
macOS has always certainly FELT faster than Windows.

------
nostrademons
IntelliJ labels the parameter at the call site with its parameter name, if its
value isn't a local variable that already has that name. On your display it
looks like CopyFile(from: "foo", to: "bar"), with the from and to labels in an
unobtrusive font, but in the source file it's just CopyFile("foo", "bar").

~~~
danfang
You don't want it unlabeled in the source code though - a code reviewer will
totally miss a swapping of parameters. It's best when fully supported and
explicit in the language.

~~~
sk5t
Yup! It's nice and all that IntelliJ ghosts the parameter names in there, but
that does nothing for external review, static analysis, etc.

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

~~~
XorNot
The problem with keywords is the compiler can know nothing of intent with them
- consider:

    
    
      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.

~~~
tom_mellior
> 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).

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.

~~~
XorNot
Is it? Until you open the file as a stream, the data to do so is kicking
around your code as a string type with compiler checks on usage. You're making
my point: a compile time check is much more helpful.

~~~
tom_mellior
From your example it looked to me like the types in question were "file"
types. You're right that "file _name_ " types are not checked by these
languages.

------
kstenerud
When it's impossible to avoid ambiguity in the parameters, you can just chose
a function name that makes the ordering explicit:

    
    
        copy_from_to(file from, file to)

~~~
raxxorrax
I like this more than being forced to name parameters since that would be too
verbose in many applications. But there is a danger that the label of a type
becomes less generic.

Vector3(...)

VectorXYZ(...)

The second variant could be weird depending on application.

Still, I see some people argue that positional arguments should be forbidden
in newer languages but I would disagree and my main argument would indeed be
laziness.

------
rzwitserloot
The solution (to me anyway) requires BOTH that parameters must be named, as
well as cultivating a culture of creating types aggressively (and, together
with that, a language that makes it easy to do this, to avoid the onus of
making types leading API developers to make the wrong choice).

Instead of doing:

    
    
        downloadFile(String url, String filePath)
    

which runs into the ordering issue raising in the article, if it was:

    
    
        downloadFile(Url from, FilePath to)
    

you no longer have the issue.

But, having some way to name the parameters, which can take _many_ forms works
just as well. It would help a lot if the language can enforce that you must do
this:

named params:

    
    
        downloadFile(from = "https://something/foo.txt", to = "/some/path")
    

objC style where the function name is split up:

    
    
        downloadFileFrom: "https://something/foo.txt" to: "/some/path";
    

same as above, but with fluent-style intermediates:

    
    
        download().from("https://something/foo.txt").to("/some/path");
    

pass an object (Javascript):

    
    
        downloadFile({from: "https://something/foo.txt", to: "/some/path");
    

these all work just as well. And nevertheless, this:

    
    
        download(server, file)
    

is cleaner than all of the previous attempts, and allows passing these
concepts around helper methods and the like. So, _IF_ it makes sense to change
the types of the arguments so that you no longer have the same type for
different arguments, it seems preferable to me.

However, in the case of CopyFile, to take that to its logical conclusion,
you'd have a separate type to represent a 'from file' and a 'to file', but
that seems too much of a stretch: The notion of a file fundamentally doesn't
have a 'direction' concept built into it; that makes sense only for a copy or
move API. Making separate types to represent the tuple of [direction,
filepath] seems like a hacky way to introduce named parameters.

So, have both.

------
axaxs
There was a post here not long ago, by jerf iirc, called something like 'tiny
types in go.' At the time I disagreed with the premise, but I think it would
be a cleaner solution here. By aliasing two string types with obvious names,
it forces you to convert at calltime. Assuming you name source and dest as
types(ie type source string), you end up with a command like
copy(source(sourcefile), dest(destfile)). It's cleaner and has less
abstraction than what's in the article.

~~~
acjohnson55
There are also techniques like type tags and refinement types in some
programming languages, which serve a similar effect. It's really useful for
marking data as validated or as earmarked for a given purpose as it flows
through layers of a system.

------
hprotagonist
a way to avoid this in python that i quite like is to _require_ keyword
arguments:

    
    
      def foo(*, x, y):
          pass
      
      # you must do this
      foo(x=5, y=6)
    
      # you cannot do this
      foo(5,6)
    
      # and therefore you also may not
      foo(6,5)
    

This is a little autocratic but the other nice thing is that if you’re finding
it really painful to use it means you should probably refactor anyway.

~~~
XJ6
I prefer to let the IDE do this.

Developing C# in rider if you call `foo(a,b)` then it'll display as `foo(from:
a, to: b)` in the IDE.

~~~
throwaway744678
All Jetbrains IDE do this (AFAIK). One can even configure the cases when these
are displayed (ie. only show the hints when there is an ambiguity)

------
fennecfoxen
Many languages have some concept of "keyword arguments".

(Those that don't will need to pass in named tuples, or structs, or similar
data structures.)

------
jph
IMHO this concept is better solved by using semantics, such as types, keyword
parameters, interfaces, traits, etc:

    
    
        copy(source: ReadOnlyFile, destination: WriteOnlyFile)
    

These all help make the code more semantic, more testable, and more securable.

These can also improve more kinds of programming, such as a function that
truly needs multiple parameters of the same type (e.g. iterators that are not
commutative) or a function that benefits from security aspects (e.g. read-only
data).

------
agwa
Here's a variation of the example which I like a bit better:

    
    
      type CopyFile string
      
      func (src CopyFile) To(dest string) error {
           // copy file here
      }
    
      func main() {
           CopyFile("presentation.md").To("/tmp/backup")
      }

~~~
jonahx
The problem is, even though it reads smoothly, the names are no longer
accurate. The smoothness comes from reading "CopyFile" as a verb
(semantically), but "CopyFile" is actually a noun (syntactically).

    
    
        Source("presentation.md").CopyTo("/tmp/backup")
    

while scoring slightly fewer slickness points, wins overall imo because it
remains accurate.

------
thinkingkong
If we're going to use types to handle this then I'm a fan of the Go method of
Writer and Reader. That way, even if you have a method named copy, you cannot
really mix the two up. Writer restricts the operations; same as Reader. Having
method names imply direction or argument order is gonna be tricky once you add
a couple people to your project.

~~~
shhsshs
That breaks down when both src and dst implement io.ReadWriter. os.File for
example.

I had an adventure upgrading versions of the .NET Core Rabbit library: one of
the (boolean) parameters of the Consume function _used to be_ “noAck”, but it
got changed to “autoAck” (with inverted behavior). Even though the change was
well documented, I would have preferred that to be a breaking change at
compile time...

------
ppod
A nice thing about "everything is an object" languages is that
a.isGreaterThan(b) seems less ambigous than isGreaterThan(a,b). There are
natural langauges that are Verb-Subject-Object. It seems less common in
natural language to have neither argument be an "agent". Is the copula in the
natural language doing the work of the dot?

~~~
gowld
That doesn't scale past two arguments.

~~~
ben509
You can use fluent interfaces to get past two args:

    
    
        myObject.doThing(a).toAthing(b)
    

Though that requires a good amount of boilerplate on the part of the library
author.

~~~
ppod
Mary.give(apple).to(John)

Mary.give(John, apple)

------
jaequery
The solution provided by OP is what I try to avoid at all costs. It’s the
beginning of spaghetti code.

~~~
radiator
I agree, and I am not still sure if he is serious.

------
hinkley
I used to feel guilty for how much I use Google, developer.mozilla.org and
Stack Overflow, but after a series of languages and a herd of APIs, it's a
blur to remember which order is correct for which library, and how they deal
with inputs that are empty or undefined.

It's safer to look it up, peruse the complaints about how it does weird shit
with negative numbers or empty string, and then write my code standing on
those shoulders.

I spend enough time stepping through other people's code even when I do this
stuff. It's really not that interesting and certainly not fun.

~~~
taneq
Amen to that. And it goes double when you're hopping around different parts of
a multidisciplinary project and probably only using a given language or
framework once or twice a year for a couple of weeks. These days I find myself
looking things up just on principle, even when I'm reeeasonably sure I
remember how they work. It just saves time in the long run.

~~~
hinkley
I had a project with three implementations of merge (including lodash) and
they all worked differently. After I fixed a handful of other people’s bugs
due to using one and expecting the behavior of another, I felt a lot less
guilty.

------
acjohnson55
There was an experimental language I can't remember where arguments were only
named by their type by default, rather than having identifiers. This
implicitly discourages having multiple parameters of the same type. After I
fully processed the idea, I came to like it quite a bit.

------
jniedrauer
To be honest, I prefer the simple form. It's immediately obvious what it does,
and the cognitive load when working with it is slightly lower. That justifies
the extra second or so you need to view the function signature and make sure
your arguments are in the right order. "Clever" solutions to anticipated
problems that haven't happened yet frequently end up being tech debt down the
line.

------
molyss
While it's not a perfect example or solution, this illustrate a fairly common
issue with modern code. He also mentions too many parameters. Other commonly
frown upon constructs include unclear names (java "filter" or "compareTo"
easily come to mind), boolean parameters (One should use named enums instead).
Some add to the list out parameters (passing a to-be-modified list as a
parameter), functions returning their modified input parameter, even though
they certainly have their usefulness (like being able to handle memory
management outside of the callee). I'd personally like to add String
parameters to the list, but modern language make such restrictions often too
cumbersome.

I the given example, I'd love to see a language with optional named
parameters. For the function copy(String fromFileName, String toFileName),
instead of calling copy (file!, file2), one would have the possibility of
calling copy(from:file1, to:file2)

~~~
darkfire613
Your last point is something that I really like about the Swift language. All
parameters have names that are mandatory when calling the function, so in
Swift you would be required to call copyFile(from: file1, to: file2). [0]

0: [https://docs.swift.org/swift-
book/LanguageGuide/Functions.ht...](https://docs.swift.org/swift-
book/LanguageGuide/Functions.html#ID166)

------
rq1
In C++ one could use strong types (template phantom types with anonymous tags)
to distinguish between source and destination.

I use this technic to disambiguate all kind of types (source/destination
gpu/cpu pointers for instance) and correctly dispatch everything at compile
time.

The compiler becomes your best friend then. :)

~~~
archi42
That's a nice idea. Also, while I love C++, I'm really frustrated that there
are no native named arguments :(

However, after your comment, I decided to spend a few minutes on the topic,
and came up with these links:

[https://www.fluentcpp.com/2016/12/08/strong-types-for-
strong...](https://www.fluentcpp.com/2016/12/08/strong-types-for-strong-
interfaces/)

[https://www.fluentcpp.com/2018/12/14/named-arguments-
cpp/](https://www.fluentcpp.com/2018/12/14/named-arguments-cpp/)

I am now looking forward to refactor some of our code base to make use of
this. Awesome and thanks for the nudge!

------
arcticbull
IMO this is totally solved with named parameters. Nobody’s gonna mess up
CopyFile(from:a, to:b). Nobody.

------
biztos
I love Golang but I often get "copy" wrong because it's the opposite of UNIX:
copy(dst, src) -- what?!

[https://golang.org/pkg/builtin/#copy](https://golang.org/pkg/builtin/#copy)

~~~
andreygrehov
[https://www.geeksforgeeks.org/memcpy-in-
cc/](https://www.geeksforgeeks.org/memcpy-in-cc/)

------
mpweiher
With Polymorphic Identifiers:

    
    
       file:bar := file:foo.
    

Let functions compute return values, use assignment if you want to move data.

References:

[https://dl.acm.org/citation.cfm?id=2508169](https://dl.acm.org/citation.cfm?id=2508169)

[https://www.hpi.uni-
potsdam.de/hirschfeld/publications/media...](https://www.hpi.uni-
potsdam.de/hirschfeld/publications/media/WeiherHirschfeld_2013_PolymorphicIdentifiersUniformResourceAccessInObjectiveSmalltalk_AcmDL.pdf)

------
z3t4
Most of these have conventions. For copy, move, etc the convention is from
target, to destination. And an IDE will show you the parameter names. If you
still get it wrong, there is also no guarantee that you would assign the
correct type either. eg. mixing up foo and bar in {from: foo, to: bar} or
foo:From, bar:To Also if there is any speculation you should always read the
source code of the function you are calling, or read the documentation, and
try the function in a REPL to see how it behaves. There is a line where over-
verbosity will have the opposite effect eg. making the code harder/slower to
comprehend. So keep it clean and simple. If people get it wrong a lot, make
the function figure out the parameters so that it doesn't matter what order
you pass them in. And have the function throw a friendly human readable error
if you get the arguments wrong! JavaScript example:

    
    
        function copy(from, to, overwrite, cb) {
            if(typeof overwrite == "function" && cb == undefined) {
                cb = overwrite;
                overwrite = false;
            }
            if(from == undefined) throw new Error('First argument "from" not specified!');
            if(to == undefined) throw new Error('Second argument "to" not specified!');
            if(!exist(from)) throw new Error("from=" + from + " does not exist!");
            if(exist(to) && !overwrite) throw new Error("to=" + to + " already exist!");
            ...
        }

~~~
friend-monoid
* Not all users have IDEs with all that much help (vim, emacs users come to mind), and not all platforms have this ability (consider for example writing serverless functions in an online IDE), and sometimes you do code reviews from an online platform or from your phone, and sometimes you just kinda pull up a piece of code in notepad, and I think you should be able to tell that the code is obviously correct in all of those cases.

* In C, the convention is "move(destination, source)". See memcpy, snprintf, strftime, others, but not all functions follow this convention. PHP is known for having lots of functions in standard library with differing conventions.

* It's easier to tell that "move(from=X, to=Y)" is correct than "move(X, Y)" though?

* I agree with the over-verbosity, but your last point that "annotating your arguments makes your code over-verbose and harder/slower to comprehend; instead, put all these guards and magic in the definition of the function" \- isn't that also making your code harder/slower to comprehend, but this time you're putting the mindwork in understanding the function instead?

~~~
z3t4
An IDE is sometimes used as band-aid for bad design. I use an IDE for
convenience, but try to make my code not depend on it. It's said that the time
it takes to type the code is a very small part of software development. Yet
programmers are lazy and would write the short-hand if one is available. I
think it's best to learn one language, and one code-base well - if you want to
be productive. (or maybe become bored) There is a trend in software, that
things get dumbed down, and locked down. It shouldn't take 15 years to become
productive, but if you aim for one month or less, you would have to sacrifice
a lot.

------
tlb
I'm not a fan of the CopyFile solution suggested.

Often one parameter is distinguished by whether it could be an array. The Unix
cat utility takes multiple sources to a single destination, so analogously:

    
    
      bool cat_files_to_file(string dst, vector<string> srcs)
    

and it'll be obvious in the call site what's going on:

    
    
      cat_files_to_file("/tmp/backup", {"presentation.md"});
    

and your function can also concatenate files.

------
jdm2212
ErrorProne has a nice safeguard against this if you're writing Java code:
[https://github.com/google/error-
prone/blob/master/docs/bugpa...](https://github.com/google/error-
prone/blob/master/docs/bugpattern/ParameterName.md)

It detects if your function signature is

> foo(first, second)

and you call it with

> foo(second, first)

that is, it actually checks for variable names that are mismatched, with
comments like

> foo(/* first= * / second, /* second= * / first)

as an escape hatch.

------
jonahx
There's a lot of hate in the Go community for languages like Haskell, and a
lot of pride in Go for "keeping it simple" and "staying out of your way."

Yet if you watch Go conference videos or read Go blogs, you'll see quite a few
idioms like this one, which boil down to just wanting to use real types for
things, instead of layering semantic meaning onto strings or ints or nils, and
keeping that meaning in your head (or in the docs).

------
eccbits
Does anyone else feel this is... ridiculous? In that “at some point, you do
have to start paying attention to the code you’re writing” way?

~~~
samvelst
I feel the same way sometimes too. One thing to consider though is that when
the overall complexity of a project increases these little things do add up.

------
Terr_
In some cases this can be fixed by introducing a new type, especially if the
language makes it nice to support as a literal. This confers additional
benefits in terms of type-checking.

For example, changing an ambiguous foo(float x, float y) into foo(distance x,
weight y) .

------
otakucode
My suggestion of a better solution would be better tools. For instance, when
viewing or editing the code, a call to CopyFile should appear something like

    
    
       CopyFile([from] 'presentation.md', [to] '/home/presenter')
    

with the "[from]" and "[to]" parts not being actual tokens that are part of
the source file, but annotations that appear to assist the developer. They
would appear as little badges, non-editable or selectable, with distinctions
in their display to make it clear they are UI elements rather than part of the
source code.

------
h2odragon
> Can you suggest a better solution?

Yeah. Mind your docs.

Languages and APIs designed like this and get you crippled tools that simply
do not allow you to express about half the shit you're gonna need to do. That
generates another layer of indirection, abstractions, and workarounds to break
through the "we think for you" for when what "they thought for you" wasn't
what you was actually thunking.

There's a place for languages that don't allow you to shoot yourself in the
foot or other member of your choice, I'm sure. I don't want to go there.

~~~
bitwize
The idea is to use the type system to help you check your work. If programmers
were anywhere near diligent about minding the docs, static typing would be far
less useful than it is. Always assume the programmer hasn't had their coffee
and is prone to making mistakes, and design your system to catch them as early
as possible.

------
thrower123
This is really the number one reason I'd like to use F# more. Single case
unions and units of measure are really slick, and while you can do the same
thing in C#, the legwork is pretty high.

~~~
louthy
In my language-ext library it's pretty trivial:

    
    
        class Hours : NewType<Hours, double> { 
            public Hours(double x) : base(x) {} 
        }
    

It gives you:

* Equality operators, Equals, and IEquatable

* Ordering operators, CompareTo, and IComparable

* GetHashCode, ToString

* Map, Select, Bind, SelectMany, Fold, ForAll, Exists, Iter

* Explict conversion operator to convert from the NewType to the internal wrapped type

* Serialisation

* Hours.New(x) constructor function (often useful when used with other methods that take first-class functions).

It's even possible with the more complex variants to provide predicates that
will run on construction to constrain the values going in.

There's also NumType for integer numeric types like int, long, etc. and
FloatType for floating-point numeric types like float, double, etc. They give
additional functionality by default.

Also, units of measure [4]:

    
    
       Length x = 10*inches;
    
       Area y = x * x;
    
       Time t = 10*sec;
    

[1] NewType - [https://github.com/louthy/language-
ext/tree/master/LanguageE...](https://github.com/louthy/language-
ext/tree/master/LanguageExt.Core/DataTypes/NewType)

[2] NumType - [https://github.com/louthy/language-
ext/tree/master/LanguageE...](https://github.com/louthy/language-
ext/tree/master/LanguageExt.Core/DataTypes/NumType)

[3] FloatType - [https://github.com/louthy/language-
ext/tree/master/LanguageE...](https://github.com/louthy/language-
ext/tree/master/LanguageExt.Core/DataTypes/FloatType)

[4] Units of Measure - [https://github.com/louthy/language-
ext/tree/master/LanguageE...](https://github.com/louthy/language-
ext/tree/master/LanguageExt.Core/DataTypes/UnitsOfMeasure)

~~~
thelazydogsback
Of course F# has units-of-measure built-in

~~~
louthy
Erm, no it doesn't. It has the ability to define units of measure, but the
language doesn't have UoM built-in. There are UoM in FSharp.Core [1], so it's
the same as including it from a library.

Obviously F#’s first class support for UoM is better than my implementation,
but for all intents and purposes the behaviour is the same.

[1]
[https://github.com/fsharp/fsharp/blob/master/src/fsharp/FSha...](https://github.com/fsharp/fsharp/blob/master/src/fsharp/FSharp.Core/SI.fs)

~~~
yawaramin
Most people would consider 'distributed with F#' as being equivalent to 'built
in to F#'. Actually it's even cooler that the SI units are in the standard
library rather than baked into the language.

------
longemen3000
Using key-value pairs? (Julia) `copy(src=>dest)` The advantage of that
notation in this case is that expresses an explicit non-conmutability of the
arguments.

------
emmelaich
The Unix/Linux command line is unfortunate like this; the cp/mv/ln commands
would be better off with the destination specified specially. For instance gnu
cp allows the syntax

    
    
       cp -t <dir> <source...>
    

Which is good because it makes it similar to most other commands.

It would be unfortunate to mandate it though; it would be unfortunate for an
`option` to be mandatory.

~~~
justincormack
It also allows you to use xargs to pipe a list of files in.

------
linuxftw
> You can’t tell without consulting the documentation.

Referring to the documentation is what you should be doing the first time you
use a function, right? Most IDEs now have handy tool-tips to display parameter
details.

I use named parameters when available, but it's no big deal to pass parameters
to a function. This is why we write tests.

------
salmonellaeater
Destructured object parameters make this easy in TypeScript (and JS):

    
    
      async function copyFile({
        source,
        dest,
      }: {source:string, dest:string}): Promise<void> {
        // ...
      }
    
      await copyFile({source: 'presentation.md', dest: '/tmp/backup'});

~~~
yawaramin
There's no need to destructure the parameters.

------
rmtech
Someone should introduce a language feature - maybe a decorator - where
arguments have to be called by name.

    
    
      @mustnameparams
      copy(str from, str to)
    
      copy(from = "blah", to = "hrg")
      > works
    
      copy("blah", "hrg")
      > throws error

~~~
newtempaccount
you mean something like def pythonDoesThis(self, *, from='':AnyStr,
to='':AnyStr)->AnyStr:

------
s17n
Positional arguments just shouldn't exist. Why they persist in newer languages
like Go is a mystery.

~~~
ben509
It's due to how language and entropy work; basically people naturally tend to
abbreviate frequently used terms.

Some languages have banned positional arguments, like Obj C, but these do get
a reputation for being verbose. And verbosity, past a point, hinders
readability.

My inclination is that for unary and binary functions, positional arguments
should be sugar for the keyword arguments, and anything beyond that must be
keywords.

An alternative rule could be you're allowed to designate one positional
argument.

Keep in mind, also, that I'll bet there's code in keyword-only languages that
just uses single letters for all the keywords. Some people just won't or can't
write readable code.

~~~
s17n
Unary, yes. Binary, no.

------
micah_chatt
This. It extends to command line tools too. I can _never_ remember what the
order is for `ln -s`.

~~~
btrettel
I remember the order for ln -s because the third argument is optional. If you
omit the third argument the command will create a symbolic link in the current
directory with the same filename as the original.

~~~
rabidrat
I remember this usage the exact same way :)

------
gsaga

      Window XCreateWindow(Display *display, Window parent, int x, int y, unsigned int width, unsigned int height, unsigned int border_width, int depth, unsigned int class, Visual *visual, unsigned long valuemask, XSetWindowAttributes *attributes);

------
broth
I would be wary of functions that take several parameters regardless if they
are of the same type -- at that point you're already going to have to look
into the documentation.

------
eternalny1
This is one example of why working in a higher-level language, if you can for
a given task, makes life easier.

C#:

PrintOrderDetails(orderNum: 31, productName: "Red Mug", sellerName: "Gift
Shop");

~~~
alkonaut
Your junior colleague would still do PrintOrderDetails(1234, "SomeSeller",
"SomeProduct");

And the compiler wouldn't notice.

You could uused a Roslyn analyzer that gives a warning "methods with
consecutive args of the same type should use named params" I guess. Or a
warning could be made at the declaring site "Don't declare methods with
consecutive args of the same type".

------
collyw
Or just use keyword arguments in Python. I am debugging someone else's code
today and it make it so much easier when you have named keyword arguments -
which he doesn't.

:(

------
AzzieElbab
I constantly tag primitives in scala. It is boilerplatey even with shapeless,
but worth it. Tagging classes might be a bit too much though

------
on_and_off
in kotlin, named parameters are a good defense against this.

They help in many cases. Mixing named and default parameters in a method,
modifying an API without breaking its usage, etc.

It is slightly more verbose, but I tend to use them a lot. Basically
everywhere unless the params are obvious (max(a;b) is a good example)

~~~
thaumasiotes
> Basically everywhere unless the params are obvious (max(a;b) is a good
> example)

This isn't so much "obvious" as "irrelevant"; the arguments to max are all
exactly equivalent -- their order can't affect the result.

~~~
thaumasiotes
Expanding on the point a little further, it would be normal from certain
mathematical perspectives to see max as a unary function, mapping an unordered
(multi)set to the set's maximum element. With one argument, order can't matter
because only one order is possible.

------
baby
that’s one of the reason why I like languages that allow you to write this:

    
    
        thing(dest=a, src=b

------
js8
TLDR: Be wary of noncommutative algebras!

In particular, if you have a noncommutative operator, do not denote it with a
symbol that is symmetric along its vertical axis.

~~~
toolslive
In math, you have: matrix multiplication, vector cross product, ... So where
does this idea originate?

~~~
acjohnson55
In linear algebra, you typically use juxtaposition to represent
multiplication, rather than explicitly with a symbol. For some reason, this
actually seems to capture noncommutativity better than a symmetric symbol.

Another example would be string concatenation, which people often debate the
merits of using a + to represent.

------
nottorp
Something about architecture astronauts comes to mind...

------
zwieback
For me Intellisense pretty much solves this problem

------
pezo1919
I don't get why this got upvoted.

~~~
Jach
[https://en.wikipedia.org/wiki/Bikeshedding](https://en.wikipedia.org/wiki/Bikeshedding)

~~~
pezo1919
Hah! I did not know that term. At least I learned something related to the
post and its comment section! :)

------
GhettoMaestro
For a second, based on the domain, I wondered if Dick Cheney had re-invented
himself as a developer.

