Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

When giving an example on how infix notation reads better:

    // This is obviously not too right
    ",".splitBy("1,2,3,4,5")

    // This should be right, because it reads out more naturally
    "1,2,3,4,5".splitBy(",")
It's funny that in Python split works this way but join doesn't. This is because in the case of split both arguments are strings, but for join one of the arguments is a Sequence, which is a general protocol rather than a class.

The proposed solution by Keli is to be inspired by the Smalltalk-style message syntax:

    'Hello world' replace: 'Hello' with: 'Bye'
I think, in general, requiring named arguments is a good thing. Swift does it, and in codebases with a decent amount of TLC, it looks great.

Function calls are one of the weird places were the syntax of the language rarely helps you figure out what is going on, and for the most part is just a sequence of human-written names one after another, and in languages with custom operators it could be even terser.

In comparison, if-statements, loops, pattern matching, etc. were (hopefully) designed to be expressive. I think by requiring named arguments, function calls will also be much more readable, relying less on an active human effort to do so.



In Clojure, for functions that will probably have more than a couple args, I like to pass a map, then destructure it by its keys in the function definition. Two benefits are: you don't need to remember the order of args, and it makes refactoring easier in cases where you are simply adding a new optional arg (you don't need to update all the old calls of that function if they aren't using the new option).


That's a pattern I use in a lot of Typescript, it's pretty useful!


We’ve got to write argument names twice when we do it in typescript though:

  function foo({ bar }: { bar: string }) {
Still a great pattern, but I’d probably use it a lot more if I only had to write the variable names once.


I get this might just boil down to preference but I absolutely hate named parameters. They’re biased towards new users of a language and quickly become painful to write once you’re familiar with the function call.

Plus they don’t always improve writability outside of IDEs because you then have to memorise the parameter names and in some functions there’s several terms that could equally apply (if you’re using an IDE with hinting then the advantages become moot as the same IDE would hint the order of parameters).


I disagree that named parameters are just biased toward new users. They're also biased toward reading rather than writing. I find that as a code base becomes bigger and more non-trivial, I end up reading code many times, and the more I appreciate named parameters.


I understand what you're saying, but something I have noticed over the years is that the amount of code that I can make sense of at any given time is actually proportional to what I can see onscreen at any given moment.

I have had pretty good luck cheating this in a bunch of ways: I use a small font, a big display, and I use a terse programming style.

Once you internalize a bunch of common higher-order functions, you learn how to draw a ton of meaning out of a relatively small number of terms.


Yes, but this strategy is only suitable for a single developer, or a small group of similarly-experienced-with-that-specific-codebase developers. Onboarding somebody into a world full of single-character variable names and such is a headache. Named parameters are for reading code, and if you're not intimately familiar with the code on your screen right this second, they are helpful.

I think what we really need is a sort of "lens" system by which we can modify the syntactic appearance of our code without adjusting the semantics, but do these changes on-the-fly. So say you're doing some debugging or whatever and you're gonna be staring at the same 300 lines of code for a few days — so you switch over to "terse" mode and suddenly the named parameters are gone and the variable names are abbreviated (assume a magic system that picks good terse variable names according to your preference). But then when you're done with that section and ready to venture into the remainder of the codebase (or if you're a new developer who's unused to the team's naming conventions or whatever), you can use "verbose" mode that shows the parameter names and whatnot.

I imagine something like this is not obviously straightforward, but it could be worth investigating!


> Onboarding somebody into a world full of single-character variable names and such is a headache.

The opposite of named arguments isn’t single character variable names. Any organisation with an enforced coding standard would ensure that variables are descriptive irrespective of whether that language uses named arguments or not.


Yes, I agree! I was specifically addressing the parent comment's line about "I use a terse programming style." When it comes to functional programmers, they (more than any other group) will take terseness to the extreme in the form of single-letter variable names in inner functions, match forms, etc.

I didn't mean for my comment to be entirely literal, either. Rather, I just meant to say that terseness can impede readability for those who are not yet familiar with the codebase. (But I have personally been on the receiving end of onboarding into a codebase full of literal single-character names, which I found incredibly frustrating.)


> When it comes to functional programmers, they (more than any other group) will take terseness to the extreme

