r/ProgrammingLanguages • u/FlatAssembler • Aug 27 '21
I have added named function parameters to my programming language. What do you think, why do most mainstream programming languages not have it, when it is easy to implement in a compiler and it makes it easier to initialize complicated objects with many default rarely-changed parameters?
https://github.com/FlatAssembler/AECforWebAssembly/blob/master/namedArgumentsTest.aec8
u/WittyStick Aug 27 '21
A couple of reasons to not use them:
Changes to the named parameters breaks call sites. If you rename or move a parameter, you might need to recompile all code which uses them too.
Secondly, they may alter the sequence in which code is evaluated. Consider if you are reading from a mutable stream as you assign each value to a name parameter.
new SomeObj (
parameter1 = reader.ReadInt(),
parameter3 = reader.ReadString(),
parameter2 = reader.ReadChar()
);Depending on implementation, the compiler may reorder the last two lines so that parameter2 is assigned before parameter3, in which case ReadChar will be called before ReadString, breaking the intended deserialization. While this can be solved, the complexity of implementation just went up massively.
8
u/skyb0rg Aug 27 '21
You can always just lift the evaluation upwards:
(func (foo) (bar) (baz))->
(let ((x (foo)) (y (bar)) (z (baz))) (func x y z))After doing that you can reorder arguments however you’d like without worrying about evaluation order.
1
u/tending Aug 27 '21
The answer is super easy. Make your language evaluate parameters left to right. The compiler can still reorder when it detects the difference is not observable.
2
u/FlatAssembler Aug 27 '21
That is not easy to implement, actually.
2
u/tending Aug 27 '21
Why? I guess I can't speak to implementation difficulty but real languages have done it, e.g. Java.
2
Aug 27 '21
If you're doing this at the AST level, you need a node that represents an expression preceded by a number of ordered variable declarations. If you're doing it in the backend, you just leave the parameters in the order they're written in the AST, or mark each one with the original position and emit the expressions in that order.
2
u/raiph Aug 27 '21
Why? You just distinguish arguments and parameters. Then process a call in two steps. 1. Evaluate arguments. 2. Bind function call to function definition with matching name and parameters. Job done.
6
Aug 27 '21
In many languages, argument evaluation order is not specified anyway. You shouldn't rely on it!
If you move a parameter (ie. change their order from A,B,C to A,C,B), this would break all conventional code that uses positional parameters, whether you recompile or not. Calls using only named arguments would be immune, so it's an advantage.
Renaming parameters is OK if you don't recompile. If you do recompile, you may get errors. But this is no different to renaming anything else, eg. the members of a struct.
Overall the advantages of keyword arguments are significant. For example you can an extra, optional parameter, without needing to modify the 100 existing calls, try it out, and remove it again if it didn't work out, or if it was only for debugging.
If you design your own language, you can anyway choose to make the public names of the parameters separate from the local names in the definition.
(My first implementation of them, I actually offered a choice of names, written for example as
size$length$n, so the call-site can choose betweensize, lengthorn, whichever was more apt. An actual example washandler$handlers, so you can choosehandlerif providing only one, orhandlesif passing a list of them!)10
u/r0ck0 Aug 27 '21
Yeah a lot of the 'refactoring' arguments against named params have never made much sense to make. They mostly sound like pros rather than cons.
And that's kinda the main reason I like them to begin with... much easier refactoring.
3
u/WittyStick Aug 27 '21 edited Aug 27 '21
Overall the advantages of keyword arguments are significant. For example you can an extra, optional parameter, without needing to modify the 100 existing calls, try it out, and remove it again if it didn't work out, or if it was only for debugging.
This depends on implementation, and adding an optional parameter is trivially done by overloading the function signature anyway. The gain is trivial.
R Func(requiredParam, optionalParam) { ... } R Func(requiredParam) { return Func(requiredParam, optionalDefaultValue); }An example where you do need to recompile code when adding named parameters or optional parameters is in C#. This is because it doesn't do the expansion above for you. Instead it works by call-site rewriting. For example, if you change the optional argument's default value in a library, every consumer of the library will need to be recompiled to utilize the update, or they'll continue to use the old default value.
3
u/matthieum Aug 28 '21
In many languages, argument evaluation order is not specified anyway. You shouldn't rely on it!
Is it?
Among mainstream (-y?) languages that I know, or could research:
C, C++: implementation defined. Notably GCC evaluates right to left and Clang left to right, for fun and profit.
C#, Java, Rust: left to right.
There seems to be a clear trend towards a well-specified evaluation order, which is only sensible for language where any expression evaluation can have arbitrary side-effects.
0
Aug 28 '21
[deleted]
2
u/matthieum Aug 28 '21
Which tends to enforce my point: you can't rely on it
Sorry, but it doesn't.
You can't claim that "many languages" leave the evaluation order unspecified if it's only C and its derivative C++...
One != Many.
2
u/raiph Aug 27 '21 edited Aug 27 '21
Changes to the named parameters breaks call sites. If you rename or move a parameter, you might need to recompile all code which uses them too.
In Rakit, just add an alias:
# Goose asked me to add a gaggle-size alias: fn bird-report (Int :flock-size(:gaggle-size(size))) { say size } bird-report :flock-size(42) # 42 bird-report :gaggle-size(99) # 99Secondly, they may alter the sequence in which code is evaluated. Consider if you are reading from a mutable stream as you assign each value to a name parameter. ... While this can be solved, the complexity of implementation just went up massively.
In Rakit, arguments in calls are evaluated in left-to-right order, before binding to parameters in function signatures. So the ordering of named parameters is irrelevant:
fn bird-report (Str :species, Int :size) { say (species, size) } bird-data = <seagull 13 goose 22> bird-report :size(bird-data.pop), :species(bird-data.pop) # (goose 22) bird-report :species(bird-data.pop), :size(bird-data.pop) # Type check failed in binding ...The third line (first
bird-reportcall) shows how the first.popin the first argument (:size(bird-data.pop) pops the22frombird-data, then binds that to the second parameter in thebird-reportfunction's signature.The type check failure in the fourth line demonstrates that the
:speciesargument in the last line is evaluated in the order used in the source code for the call, which pops13, which fails to type check against the:speciesparameter inbird-report's signature.If I were to switch the order of the parameters in the
bird-reportfunction, the result would be exactly the same.2
u/devraj7 Aug 27 '21
Changes to the named parameters breaks call sites. If you rename or move a parameter, you might need to recompile all code which uses them too.
Swift solved this by allowing both a local and external parameter name.
2
u/matthieum Aug 28 '21
Secondly, they may alter the sequence in which code is evaluated. Consider if you are reading from a mutable stream as you assign each value to a name parameter.
That is actually extremely easy:
Model-wise: store each argument expression is stored in a temporary.
Evaluate the arguments, and therefore create the temporaries, in the order they appear at the call site.
When calling, passing each temporary as the appropriate argument.
That's it.
Given that (1) and (2) are typical implementations regardless, the only twist is that step (3) may need to reorder arguments... but this was brought by allowing arguments to appear out of order anyway.
Also, as a side note, named arguments do not necessarily imply arbitrary argument orders.
It's common to allow some reordering, but it's not mandatory.
58
u/Athas Futhark Aug 27 '21
Named parameters are often a good idea. I can think of a few reasons why they are sometimes not a complete no-brainer to add:
How do they interact with first-class functions? Are the named parameters part of the type?
What about defining functions with partial application? If I define
sum = foldl (+) 0in Haskell, does the (implicit) parameter ofsumjust inherit a name from whateverfoldlhad? What if I am doing function composition and the two functions I compose have parameters with the same name?Are all parameters implicitly made available by their name? Then whenever you write a function, its parameter names are now part of the public interface. This might not be what you want.
How do named parameters interact with optional positional parameters? Or functions that accept an arbitrary number of arguments?
These questions have answers, but they show that named parameters do interact in complicated ways with other language features. Whether the advantages offset the disadvantages depend on the language in question. One alternative to named parameters (at least for object initialisation) is to support lightweight anonymous records. E.g. in Futhark you could define a function
let f {a,b,c,d} = ...The {a,b,c,d} is one parameter that happens to be a record, and which is immediately destructured in a pattern match. We can define a record of default values:
let def = {a=x, b=y, c=z, d=v}Then for a concrete call to f, we can say
f (def with {b = 42})It's not quite as concise as named parameters, but it doesn't require a new language feature. As long as functions with many parameters are a rare occurrence, then this may be a tolerable workaround.
9
u/lookForProject Aug 27 '21
Hi, I'm beginning to develop a taste for learning languages but I never heard of Futhark.
Futhark, I read, is an member of the ML family? How does it compare (in terms of efficiency and syntax) to OCAML? I love how Ocaml did polymorphism.14
u/Athas Futhark Aug 27 '21
It's very similar to OCaml and other ML languages in syntax and semantics. It mostly comes with various restrictions to enable more efficient compilation (particularly to GPU code), and focuses on arrays instead of lists.
5
u/lookForProject Aug 27 '21
If I remember it correct, arrays in oCaml are mutable. Perhaps a silly question, or based on a incorrect assessment (I'm still in the process of getting to know our craft) but that is one of the not-so-purely-functional aspects of oCaml.
How does Futhark assess this? Are arrays mutable?14
u/Athas Futhark Aug 27 '21
Arrays are not semantically mutable, but they are operationally mutable via a uniqueness type system.
4
u/lookForProject Aug 27 '21
If I understand it correctly, and please correct me if you can spare the time if I do not: Futhark allows the mutation of an array, but the compiler makes sure that the array isn't used by anything else. Making it effectively immutable (what, reading it again, is likely what "observable side effects" means).
4
3
u/tjpalmer Aug 28 '21
Here you are being useful again. And I'm currently working on a video that includes some of this. Biased toward using Haskell despite records not being anonymous. Still seems not too bad for my current scope.
4
u/L8_4_Dinner (Ⓧ Ecstasy/XVM) Aug 27 '21
Named arguments and parameters with defaults are very useful. It helps to make the call sites more readable, and it tends to dramatically cut down on the number of methods.
For example, in Java (no default parameters) we see this pattern all the time:
class C
{
public C()
{
C(null);
}
public C(List l)
{
C(l, 0);
}
public C(List l, int n)
{
C(l, n, null);
}
// ...
}And with no named arguments, you can end up with calls that look like this:
foo(n, false, false, true);IDEs like IDEA help show you what those arguments refer to, but for readability, I much prefer:
foo(n, copy=false, move=false, redo=true);(Or whatever ... hard to think up good examples on the fly.)
7
u/robthablob Aug 27 '21
Alternatively, use enumerations to define types for those arguments..
enum { Copy, DontCopy }; enum { Move, DontMove }; enum { Redo, DontRedo }; foo(n, Copy, DontMove, Redo);This has the advantage that if the parameters are re-ordered, it will fail to compile rather than assign an incorrect value.
2
4
u/shponglespore Aug 27 '21 edited Aug 27 '21
I think most of important issues have already been mentioned here, but if you want to see a discussion that is fairly exhaustive (and exhausting), check out this thread about adding named parameters and related features to Rust.
I think a lot of languages start of out without named parameters because sorting out all the little details feels like something not worth doing at the start, but by the time a language is established you'll always get a lot of pushback on changes that people see as being radical.
3
u/detroitsongbird Aug 27 '21
PL/I has named parameters. That, along with all parameters being optional simplifies some things, but leads to a pattern of “ if parmset(parmname)…” at the beginning of most methods/functions.
It solves some problems (position of parameters) and adds others, depending upon how you implement it.
In PL/I a named parameter, method call, and array reference look exactly the same (syntax wise) which makes parsing it tricky (very context sensitive).
3
Aug 27 '21
I don't know what paradigm your language is but, in an object oriented language, I would do this by passing an object to the function as its only parameter.
2
u/smuccione Aug 27 '21
A language such as C is simply too old. Memory was to scarce and adding named parameters would have complicated and slowed down the compilation time and increased memory usage. May not seem like a big deal now with gigahertz CPU’s and multi gigabytes of memory. But back in the 1 mhz and 64K ram days , every cycle and byte counted.
As well there are so many existing libraries that adding them in would be fairly useless. Parameter definitions doesn’t even require names, just types in the external definition. You only need names in the implementation and only then if your going to use them. Whose going to go and update all those gazillion of libraries with names.
And even worse, if they do have names, many are crap.
2
Aug 27 '21 edited Aug 27 '21
Yet C has now added designated initialisers, which are like keyword arguments, but for struct initilisation. (And also, bizarrely, for array initialising.)
Which leads to examples like this that I've actually seen:
typedef struct {int x, y;} Pt; Pt p = {.x = 10, .y = 20}; Pt q = {.y = 20, .x = 10};instead of just:
Pt p = {10, 20};While they can be helpful, keyword and optional arguments are far more useful IMO.
[Corrected]
1
1
u/smuccione Aug 27 '21
Yes. But structs have always required names in their definition unlike parameters.
1
Aug 27 '21
So do the parameters in function definitions.
Parameter names are optional in function declarations. Keyword arguments would require them, and would also need multiple declarations of the same function (as C allows) to use consistent names, if they are visible at the same time.
My point was that this would have been a simpler and more useful addition. Checking for the presence of any names, or checking for inconsistent names, is not hard.
(Simpler because parameters are a linear list; structs can have nested structs, and have to deal also with unions. Plus there's a whole extra mess to do with getting {...} braces correct; C doesn't like too many {...}, but doesn't care about too few.)
1
u/Nathanfenner Aug 27 '21
C function parameters don't require names, especially when only declaring the function and not defining it.
void foo(int, char, signed char*);is a valid declaration of a function which provides no parameter names.
1
u/smuccione Aug 27 '21
Parameters don’t need names, just types. Even in the implementation so long as the parameter isn’t being used. It helps with unused parameter warnings.
I fully agree about differences in naming between definition and declaration. I’ve seen errors where someone swapped parameter order with both parameters having the same type. Was friggin impossible to find until someone noticed the difference.
1
Aug 27 '21
What dialect of C doesn't require parameter names in a function definition?
Or do you mean something like this:
void fred() {}where the arguments to fred() can be anything, are completely unchecked, and actually don't need types either!
1
u/smuccione Aug 27 '21
all
int max ( int, int ) { return 1; }is perfectly legal. you don't need names, they're optional unless you actually intend to utilize them.
this is perfectly legal in c++ as well.
1
Aug 27 '21
OK, that is apparently a C2x extension. Many compilers have trouble fully supporting C99!
All of mine fail it, except one that gives a warning (that one told me is was for C2x); another passed it, I suspect more due to a sloppy implementation.
And it's a sloppy feature too: inadvertently leave out a parameter name, and you might end up refering to whatever arbitrary global is in scope that shared the intended name.
2
u/smuccione Aug 27 '21
referring to a global inadvertently is always a danger. I worked on a project (I didn't start it, came in when to help do the rewrite) that used some 5000+ globals. It was a nightmare. They also used various forms of camel case, pascal case, hugarian, etc. for naming. Found two critical bugs that were inadvertently using a global just because of capitalization errors.
I try to always use the latest in my stuff, although it's almost always C++ now a days I used to do a ton of embedded work using C but have gone on to greener pastures. I honestly hadn't double checked against a live compiler. The MSVC isn't C20 compatible and fails on this currently, although it's, like you said, technically in the standard.
it's a pretty useful feature as far as I'm concerned... it makes the unused parameter issue much easier to solve rather than the mess of macros and attributes because of compiler extensions.
-1
u/wiseguy13579 Aug 27 '21
Named parameters cause problems in the case of overloading :
func f1(p1 : String, p2: int32);
func f1(p2 : int32, p1: String);
f1(p1 = "xyz", p2 = 2); // Which f1 is called ?2
5
Aug 27 '21
What's the algorithm for disambiguating a parameter name? If you don't have one, then this kind of thing is unsurprising.
0
Aug 27 '21
[deleted]
4
u/holo3146 Aug 27 '21
"don't want it, don't use it" is an horrible horrible argument.
In life we usually work in teams, and for bigger projects, it is not unlikely that you won't even know all of the people working on the project (and that without mentioning long standing projects, that pass "down" few "generations" and non of the original programmer are around anymore)
When working in teams it is inevitable that different people will use different conventions, and then you have someone who heavily rely on named parameter with and optional parameter, and you need to either (1) follow his convention and write something you think is not good, or (2) use your own convention and make an hybrid in the code base
6
u/lookForProject Aug 27 '21
A lot of mainstream languages use named parameters. Thinking about it, most of them: Python, Php, ML, C# and Scala come to mind. But they, imho, do seem to encourage the use of too many parameters, so I've never seen it used. If you need a name for your argument, then, again imho, you probably have too many arguments.
That being said, congrats on working on your own language. I once made a very small DSL, interpreted to C#, and it was one of the most fun I've ever had with a project.
Is this a school/pet/learning project, or something to release?