Here's an interesting question. Given the followi...
# cfml-general
b
Here's an interesting question. Given the following code
Copy code
<cfloop from="10" to="1" index="i">
    foo
</cfloop>
It currently loops zero times. • Should CF default the loop to
step="-1"
when the
from
is greater than the
to
? • How much existing code would break?
Replies here please....
z
this isn't RoR mate
🤭 2
1
b
The only reason I even thought of this was I found a bug in someones code where they had typed just that and apparently expected it to count down automatically... which caused me to ponder how reasonable that expectation was.
z
That should be an IDE warning?
b
Maybe, the reality is, it's probably going to be variables so the IDE won't know the values at runtime
d
I don't think it is that reasonable of an expectation. The script for loop requires the step of
1--
so to me it makes sense that tag requires it too.
b
In my case, it was
Copy code
loop from=array.len() to="1" ....
@Daniel Mejia Forget tag vs script. It's the same either way. I just used tags in the example.
a
I want to say "nooooo", but all my justifications for doing so are based on being dogmatic about "the way things are done". The only good reason i can think of is "it was a bug in their code. It surfaced easily. They fixed it". Is there any reason to suggest a feature and to "invest" two CFML teams' time to implement something in the language to work around some thoughtless code?
b
I don't know that I'd call it thoughtless per se. If I asked a kid to count from 10 to 1, they'd just count down without hesitation. Because that's what makes sense.
a
It's not a kid.
b
Now, that said, I'm not really in favor of it because I think there are real life scenarios like pagination where you legit may have a value where you just don't want it to loop.
d
haha true
z
But the unit test failed
a
It's a programming language. I don't think that's a compelling analogy
b
Yes, so you should be even smarter 🙂
I think it is-- the point is a programming language exists for the benifit of the human, not the computer. Which is why arrays should start at 1, because humans write code, not computers
If you can point to a natural way that humans think, it can be a valid basis for how a feature works. A basis mind you, not the final say...
a
I'm also slightly hesitant... but can't quite quantify why.... but in your example it's obvious what the code "ought" to do because you have
10
and
1
. But those are expressions, so I think when they're literals then having special "I guessed what you actually meant" behaviour is... questionable.
Also... it so easy to go "oh right. I need to specify the step size when it's not 1" is so easy to solve.
b
Agreed, what code "ought" to do is often times clouded by context we attribute in our minds that's not safe for the compiler to assume.
As I clarified, the real code used an array.len() for the from, and it was looping backwards so it could delete items from the array with out a moving target for the index.
a
I had some code last week when I had an array that I needed to be able to traverse both ways, and I initially wanted an
eachRight
function (akin to
reduceRight
). But then I went "stop being lazy Adam... reverse the array first, then just use `each`". I think this is similar. Something things are already easy enough, they don't need to be made easier.
BUT... like I started with...
I want to say "nooooo", but all my justifications for doing so are based on being dogmatic about "the way things are done".
I'm thinking "what's the harm?"
b
lol, yeah. I try to be pragmatic when possible, but the issue IMO is when you introduce ambiguity or edge cases where something valid no longer works. And Ruby has defo done that in a few places, like not having to quote strings in an array-- but only if you don't have whitespace!
a
I don't think it's a good use of anyone's time though.
If it was already there, I might go "yeah OK". But I'd not ask for it. Nor vote for it. But if it was implemented, I'd be "yeah, whatevs, cool I guess"
b
I think I feel the same way. it seemed interesting but also lazy at the same time 😆
a
If CFML had a range type (eg:
myRange = [10..1]
) I would probably expect it to iterate downwards if I iterated over it. So... how is this different?
🤔 1
d
now thats 2 new features. might be worth submitting a nice list of new features instead of just 1.
a
Other things I ask "does it make code less clear? Does it gloss over intent? Will it actually cause more problems than it solves cos suddenly poorly controlled code will be accidentally iterating the wrong way?" (etc)
2
I also think "how fuckin long would it take me to find bugs in both CF's and Lucee's implementation of this because ppl didn't think things through?"
d
#cfmlenhancement search would take days
a
I've said it before... I really don't think anyone should cite Ruby as an example of how things should be done. Less so RoR. (edit: actually that's an unfair blanket statement about Ruby... a lot of its core design seems nice, but it tries to solve too many things in too many ways out on the edges sometimes. RoR though... the whole thing is an anti-pattern from the outset IMO)
z
Ah the old why hasn't someone else done something I could again?
1
a
Oh Zac you mean "just fix ya code mate", instead of "could CFML do this for me pls"?
s
I may just be old and stuck in my ways (because I have been using cfloop for over 22 years), but I would not expect or ever want cfloop to automatically count backwards unless I specifically wanted it to by adding the step argument.
👍 3
I would imagine that if this were implemented we would all very likely just end up having the opposite problem, where it is unexpectedly looping backwards rather than unexpectedly not looping at all.... then we would probably all start a new habit of always adding a "step" argument to all our From/To cfloops to avoid ambiguity... which means now all our loops require us to type more, which will certainly lead to me being annoyed.... I would also imagine a lot of future rants from people like @Adam Cameron about how stupid that was of them to implement such a thing... just saying.
a
Oh god.
step="1"
everywhere. Kill me now. It's be the 2020s version of "closing" CFML tags.
😜 1
Could we not all blame Brad if this happened?
2
OK we're agreed. Implement it. Get some of it wrong in a dumbarse way so I get to write a whiny blog article about it, and we all blame Brad. You paying attn @Mark Takata (Adobe)?
🍻 4
Oh and it's essential that CF and Lucee implement it slightly differently. And Lucee should require a special Application.cfc setting for it to work.
this.loopCounter = "modern"
or something.
😂 2
s
Maybe if both of the from and to numbers are even, it should also assume that you want step=2... just because that might help one person one time someday maybe.
😜 1
a
this.loopCounter = "evenModerner"
🤣 1
m
This thread just made a baby panda someplace cry
s
And no one seemed to point out that
loop from=array.len() to 1
would run for an empty array (from 0 to 1 -- so it would run twice, except it would likely blow up on indexing by zero) and for a single-element array (from 1 to 1), so the original code is broken in multiple ways.
👍 1
s
message has been deleted
My guess is that they actually meant to do
loop from="1" to=array.len()
because that would make more sense
b
@Scott Bennett No, as I said above, they wanted to iterate over the array backwards to delete indexes without worrying about about them shifting.
d
I don't like that extra bit of "intelligence" there, at all. I know I've written code that calculates the start and/or end and/or step, so the loop does (or doesn't do) something, in some particular order, to achieve whatever it was I was after. You should be able to do that, without the engine second-guessing your intent. If start > end and step is positive, who's to say that that won't do what you meant it to do? Maybe this is a case where the loop SHOULD do nothing.
3
s
ah, I must have been reading too fast
couldn't they have just done ArrayClear() then?
b
Because they didn't want to delete ALL the items 🙂
s
or I suppose there was something being done
sorry... there was very little context
b
Don't get hung up on the why, the point was they wanted to loop "down". The context doesn't really matter
s
yeah, well I stand by my original comment
👍 1
ignore my tendancy to want to get hung up on the why
s
I think your original sentiment was fine: it would be bad for the loop direction to magically change based on
from > to
and it could well break a lot of code that expects such a situation to not loop!
👍 4
a
This thread just made a baby panda someplace cry
My work here is done.
m
my vote is 'no' cf should not default the step to -1 in the given scenario.
2
👍 2
a
@seancorfield did I mention in "other channels" that I looked at Lucee's cock-a-mamie (how is that spelt?) notion that structs with numeric indexes should be iterable as arrays?
s
Hahaha... I don't think I've seen that thread yet... how... "helpful"...
a
Trying to find the code for the test I tried on it. It involved and index of
0
in the struct though. You can guess how well-prepared Lucee was for that...
Copy code
<cfscript>
// control
st = {1="one", 2="two", 3 ="three"}

result = arrayMap(st, (v, i) => "#i#:#v#")
writeOutput(serializeJson(result)) // ["1:one","2:two","3:three"]

writeOutput("<br>")

// example
st = {0="zero", 1="one", 2="two", 3 ="three"}

try {
    result = arrayMap(st, (v, i) => "#i#:#v#")
    writeDump(result)
} catch (any e) {
    writeOutput(serializeJson([e.message, e.detail])) // ["can not set Element at position [0]","Index -1 out of bounds for length 3"]
}
</cfscript>
Full disclosure: I ran this on
5.3.7.47
. It seems to be fixed in the current version.
That was the Very. First. Thing. I tested when I found out about this "feature".
I take it back. It's still broke on
5.3.9.133
b
Are you expecting the error to validate that the struct will not work as a valid array?
a
I'm expecting the devs of Lucee to not implement stupid features they don't think through
a struct is not an array a struct with integer indexes is not an array don't implement "features" that don't get those first two points.
s
And ACF does what with the above?
a
It says "fuck off that's not an array"
Ooops, not quite that. "Object of type class coldfusion.runtime.Struct cannot be used as an array on line 5"
I knew it was something like that
b
The feature doesn't really bother me to be honest. I think this is just a validation issue
here's Lucee's code that tests if a struct can be used as an array
Copy code
while (it.hasNext()) {
	k = it.next();
	if (!Decision.isInteger(k.getString())) throw new ExpressionException("can't cast struct to an array, key [" + k.getString() + "] is not a number");
}
You can see isInteger() is a little too broad of a net since
0
will pass that
1
That's in the
StructAsArray.java
class
s
So, wait, it loops over the struct first, just to decide whether to treat something as an array????
b
Yes, if you have a struct where all keys are an integer, Lucee will let you pass it into an array function
s
That is so f'ing stupid... Good grief!
😀 1
a
@bdw429s without testing, what does this end up with:
Copy code
st = [1="one", 4="four", 3 ="three"]

result = arrayMap(st, (v, i) => "#i#:#v#")
writeOutput(serializeJson(result))
b
If I recall, indexes not explicitly set are null
s
I was just about to ask that same question
b
Which isn't really any different than typing
Copy code
arr=[]
arr[2]='foo'
a
What they could have done, and would seem legit to me, is if they exposed the type that arguments collections are as something that could be created. As it exposes its collection as both indexed and by key (if poss).
["1:one",null,"3:three","4:four"]
But I guess: more fool the writer of the code for passing in an integer-keyed ordered struct that is not ordered numerically into an array function.
🤯 1
At the same time... it's so stupid I even had to type that.
s
And it's the same for
st = {1="one", 4="four", 3 ="three"}
and also for
st = {"1"="one", "4"="four", "3" ="three"}
which categorically ought to be the equivalent of
Map<String,String>
... sigh
b
Yeah, I've always been curious to the impetus for this behavior. Like i said, it doesn't bother me too much given all the crazy stuff the Caster does to turn thigns into a usable array, but I don't think I've ever consciously used this.
s
I suppose we should be glad that at least this fails:
Copy code
st = createObject("java","java.util.HashMap");
st.put("2","two");
st.put("3","three");
st.put("5","five");
result = arrayMap(st, (v, i) => "#i#:#v#") // Can't cast Object type [java.util.HashMap] to a value of type [Array] on line 32
because, you know, heaven forbid that Lucee treats HashMap like Struct 🤣
😛 1
a
I only found out about it as I accidentally typed
{}
when I meant to type
[]
in
myArray.append({}, true)
and it worked fine in
5.3.7.47
but broke in
5.3.9.133
, and I started to dig into "WTF, Lucee?"
This is analogous to the backwards looping question. Sometimes trying to be helpful just... isn't.
b
Hah, interesting. Lucee's code path for converting a struct to an array is
Copy code
else if (o instanceof Struct) {
so it must be a CFML struct, a
Map
won't do.
a
It's OK for ppl to have to write correct code.
s
But a HashMap is enough of a Struct for this to work:
Copy code
st = createObject("java","java.util.HashMap");
st.put("2","two");
st.put("3","three");
st.put("5","five");
result = structMap(st, (v, i) => "#i#:#v#")
writeOutput("struct: " & serializeJson(result))
b
Yeah, I agree that's inconsistent
s
This is quite a rambling-rabbit-hole of a thread
a
😐
This is what happens when I have beer but no material for a blog article and nothing better to do of an evening.
s
Missing the good old days when you could do things like this when you were bored?
a
HA!
m
RIP my phone battery lol