Simulate a For Loop (Reference position inside FormulaMap)

So I was struggling with a problem where I needed to evaluate multiple lists simultaneously. A classic use case for a loop.

Coda doesn’t have built-in formulas for classic loops such as for or while. The closest thing being FormulaMap. It takes a list as input and runs every item through an expression. Inside the expression, you use CurrentValue to reference the current item being evaluated. As a result, CurrentValue can be of any type (row, date, etc).

But there’s no obvious way to get the key, or the item position inside the list.

Unless…

You make CurrentValue to be exactly the same as its position! You can do that using Sequence to create an auxiliary list.

Sequence(1,finalValue) // =[1,2,3...finalValue]

Then run FormulaMap over this list.

Sequence(1, finalValue).FormulaMap(CurrentValue*2) // =[2,4,6...finalValue*2]

Applying it to my case:

//List1 = [1,2,3]
//List2 = [5,7,1]
Sequence(1, List1.Count()).FormulaMap(List1.Nth(CurrentValue)+List2.Nth(CurrentValue)) // =[6,9,4]

The more general form would be:

Sequence(initialValue, finalValue, step).FormulaMap(expression(CurrentValue))

More or less equivalent to

for (i = initialValue; i <= finalValue; i+=step) {
  expression // use i here
}

Since CurrentValue is a number from the sequence, it serves the purpose of the i.

23 Likes

Very nice writeup! I use this trick a lot and this a very clear writeup of the technique.

Btw as a sidenote, we have an ongoing debate about (1) the naming of FormulaMap and (2) if we were to add a more generic For or While loop syntax, how would it work? Got any ideas?

6 Likes

@shishir

@Lloyd_Montgomery and I recently discussed FormulaMap():

I run into these context issues often, and usually have to rely on trial and error to arrive at a solution, without ever truly understanding why that solution works and another doesn’t.

Would appreciate some official clarification on this!! :smiley:


Full discussion here: Automatic Scheduling of Todos - #4 by Lloyd_Montgomery

2 Likes

I’ll get to the syntax at the bottom, but conceptually I believe a for loop would have to be an ⚡Action, since it doesn’t return anything.

(After writing that I realize now I don’t think a loop would be a better solution for my initial problem in this post - the better one would be having the key keyword inside FormulaMap).


There are a few problems with iterative formulas in Coda regarding scope and evaluation of values. What @Ander pointed out is definitely one.

Another one is they all use the same alias for CurrentValue, so when you nest them you lose the parent’s context. I guess this could be solved by allowing we to call their parent, as in parent.CurrentValue and parent.parent....


Finally, a loop would also need to modify external data in real time. Correct me if I’m wrong, but the way I see it FormulaMap (or every other formula) makes a local copy of its arguments and evaluates everything then returns a result. So you can’t make a step rely on the result of the previous one.

For example, take this table

image

and add this to a button

Sequence(2,10).FormulaMap(AddRow(Table12, Column 1, Table12.Column 1.Nth(CurrentValue-1).Power(2)))

Initially you’d expect to run AddRow 9 times, squaring each previous row, but actually the sequence halts after the first step, and you get

image

That is even explicit when you edit the formula, it evaluates to a list of actions with only one valid action

image

Which becames obvious when you get it that every time Nth() is evaluated Table 12 contains only one row.


After all that am I wrong in extrapolating that every reference to a table or data inside a formula is not really a pointer to the real thing but just a local snapshot? (The exception being as the first parameter of an Action). This would be the root of lots of confusing moments.

Maybe a pointer syntax would be good then (although very resource demanding?) or maybe I’m overcomplicating things…

I really like the intuitive way we use functions without having to name variables and arguments, it’s one of the appealing features of Coda. I can see why you would avoid it. So the syntax problem is a real one for you devs haha.

1 Like

Just posted a more straightforward way to make loops (which is also a while loop):

Did you ever find a solution for referencing a parent CurrentValue in a nested FormulaMap?

1 Like

I’m struggling with this exact problem right now.

Sometimes I can do it by exploiting this bug, but it’s far from ideal.

I’m trying to do some hefty nested FormulaMap now, with FormulaMap inside FormulaMap inside FormulaMap to generate rows based on three different tables. I’m struggle quite a bit and that lead me to think about how I would like it to work. This is the syntax I would like to have to do what I describe above, and it addresses your second question. As for the first section I would just call it Map or Iterate since that is what is going on.

Here’s how I would like it to work:

TableA.FormulaMap(ta -> TableB.FormulaMap(tb -> TableC.FormulatMap(tc -> addRow(TableX, c1, ta.c1, c2, tb.c2, c3, tc.c3))))

another example:

TableA.FormulaMap(ta -> ta.SomeList.FormulaMap(x -> doSomethingWithTaAndX(ta, x))

From a programming standpoint leveraging lambdas, so the t_ will be a named variable in each iteration which makes it super clear what you are using. This is way more expressive compared to for loops in my opinion and removes the boiler plate you need for a for loop.

The solution today that everything with the CurrentValue approach is getting hard to understand when doing more complex things like in my case.

4 Likes
3 Likes

So, this is exactly what I was looking for so that I could have multiple users looking at different views of my app without having to update any queries (I just add them to a table). I’m able to get the formulamap and sequence to run my query (great!). What isn’t working is that when formulamap runs through its sequence (in this case 3 times) and outputs 1 true and 2 false. This creates those annoying little commas next to my content. I think running the query 3 times is also breaking actions in buttons. Any ideas on how to avoid getting the “false” results? I know I can use “” instead of false(), but that still leaves the empty spaces with commas in the doc.

Disco screen for formulamap output

Disco screen for formulamap

Could you chain this to the end of your formula:
.listCombine().filter(currentValue!=false()).concatenate()

Im thinking (without being in your doc) that the above chain would

  • Flattern your list of lists to a single list
  • Remove all false() values
  • then the concatenate would turn it from a list to just a string of values with no commas

You could also maybe try join() instead of concatenate and join each value with a desired character?

1 Like

Awesome, thank you! It seems to work, but only when I attach to the end of the formulamap statement. It does throw an error in the editor, but accomplishes the task of removing the false values.
Screen Shot 2022-01-26 at 2.02.37 PM
Screen Shot 2022-01-26 at 2.01.37 PM

1 Like

And note I did have to remove the concatenate on this particular field because I needed the tags to be distinct from one another (they link to other sections of the doc). That’s likely why I’m getting that error (but still works!).