J: Why does `f^:proposition^:_ y` stand for a while loop?
Asked Answered
M

2

5

As title says, I don't understand why f^:proposition^:_ y is a while loop. I have actually used it a couple times, but I don't understand how it works. I get that ^: repeats functions, but I'm confused by its double use in that statement.

I also can't understand why f^:proposition^:a: y works. This is the same as the previous one but returns the values from all the iterations, instead of only the last one as did the one above.

a: is an empty box and I get that has a special meaning used with ^: but even after having looked into the dictionary I couldn't understand it.

Thanks.

Mir answered 8/9, 2013 at 9:52 Comment(0)
O
3

f^:proposition^:_ is not a while loop. It's (almost) a while loop when proposition returns 1 or 0. It's some strange kind of while loop when proposition returns other results.

Let's take a simple monadic case.

f =: +:        NB. Double
v =: 20 > ]    NB. y less than 20

(f^:v^:_) 0     NB. steady case
0
(f^:v^:_) 1     NB. (f^:1) y, until (v y) = 0
32
(f^:v^:_) 2
32
(f^:v^:_) 5
20
(f^:v^:_) 21   NB. (f^:0) y
21

This is what's happening: every time that v y is 1, (f^:1) y is executed. The result of (f^:1) y is the new y and so on.

  • If y stays the same for two times in a row → output y and stop.
  • If v y is 0→ output y and stop.

So f^:v^:_ here, works like double while less than 20 (or until the result doesn't change)

Let's see what happens when v returns 2/0 instead of 1/0.

 v =: 2 * 20 > ]

(f^:v^:_) 0      NB. steady state
0
(f^:v^:_) 1      NB. (f^:2) 1 = 4 -> (f^:2) 4 = 16 -> (f^:2) 16 = 64 [ -> (f^:0) 64 ]
64
(f^:v^:_) 2      NB. (f^:2) 2 = 8 -> (f^:2) 8 = 32 [ -> (f^:0) 32 ]
32
(f^:v^:_) 5      NB. (f^:2) 5 = 20 [ -> (f^:0) 20 ]
20
(f^:v^:_) 21     NB. [ (f^:0) 21 ]
21

You can have many kinds of "strange" loops by playing with v. (It can even return negative integers, to use the inverse of f).

Outskirts answered 8/9, 2013 at 12:51 Comment(1)
Thank you! Your last line enlightened me! So the result of v specifies how many times repeat f (and if it's negative, you get the inverse of f). Perfect! I also understood why a: works reading again the dictionary. Basically it translates into f^:i.k for some integer k and that's why you get multiple results: an array is passed to ^:.Mir
L
6

Excerpted and adapted from a longer writeup I posted to the J forums in 2009:

while =:  ^:break_clause^:_

Here's an adverb you can apply to any code (which would equivalent of the loop body) to create a while loop. In case you haven't seen it before, ^: is the power conjunction. More specifically, the phrase f^:n y applies the function f to the argument y exactly n times. The count n maybe be an integer or a function which applied to y produces an integer¹.

In the adverb above, we see the power conjunction twice, once in ^:break_clause and again in ^:_ . Let's first discuss the latter. That _ is J's notation for infinity. So, read literally, ^:_ is "apply the function an infinite number of times" or "keep reapplying forever". This is related to a while-loop's function, but it's not very useful if applied literally.

So, instead, ^:_ and its kin were defined to mean "apply a function to its limit", that is, "keep applying the function until its output matches its input". In that case, applying the function again would have no effect, because the next iteration would have the same input as the previous (remember that J is a functional language). So there's no point in applying the function even once more: it has reached its limit.

For example:

   cos=:  2&o.   NB. Cosine function
   pi =:  1p1    NB. J's notation for 1*pi^1 analogous to scientific notation 1e1

   cos pi
_1
   cos cos cos pi
0.857553
   cos^:3 pi
0.857553
   cos^:10 pi
0.731404
   cos^:_ pi  NB.  Fixed point of cosine
0.739085

Here, we keep applying cosine until the answer stops changing: cosine has reached its fixed point, and more applications are superfluous. We can visualize this by showing the intermediate steps:

   cos^:a: pi
3.1415926535897 _1 0.54030230586813 ...73 more... 0.73908513321512 0.73908513321

So ^:_ applies a function to its limit. OK, what about ^:break_condition? Again, it's the same concept: apply the function on the left the number of times specified by the function on the right. In the case of _ (or its function-equivalent, _: ) the output is "infinity", in the case of break_condition the output will be 0 or 1 depending on the input (a break condition is boolean).

So if the input is "right" (i.e. processing is done), then the break_condition will be 0, whence loop_body^:break_condition^:_ will become loop_body^:0^:_ . Obviously, loop_body^:0 applies the loop_body zero times, which has no effect.

To "have no effect" is to leave the input untouched; put another way, it copies the input to the output ... but if the input matches the output, then the function has reached its limit! Obviously ^:_: detects this fact and terminates. Voila, a while loop!


¹ Yes, including zero and negative integers, and "an integer" should be more properly read as "an arbitrary array of integers" (so the function can be applied at more than one power simultaneously).

Lint answered 8/9, 2013 at 16:12 Comment(2)
And if the integer argument to f^: is _1 ... you get the inverse of the function f. A quick way to define the inverse of a function!Infiltration
Thank you! Nice explanation and the link you provided was interesting as well.Mir
O
3

f^:proposition^:_ is not a while loop. It's (almost) a while loop when proposition returns 1 or 0. It's some strange kind of while loop when proposition returns other results.

Let's take a simple monadic case.

f =: +:        NB. Double
v =: 20 > ]    NB. y less than 20

(f^:v^:_) 0     NB. steady case
0
(f^:v^:_) 1     NB. (f^:1) y, until (v y) = 0
32
(f^:v^:_) 2
32
(f^:v^:_) 5
20
(f^:v^:_) 21   NB. (f^:0) y
21

This is what's happening: every time that v y is 1, (f^:1) y is executed. The result of (f^:1) y is the new y and so on.

  • If y stays the same for two times in a row → output y and stop.
  • If v y is 0→ output y and stop.

So f^:v^:_ here, works like double while less than 20 (or until the result doesn't change)

Let's see what happens when v returns 2/0 instead of 1/0.

 v =: 2 * 20 > ]

(f^:v^:_) 0      NB. steady state
0
(f^:v^:_) 1      NB. (f^:2) 1 = 4 -> (f^:2) 4 = 16 -> (f^:2) 16 = 64 [ -> (f^:0) 64 ]
64
(f^:v^:_) 2      NB. (f^:2) 2 = 8 -> (f^:2) 8 = 32 [ -> (f^:0) 32 ]
32
(f^:v^:_) 5      NB. (f^:2) 5 = 20 [ -> (f^:0) 20 ]
20
(f^:v^:_) 21     NB. [ (f^:0) 21 ]
21

You can have many kinds of "strange" loops by playing with v. (It can even return negative integers, to use the inverse of f).

Outskirts answered 8/9, 2013 at 12:51 Comment(1)
Thank you! Your last line enlightened me! So the result of v specifies how many times repeat f (and if it's negative, you get the inverse of f). Perfect! I also understood why a: works reading again the dictionary. Basically it translates into f^:i.k for some integer k and that's why you get multiple results: an array is passed to ^:.Mir

© 2022 - 2024 — McMap. All rights reserved.