You should really read about APL and other array languages then. (I don't have a good starting point, but they tend to come up on HN periodically such as [0] [1]).

0: https://news.ycombinator.com/item?id=23055793

1: https://news.ycombinator.com/item?id=16847641


The right balance here can depend on the specific business you're working in.

Some companies earn the privilege of a super tenured core team of engineers who work on their product for an extended period of time. They will choose different tradeoffs from a team that needs to adapt to higher turnover.


For a language that says it'll work hard to be IDE friendly, an IDE can easily show you the argument names and their order for any function. On IntelliJ, it even overlay them.

Thus, you can be writer friendly and reader friendly, considering the reader uses an IDE.


I can't disagree more strongly about this. Many, many times I'm trying to understand a codebase, and what I have is that codebase, not the dozens of dependencies involved, and not a full development/build environment. I may not even know what, if any, IDE the original developers used.

There's also very common sources of bugs when functions take multiple arguments of same (or sadly in some languages, implicitly convertible) types. With named arguments in complex functions, you can sit down, read the code, and spot the bugs. Happens frequently enough in code review that we have a category for it. Without named parameters here, every single function call becomes a game of "mouse over the parameter in the IDE". Moreover, "you can just read the docs inline in the IDE" also causes people to not think much about naming things, which also harms maintainability.

It's a real issue in large, long-lived codebases that may not seem like much in smaller projects.


But why not turn this around? Let the IDE autocomplete everything, so it’s easy for the writer (or even hide the named arguments). And have the named arguments in the source code, then all readers can read it no matter their IDE!


Because it's a pain to navigate with the keyboard, plus accidental completion means more deleting, and other little niggles like that.


They are biased to new readers of a codebase, not of a language, which is really just another way of saying that the code is very readable.


I don’t think it’s fair to say what’s readable to new developers is the same as what’s readable to experienced developers nor even every developer.

For example the terse style of Sexpressions or C-style braces are off putting to some but after a short while they become second nature to visually parse (not saying all code should follow Those idioms, just using that as an example of how readability changes with experience). On the other hand I never found it as straightforward visually parsing the “word soup” of named arguments when using languages that favoured it. While those languages were easier to learn the basics, they quickly became tiresome to write larger code bases with.

But I guess this just boils down to personal preference.


I'm talking specifically about the function calls, which are effectively the same in C, Rust, Python, Lisp, Haskell, and many others. No matter how terse, it's always some form of

<name of function> <value1> <value2> <value3> ...

The absence/presence of parenthesis and commas don't add anything of semantic value here, so that's not really what I'm referring to.

The problem is, is that the values often don't have any meaning attached to them, especially when they are literals. C++ codebases are pretty bad about this with boolean parameters, just having a random `true` at the end of the function call.

Variable names at the call-site are good hints, but we often pass the same variable to different functions, and the name doesn't exactly fit the meaning of each function. This is why I think requiring named parameters, at least by default, is better.


I agree random boolean arguments are unhelpful. Good software development standards would say any uses of boolean values should be substituted with well named constants if the same value. However the advantage of having that baked into the language rather than forcing developers to via code reviews isn’t lost on me


Disagree. Particularly since named arguments are part of the function name and in languages where you use named arguments you will often have very expressive overloaded function names.

You might have 3 related functions like so:

  getStudent(datastore: DS, byId: number)
  getStudent(datastore: DS, byAssessmentId: number)
  getStudent(datastore: DS, firstName: string, lastName: string, birthdate: date)
Without named parameters, you are stuck with function names like: getStudentById, or relying on convention for naming parameters and often wind up with duplicate names or not knowing if a function is out there because your idea of naming is different from someone else's.

Intellisense picks this up too and suggests the 3 different names. Works fantastic.


I get overloading is a popular way of writing DRY code but I’ve never been a fan of overloading either. You might dislike getSomethingBySomething style function names but it’s no different to the examples you’ve given in terms of readability but with the bonus of having fewer surprises.


Should I look for the function called: `getStudentByClassRoomFirstNameLastName` or is it `getStudentByClassRoomLastNameFirstName`, or `getStudentByClassRoomFullName` since you sort that way. They you have to deal with optional parameters, do you have an extra argument for that or is there a `getStudentByClassRoomFirstNameLastNameMiddleName` function out there? Now you are scanning the auto-complete code trying to figure out which function you need to call.

If you have named parameters, this just works:

  `getStudent(classRoom: 15, firstName: "Mary", lastName: "Jane")`
  `getStudent(classRoom: 15, firstName: "Mary", lastName: "Jane", middle: "Anne")`
It can call the same function with optional parameters, or 2 different functions and you don't care from the calling side because the syntax is the same and always makes sense.

(slight tangent)

Far better with overloading in general is being able to have 2 methods with the same name which return the same thing but have different types of inputs. Like in Elixir, you can have an API callback be `handle(apiResult)`, then have 2 functions, one which handles the error and one which handles the success. Zero logic written to filter out bad-calls, it's just the same method with different types (the Error result type and the Success result type). Vastly simplifies and cleans up that kind of code.


Even better is Elixir's Ecto or Linq

    Students |> Students.Repo.get_by(first_name: "Ryan")

    from s in Students where firstName = "X" select s
    
    Students.AsEnumerable().Select(s => s).Where(s => s.Name == "X")
etc.


Elixir is what I was thinking of for much of the above, unfortunately, I only used it fairly briefly and so I was going largely on memory and some of that is likely more Swift than Elixir which shares some of that.

I really loved using Elixir, very frustrating going back to TypeScript after that.


I'm honestly curious what you see as the issue with "getStudentBy"-type function names. Could you expand on that?

Of course I understand a name could become rather verbose in your last case, but I'm not satisfied that it would be much of an issue in practice.


See above.


Hmmm... I see where you are going there, but I don't understand how named arguments solve the problem[0] you have outlined. That is, a problem of discoverability.

Like what, precisely, does the autocomplete show in your editor when you type `getStudent` vs `getStudentBy` that makes the former so much better? In either case you are left to disambiguate either the correct overload or the correct method unless... well... you already know there is an overload that accepts the data you happen to be working with. Presumably you would also then know the correct method name.

I suppose it's possible to start typing your named parameters directly and the editor "fills in" name you are going for? But that poses nearly the same problem again: "Wait... is it 'nationality' or 'country'? Did we decide on 'lastname' or 'surname'?"

I agree the `getStudentByCountryAndName` isn't a great signature, and that wrapping all of the args into a single DTO like `StudentInfo` is not so great either. But I can honestly say, I have never once run into a problem of finding a method in the wild! Even if we had numerous `getStudentBy`-type functions all with similar signatures/names it probably wouldn't take more than a few seconds to narrow down to the one of interest.

I also find, from a consumer perspective, named-parameters to be less ergonomic. I like that when I type `getStudentById(` my editor boldens the argument I am meant to pass next. I don't have to decide which parameter I am going to pass next... my editor just walks me through the function (maybe I'm just lazy!)

