What are people's thoughts on this, which I percei...
# adobe
a
What are people's thoughts on this, which I perceive to be a bug in CF's rest operator implementation: https://trycf.com/gist/f78d75b0f5a0200ad1d40aa0671ee3c4/acf2021?setupCodeGistId=b81066870ee105d8c47724cfbfe07220&theme=monokai

https://i2.paste.pics/84ae311855506da886f46722ed3b48fb.png

m
Adam. It is Friday afternoon here & I've been online since 6am lol. For the love of all that is hoppy, what in blazes have you done here man?
a
Oh I made the mistake of expecting a less-than-obvious feature of CF to work properly. My bad. I realise I should know better by now.
d
Honestly I am not entirely against how it doesn't handle named parameters. They are basically 2 separate conceptual ways to handle arguments. Is there any examples of other languages handling names parameters with your assumption? That opinion also assumes documentation doesn't state it can handle named parameters.
a
I think it's unusual to use rest with named parameters, but, CF supports named parameters as well as positional so I would expect it to work. I'd settle for it being fully documented though as only working with positional.
a
My reasoning was remarkably similar. I started thinking "it's a bit unorthodox to use named arguments here", but then I stopped to think... why? And my conclusion was "because in other languages I use this operation there's no such thing as named arguments, so I've not been used to thinking about it", and that was the only reason I could come up with for me to think that. So I binned that thought (other than deciding to ask about it here). The thing is there's no good reason I can think of that named arguments should not work. One cannot mix named and positional arguments which would be one wrinkle, so it's 100% reliable to take a set of named arguments, and match the argument names to the parameter names in the method signature. There is no ambiguity: any args that have the same name as a param are assigned as that parameter value. All the rest - instead of being passed in as ad-hoc arguments - are handled by the
...
operation. I cannot see a way that there's any ambiguity either. It's 100% "match the named ones, pass all others in the rest param. What happens if the method call actually specifies a named argument that matched the name of the "rest" param? Same as if one specifies a positional argument in the position of the "rest" param: it doesn't matter. all arguments that don't match other named params are passed in the rest argument value. I also think that if for some reason named arguments are not supported for use on function using the rest operator, then an exception should be thrown; not simply the code being ignored. And whatever the behaviour is needs to be documented.
d
I have a feeling it is going to be a backward compatibility thing. Since named parameters can be passed through and the function have full access to them, how does one know wether it should be passed through or collected? I suppose that the language could treat the variables differently and categorize the arguments as if …rest then collect all unnamed variables else fall back on normal argument pass through but that may be a heavy lift on making that work. I also am not a fan of the rest argument being different types, because the caller is determining wether the …rest should be an array or it should be a struct with keys. Seems like a very big break in encapsulation.
a
I suppose that the language could treat the variables differently and categorize the arguments as if …rest then collect all unnamed variables else fall back on normal argument pass through
Well: exactly. This is how the rest operator works. It won't come as a surprise to anyone.
Copy code
function f(param1, param2, param3)
With ordered or named arguments: the behaviour is the same. All args (any number of them) passed go into their own slots (either index or name).
Copy code
function f(param1, param2, ...param3)
With ordered or named arguments: the behaviour is the same. First two args go with their position / name. Rest go... in the third one. The hint is in the name:
rest
operator. There is no ambiguity here.
I also am not a fan of the rest argument being different types
Absolutely. And that's controlled by the method signature:
Copy code
function f(required string param1, required numeric param2, ... date param3) // does not work in CF2021. Does not error, but ignores the rest operator
I need to call that method thus:
Copy code
f(param1="a string", param2=42, [any other named params you like but they need to be dates])
// or
f(arg3=needsToBeADate, arg3=alsoNeedsToBeADate, anyOtherArg=yupADate, param1="a string", param2=42)
It is also a code design decision. Not a language design one. And the intent is absolutely clear from the method signature.
Seems like a very big break in encapsulation.
That comment doesn't make any sense to me. ----
I have a feeling it is going to be a backward compatibility thing. Since named parameters can be passed through and the function have full access to them, how does one know wether it should be passed through or collected?
Because the method signature would specify the rest operator. You seem to think there some sort of magic going on to invoke this behaviour. The method literally needs to specify
...lastParam)
in the method signature. Or do you mean ppl that have taken a method like this:
Copy code
function f(this, that, ...everythingElse)
And call it like this:
Copy code
result = f(this="something", that="something else", thing="whatevs", sigh="ridiculous")
And just ignore the fact they were the ones who put the rest operator in the method signature in the first place, and the implementation of the method is intrinsically expecting all the rest of the args to be collected into one data structure_?_ You're suggesting this is actually a thing that would happen? You would have to implement a method actively specifying the rest operator and then explicitly implement the method to ignore it. Who would do that? Or let's say it's a third-party method that says something like:
Copy code
/**
@this - this thing
@that - that other thing
@everythingElse - everything else gets treated as an array
*/
function f(this, that, ...everythingElse)
And then some Fuckknuckle comes along and calls it like this:
Copy code
result = f(this="something", that="something else", thing="whatevs", sigh="ridiculous")
And expect to not collect
{thing="whatevs", sigh="ridiculous"}
into the
everythingElse
param value? Despite the docs and the method signature (and, intrinsically: the implementation) saying that's what it does. How would this come about? The mooted change here is to fix methods than use the rest operator so the methods internal logic can be implemented as needs must for named args. It doesn't impact the consuming code in any meaningful way. No method using the rest operator can currently support being called with named arguments. It doesn't work (see also: this entire conversation). So there's no backwards compat issues. Fixing it would change behaviour from "it doesn't work" to "it does work".
d
So I unfortunately don't have time to comment on everything here. May do it tonight but the encapsulation comment is the following. What I mean by breaking encapsulation is that by passing named parameters you are expecting the …rest to be a struct. So if I call the same function as
function(regarg, collected1,collected2)
I would get an arguments scope with a single key of regarg, and then an array of arguments in the rest key. But if I call it as
function(regarg=regarg, collected1=collected1,collected2=collected2)
I would get a single key with the regarg like the first but then I would get a struct (maybe an ordered struct assuming the name argument parsing parse/Lexing can maintain the order) as the second argument. So you have to write your function to handle both because you can't predict how developers will use the function.
I'd honestly rather have the half ass implemented …rest feature removed or left as order parameter only then risk the fundamental change to how parameters are written and see more issues arise. Tho that would keep you in the blogging game more I suppose 😜
a
So you have to write your function to handle both because you can't predict how developers will use the function.
This is a good point that I had not thought about. I suspect it would probably be solved by gathering them as a
coldfusion.runtime.ArgumentCollection
though (same type used for the arguments scope). This can be accessed via either index or key name. I think this is inkeeping with idiomatic CFML? It'd be transparent to most CFML devs ("It'd just work", and it would never occur to them to wonder why/how), and would not be "surprising" to users that are perhaps a bit more... discerning.
I'd honestly rather have the half ass implemented …rest feature removed or left as order parameter only then risk the fundamental change to how parameters are written and see more issues arise.
I'd prefer they threw an exception with named args and said "not supported", that just ignore it. Code should not be ignored. This also circles back you your - valid - point I quoted above... the dev currently kinda has to manually check if the function's been called as they would be intending (positional args) or by some twerp using named params.
d
Yes I would also be fine with exception thrown when calling an function of this nature with named parameters. That is going in line with not messing with it anymore but an extra step further. At minimum it should be better documented. I'm not sure if argument collection would truly solve my concern because your still assuming order in the named parameters case and depending on the function implementation it may have side effects. But then again I am not really a huge fan of this style of function writing so I can't come up with reasonable use cases other than some basic ones, where I'd prefer to just pass an array over letting …rest collect the args for me.
a
your still assuming order in the named parameters
That is already the case with named arguments and how they're exposed in the
ArgumentCollection
anyhow. It's no different from how it works now. This is what I see is the benefit of taking this approach: it's already well-trod and well-understood ground.
d
But does anyone ever access argument scope via index? I can't say I have ever seen that, and wouldn't think it would be easily understandable. So that would force the ...rest argument (sorry I don't have a better name) to always need to have proper argument names.
a
Yes they do. When the function signature doesn't specify a name, quite possibly because it's already a variadic function, just CFML doesn't have the syntax to show that it is. I do not follow your second sentence.
d
I can agree with devs already understand the concept, but I am not sure if solves the problem of forcing the function writer to write the code in a particular way depending on if the user uses named or not. If I were to support making it work with named parameters 2 things would be needed. 1. named parameters maintain their order consistently (Again a parsing/lexing issue which I am not sure is currently supported because I don't think it is currently needed). 2. the ...rest operator should just alway be an array and have that consistent order. So even if you passed named params it would just ignore the names.
When the function signature doesn't specify a name
Then why do we need to collect it since the argument scope is already a collected set.
I guess let me ask you, as a writer of the function. Would you ever access that rest argument as anything other than an array by looping over the index? Is there ever a safe way to inform the developer who only gets the signature of your function, what named parameters to use in case they have fusebox or some other framework/factory that always uses named parameters?
a
Is there ever a safe way to inform the developer who only gets the signature of your function, what named parameters to use in case they have fusebox or some other framework/factory that always uses named parameters?
Sorry, I'm not gonna get into dumb whataboutery like this. The same could be said of any function where the dev "only gets the signature of your function". "without docs, isn't it hard to work out how things work". Well... yes. [boggle] The thing is... variadic functions are really well-trod ground. This is not something Adobe invented. Everything you say above (immediately above, and a bunch of stuff in earlier comments, back when I had more patience) applies equally to the implementation of variadic functions in CFML without the rest operator. What we're trying to do here... given Adobe have decided to formalise syntax for variadic functions... is to work out how to implement them, and assess the shortcomings therein. Given they've done a half-arsed, unthinking job of it so far. Yer arguing the merits of variadic functions at all. I mean... fill yer boots... it's a discussion that someone might find has merit. But that's not what this thread is supposed to be about. (and in case you can't tell... I'm beginning to find it a bit exasperating and distracting)
d
Thats fine, I was just trying to tease out a real reason to have argumentcollection be the type for the rest argument over the plain and already implemented array. Hoping maybe you had some reasonable example that I couldn't think of.
a
For the same reason anyone might want to have data that is key/value pair. I dunno what else to say beyond that! Sometimes data points have labels. Given CFML allows named args when a function is called, then it needs to support this. So there's a question "with a variadic function that is called with named args (for whatever fucking reason), then it needs to support it". "pass it as an array pretending the names aren't they" is certainly a suggestion. I don't think it's a very good one. My specific usecase at the time was along these lines: I had this sort of thing:
Copy code
function inDiv(message, status) {
    writeOutput("<div class="#status#>")
    writeOutput(message)
    writeOutput("</div")
}
And I realised I need to chuck more attributes on that div, and on whim I thought "let's see how the rest operator will help me here", thinking I could do this sort of thing:
Copy code
function inDiv(message, status, ...divAttributes) {
    attrs = structReduce(divAttributes, (attrs, attr, value) => '#attrs# #attr#="#value#"', "")
    writeOutput("<div class="#status# #attrs#>")
    writeOutput(message)
    writeOutput("</div")
}
And call it thus:
Copy code
inDiv(message="Cool", status="OK", style="fancy", id="something", whatevs="can't remember now")
TBH I would be more likely to just make
divAttributes
a vanilla struct arg, but that's less interesting that trying new stuff. Plus also "oh I wonder how that would work?" is a reasonable question for someone wanting to improve themselves, I think.
Another thing I tried here in case it might work is this:
Copy code
function f(first, ...rest){
    writeDump(arguments) // [1,3,4]
}

f(first=1, rest=2, rest=3, other=4)
Which might've been a reasonable approach for the rest operator to worked with named arguments. But: no, didn't work