Figured it out. The two bits I was stuck on I found solutions to almost as soon as I'd published that last post: `Zip` for joining the two lists and I was very close to sorting the nested lists, I was just missing the `unary2` combinator.

The hardest thing about Joy is figuring out functions with multiple arguments that are used multiple times and manipulating the stack to suit. Take, for example, this bit of code I'd figured out which takes a list as an argument and uses it twice:

``````DEFINE
ratio ==
dup
[34.0 swap /] map
swap
zip
[[34.0] concat] map.
``````

So when applied to a list of sprockets it gives the following results:

``````>[12 13 15 17 19 21 23 26] ratio.
>[[2.83333 12 34] [2.61538 13 34] [2.26667 15 34] [2 17 34] [1.78947 19 34] ... ]
``````

Step by step, this is what happens on the stack:

``````(* Stack: [12 13 15 17 19 21 23 26] *)

dup

(* Stack: [12 13 15 17 19 21 23 26] [12 13 15 17 19 21 23 26] *)

[34.0 swap /] map

(* Stack: [12 13 15 17 19 21 23 26] [2.83333 2.61538 2.26667 2 1.78947 1.61905 1.47826 1.30769] *)

swap

(* Stack: [2.83333 2.61538 2.26667 2 1.78947 1.61905 1.47826 1.30769] [12 13 15 17 19 21 23 26] *)

zip

(* Stack: [[2.83333 12] [2.61538 13] [2.26667 15] [2 17] [1.78947 19] [1.61905 21] ] *)

[[34.0] concat] map.

(* Stack: [[2.83333 12 34] [2.61538 13 34] [2.26667 15 34] ... ] *)
``````

`dup` is used to create two copies of the list argument to the programme and after the first one has been used (`[34.0 swap /] map`) the stack is swapped around so we can get to the second one. The `[34.0 swap /] map` bit I kind of explained last time: `[34.0 /] map` takes each item in the list and "divides by 34.0" so using swap just means it does "34.0 divided by" each item in the list. The `[[34.0] concat] map` adds on the `34.0` to what is a list of pairs at this point to create a list of triples.

All well and good, but what if I want the `34.0` as an argument to the programme as well as the list? It's used twice and inside a list both times! This would be a piece of cake in any other language as we just make it a variable, but we can't do that with Joy, we've got to introduce it onto the stack along with the list and then manipulate the stack to form the functions we need. This requires more manipulation of the stack than `swap`:

``````ratios ==

(* Stack: 34.0 [12 13 15 17 19 21 23 26] *)

dup

(* Stack:  34.0 [12 13 15 17 19 21 23 26] [12 13 15 17 19 21 23 26] *)

rotate

(* Stack: [12 13 15 17 19 21 23 26] [12 13 15 17 19 21 23 26] 34.0 *)

dup

(* Stack: [12 13 15 17 19 21 23 26] [12 13 15 17 19 21 23 26] 34.0 34.0 *)

unitlist [swap /] concat

(* Stack: [12 13 15 17 19 21 23 26] [12 13 15 17 19 21 23 26] 34.0 [34.0 swap /] *)

rolldown swap

(* Stack: [12 13 15 17 19 21 23 26] 34.0 [12 13 15 17 19 21 23 26] [34.0 swap /] *)

map

(* Stack: [12 13 15 17 19 21 23 26] 34.0 [2.83333 2.61538 2.26667 2 1.78947 1.61905 1.47826 1.30769] *)

rolldown

(* Stack: 34.0 [2.83333 2.61538 2.26667 2 1.78947 1.61905 1.47826 1.30769] [12 13 15 17 19 21 23 26] *)

zip

(* Stack: 34.0 [[2.83333 12] [2.61538 13] [2.26667 15] [2 17] [1.78947 19] [1.61905 21]  ... ] *)

swap

(* Stack: [[2.83333 12] [2.61538 13] [2.26667 15] [2 17] [1.78947 19] [1.61905 21]  ... ] 34.0 *)

unitlist [unitlist concat] concat

(* Stack: [[2.83333 12] [2.61538 13] [2.26667 15] [2 17] [1.78947 19] [1.61905 21]  ... ] [34.0 unitlist concat] *)

map.

(* Stack: [[2.83333 12 34] [2.61538 13 34] [2.26667 15 34] [2 17 34] [1.78947 19 34] [1.61905 21 34] ... ] *)
``````

Here we use `rolldown`, `rotate` and `swap` to manipulate the stack as required. Whereas `swap` only works on the top two items on the stack, `rolldown` and `rotate` work on the top three, giving us a bit more reach. There are other operators as well, some that re-order up to four items on the stack.

The other function that needs mentioning here is `unitlist`. This simply takes whatever is on the stack and wraps it in a list - this is how we get the `34.0` from being on the stack to inside a list. The same would be true if we'd defined a constant we wanted to use inside a list:

``````DEFINE

gear == 34.
``````

If we wanted to use this like so:

``````>[[1 2] [3 4] [5 6]] [[gear] concat] map.
``````

Expecting we'd get:

``````>[[1 2 34] [3 4 34] [5 6 34]]
``````

We'd actually get:

``````>[[1 2 gear] [3 4 gear] [5 6 gear]]
``````

Because `gear` is wrapped in the list it doesn't get executed. It actually gets concatenated as a function. By using `unitlist` we can execute `gear` on the stack and then wrap it in a list:

``````>[[1 2] [3 4] [5 6]] [gear unitlist concat] map.
``````

Taking away the plethora of comments you actually end up with a reasonably concise programme, but it takes a bigger brain than mine not to have a running commentary on the stack.

For comparison, in Ruby I'd use a looping construct and the whole thing is a bit more succinct (and could probably be made more so):

``````def joycog(biggears, littlegears)
triples = []
biggears.each do |bg|
#Calc ratios
ratios = littlegears.map{ |lg| bg.to_f/lg }
ratios = ratios.zip(littlegears)
triples.concat(ratios.map{ |pair| pair.concat([bg]) })
end
triples.sort
end
``````

The end result of all this? This is the actual progression of gears on my road bike:

1. 1.30769 26 34
2. 1.47826 23 34
3. 1.61905 21 34
4. 1.78947 19 34
5. 1.92308 26 50
6. 2.00000 17 34
7. 2.17391 23 50
8. 2.26667 15 34
9. 2.38095 21 50
10. 2.61538 13 34
11. 2.63158 19 50
12. 2.83333 12 34
13. 2.94118 17 50
14. 3.33333 15 50
15. 3.84615 13 50
16. 4.16667 12 50

Even discounting gear combinations at the extreme of chain angles (which is an exercise for another day as far as doing it in Joy goes), there is still more overlap than I realised:

1. 1.30769 26 34
2. 1.47826 23 34
3. 1.61905 21 34
4. 1.78947 19 34
5. 2.00000 17 34
6. 2.17391 23 50
7. 2.26667 15 34
8. 2.38095 21 50
9. 2.61538 13 34
10. 2.63158 19 50
11. 2.94118 17 50
12. 3.33333 15 50
13. 3.84615 13 50
14. 4.16667 12 50

In practice though changing gear like this is not going to happen. Rather I find it's a case of picking the front chain ring to suit the conditions (hills and silly headwinds for the 34) and only shifting to the other one when you run out of room on the cassette.