To be clear, I am not arguing against named-parameters. Though I dislike that they broaden the public surface of functions, I suppose I would rather at least have the option to use them than not at all. I just don't really see, in a practical sense, how they would improve a code-base beyond some fairly niche scenarios that are probably a code smell anyway.

[0] It seems to me your argument is more aimed towards overloading than named-parameters in particular, but I will accept that there is significant overlap in this space.


I think a better question is:

Why would you want a function that gets students to be called anything other than `getStudent`? Fundamentally, the simpler, easier to parse your function name is, the more readable and editable your code is.

I definitely think overloading functions is more interesting/ important than named parameters. But named parameters are useful both for overloaded functions and for optional parameters where you might not know what the 3rd/ 4th parameter might be.

One pattern I see frequently in languages which lack named parameters is instead of using optional parameters, people pass objects with optional properties. This is used a ton in Javascript/ Typescript. If the language supported proper named & optional parameters, it wouldn't be an issue.


How would function composition work without currying? You'd have to name everything, wouldn't you?

I'm thinking of how you could write in point-free style or even have a `(.)` function to begin with if you had the Smalltalk "message passing" style.

Sure the Haskell syntax is a bit much when you're not familiar with it but, like almost any language, it's often the least-interesting part of the language and the most talked about.


That's the whole point of the syntax changes: to make the tradeoff that sacrifices terseness or efficiency or even composability in favor of readability.

A lot of functional languages -- Haskell as I understand it is the biggest perpetrator -- optimize for code length and composability, trying to make things as expressive as possible, and decrying those who can't read something that has meaning densely packed into every single character as those who are "simply not familiar" with the language.

But that's what OP is saying: OO languages go for readability and not expressiveness, and wouldn't it be cool if there was an FP language that did the same thing?


I feel like that's a value statement and how you qualify readability would change the answer.

I'm curious if Keli plans to maintain function composition in the face of named arguments. It would be quite nice to have both.


After learning some array languages, functional languages like Haskell are actually pretty verbose.

Moreover, I believe that in general, shorter code is more readable code. People just like to make excuses in order to avoid learning anything new.


Just spitballing, but you could have composition eat the name of an input to the outer function: Given f(x,y) and g(z), write

let q = (f .y g)(z=a) then q(y=b) = f(x=g(z=a), y=b)

let q = (f .x g)(z=a) then q(z=a) = f(y=g(z=a), x=b)

Aesthetically it would be kinda like the notation for fiber product.


They are biased towards new users of a codebase, not only the language.

Named arguments improve readability of a code if you are not used to a codebase. By improving readability, you also improve maintanability: you need less experience in a codebase to make a change.

You focus on writability where it's not the biggest issue of the field, it's software debt.


I disagree 100%. I write with more and more named arguments. There are missing features to make it really nice in python though, like kwarg short forms like ocaml has.


Advocating for trading away readability in favor of easing the lives of devs who refuse to use an IDE doesn't seem like a win, especially in the context of a team.


I’m often writing code into vim (without any fancy plugins installed) and never had an issue. As I stated in my previous comment, I don’t particularly believe named arguments make it any easier to write without an IDE because you’re only trading memorising the order of parameters with the names of those parameters. Either way there’s still a minimum level of memorisation required.

I get each developer is different but personally I remember numbers and orders easier than names.

And frankly, if you have a developer who can’t learn the basics of the language they’re employed to work on yet also refuses to use any developer-friendly tools to assist him, then that’s a failing of the the developer and not the language.


and quickly become painful to write once you’re familiar with the function call

JavaScript's more recent structuring/desctructuring arguments has made this pretty seamless as long as you name your variables well.

For the most part switching from ordered to named is just func(arg1, arg2, arg3) is just foo({arg1, arg2, arg3})

You may need to rename or pass in the params on the call but it's generally pretty nice. As an above comment says about Clojure - as soon as I need a third argument in a function I switch from ordered to named and its pretty painless.


>I absolutely hate named parameters. They’re biased towards new users of a language and quickly become painful to write once you’re familiar with the function call.

It is a fair complaint, but don't you think this is offset almost entirely by a good IDE?


First, having spent the past two years in IntelliJ with Java, no. I still would have much preferred named parameters to having to worry about the order (and still occasionally mixing them up when they're not unique types, and only finding out when things fail).

Second, if a language is heavily reliant on features from an IDE, yeah, I'll echo that's a badly designed language.


Sorry, I agree with you. I like named parameters as well. I meant the overhead of having to type them is pretty much entirely offset by a good IDE that fills them out for you. I don't think there is a downside unless you are coding in a text editor.


Unlike positional arguments? ...


I haven't used an IDE in years. Just VSCode. Which is not really an IDE. I haven't missed it. I think that if a language depends on an IDE to be useful, it's a design smell.


VSCode is an IDE. Otherwise, what is am IDE according to you? Is Emacs an IDE? Eclispe?


VS Code (which I like and have used extensively for a number of projects) is kind of an IDE, but it doesn't quite compare to full blown Java IDEs IMO.


It's an editor with syntax highlighter, autocompletion, go-to-def, and go-to-uses that also builds the code, runs the code, runs the tests, and also allows you to run them (code & tests) under a debugger. Oh, and it also has projects/solutions/workspaces/whatever they're called.

Back in my days™ that's what they used to call an "IDE". But today that's just "glorified Notepad++", I guess? What features does it miss that stop it being a full-blown IDE?


I wrote kind of like an IDE :-)

Maybe I'm just a spoiled brat, but I came to Java from C, PHP, Visual Studio (the old one) and learned how nice programming could be before I left for .Net Core and frontend (with much TypeScript), mostly in VS Code but sometimes in Rider and Webstorm.

To me, to be a real IDE these days you need to have proper refactoring support.

And to a spoiled brat like me that excludes almost everything except Rider, Visual Studio with Resharper, NetBeans, Eclipse and IntelliJ :-)

But yes, I too am old enough to remember back when the C/Assembler program we used to program microcontrollers were considered IDEs.

And technically you are of course correct :-)

I guess a better classification would be: IDE level 1,2,...n where what I call IDEs today are really level 3 IDEs.


maybe named parameters only for functions with more than 2 arguments might be a good compromise?


Hmm, to me it seems that 'splitBy ","' should be a function that takes a string and returns a list of strings:

  splitBy "," : String -> List String
so it seems "clear" that the separator should be the first argument. But maybe I've just programmed functionally for too long?

As an alternative, perhaps there's space to introduce types to make this something like

  split : Pattern -> String -> List String


I agree with your intuition. I'll try to make my intuition explicit:

The 'splittee' should be the last argument. The last argument is usually what you want to a) elide as part of function composition, or b) loop over (the tightest).

E.g. Splitting a file into lines:

    lines content = splitBy "\n" content
can be:

    lines = splitBy "\n"
If splitBy used the other order, it would be:

    lines = (\c -> splitBy c "\n")
E.g. Splitting a file by lines and tabs:

    entries content = concatMap splitBy "\t" (splitBy "\n" content)
can be:

    entries = concatMap splitBy "\t" . splitBy "\n"
If splitBy used the other order, it would be:

    entries content = concatMap splitBy (splitBy content "\n") "\t"


I don't have a firm position on the matter, but consider:

  lines content = content .splitBy "\n"
  lines = (.splitBy "\n")
  dec x = x - 1
  dec = (- 1)
It seems like some infix-like things ought to be equivalent to (oper right left) rather than (oper left right). Not sure how that works out in practice, though.


The question is should the writer go through the effort of placing the parameter name, or should the reader go through the effort of looking up the function prototype? Since according to Clean Code (https://www.goodreads.com/quotes/835238-indeed-the-ratio-of-...) code is more often read than written, it should makes sense for the writer to do it.


> The proposed solution by Keli is to be inspired by the Smalltalk-style message syntax

But that doesn't actually solve the problem.

  "Hello world".replace("Hello","Bye") // works fine
  but
  somesequence join: "," // still doesn't work
  // unless every $Sequence implements join explictly


This particular set of examples is especially ambiguous because "splitBy" can be read two different ways: as a description of the result (noun having been split by a separator) or as an action (subject split direct object by a separator). Which one you choose will subtly affect the way you group the arguments. Conventions in functional programming tend to prefer the former, whereas imperative (including OOP) languages prefer the latter. Either way, however, the string which directly follows `splitBy` should be the separator. In the functional example:

    -- Is this correct?
    splitBy ","  "1,2,3,4,5"

    -- or this?
    splitBy "1,2,3,4,5"  ","
there really is no question that the first version is correct. Even oversimplifying as the article does and treating "basic English" as the only applicable domain knowledge, I'm not likely to read "split by ',' …" as an instruction to split the string "," by some other separator. To understand how the second string fits in you need more than basic English, because English doesn't have that sort of syntax. However, once you learn that it parses as `(splitBy ",") "1,2,3,4,5"` the result is pretty obvious.

In the OOP language example the second version with the string knowing how to split itself, as it were, does make more sense than the first version. The target of the method is always the subject, and the method name is usually read as a verb, with parameters as direct or indirect objects. (Read as: String "1,2,3,4,5" (S), split yourself (V) by the string "," (I.O.).) However, in functional programming the bias is exactly the opposite, because functional programming is about programming with first-class functions on data, not performing actions. You could easily arrange to write:

    "1,2,3,4,5" `splitBy` ","
in Haskell using infix notation, and it even reads fairly well reinterpreted as English prose. However, defining `splitBy` this way would imply the curried prefix version:

    splitBy ","
would not be a function that splits its input by ",", as one would expect, but rather one that splits "," by its input. Which just goes to show that not every function is well suited for both infix and prefix notation. Whichever version you choose needs to be used consistently. In general the convention has been to define multi-parameter functions such that they can easily be used as combinators, via currying, which seems fitting to me given the nature of functional programming. Functions which are designed to read well as infix operators usually require operator sections or `flip` to adapt them for use as combinators.

> I think, in general, requiring named arguments is a good thing.

I have no objection to optional named parameters, but making them required would necessarily eliminate currying and simple function composition, and that I have a problem with. Record parameters with named fields are generally sufficient for the situations where named parameters are useful, especially since they're ordinary data which you can manipulate at will and not some special syntax baked into the language just for function calls.


> Functions which are designed to read well as infix operators usually require operator sections or `flip` to adapt them for use as combinators.

It occurs to me that this could have been avoided at the language level by defining infix notation to implicitly flip the arguments. For example,

    x / 3 == (/ 3) x == (/) 3 x

    x ++ "suffix" == (++ "suffix") x == (++) "suffix" x

    x `splitBy` "," == (`splitBy` ",") x == splitBy "," x
(The left and center forms are valid and equivalent Haskell code today; the right form has the arguments flipped.) I think on the whole this would have made it easier to transition between the curried forms and the infix forms, since it's more common to want to bind the RHS of an infix operator than the LHS. I would also say that e.g. `(/) 3` reads more naturally as "divided by three" than its actual definition as "three divided by …". No doubt we could find a few counterexamples where it reads better to have the LHS first, though I can't think of any at the moment.

Of course, it's much too late to be changing something so fundamental at this point. Even as an extension it would cause far too much confusion.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: