From 8389e763344637c01d0d7161091e5f2cd9b14251 Mon Sep 17 00:00:00 2001 From: Marshall Lochbaum Date: Mon, 27 Jun 2022 22:00:55 -0400 Subject: Yet still more editing --- docs/doc/control.html | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) (limited to 'docs/doc/control.html') diff --git a/docs/doc/control.html b/docs/doc/control.html index fbd5f0c5..df625e47 100644 --- a/docs/doc/control.html +++ b/docs/doc/control.html @@ -5,10 +5,10 @@

Control flow in BQN

-

BQN does not have ALGOL-style control structures. Instead, functional techniques can be used to control when code is evaluated. This page describes how BQN functionality can be used to emulate something more familiar to an imperative programmer.

+

BQN does not have ALGOL-style control structures. Instead, functional techniques can be used to control when code is evaluated. This page describes how BQN functionality can be used to emulate something more familiar to an imperative programmer.

Control structures here are always functions that act on lists of functions, although alternatives might be presented. This is because stranded functions can be formatted in a very similar way to blocks in curly-brace languages. However, there are many ways to write control flow, including simple operators and a mix of operators and more control-structure-like code. Implementing a control structure rarely takes much code with any method, so there are usually several simple ways to implement a given flow or a variation of it.

The surfeit of ways to write control structures could be a bit of an issue for reading BQN. My hope is that the community can eventually settle on a smaller set of standard forms to recommend so that you won't have to recognize all the variants given here. On the other hand, the cost of using specialized control structures is lower in a large project without too many contributors. In this case BQN's flexibility allows developers to adapt to the project's particular demands (for example, some programs use switch/case statements heavily but most do not).

-

The useful control structures introduced here are collected as shortened definitions below. While uses the slightly more complicated implementation that avoids stack overflow, and DoWhile and For are written in terms of it in order to share this property. The more direct versions with linear stack use appear in the main text.

+

The most useful control structures introduced here are collected as shortened definitions below. While uses the slightly more complicated implementation that avoids stack overflow, and DoWhile and For are written in terms of it in order to share this property. The more direct versions with linear stack use appear in the main text.

If      โ† {๐•โŸ๐•Ž@}ยด                 # Also Repeat
 IfElse  โ† {cโ€ฟTโ€ฟF: cโ—ถFโ€ฟT@}
 While   โ† {๐•ฉ{๐”ฝโŸ๐”พโˆ˜๐”ฝ_๐•ฃ_๐”พโˆ˜๐”ฝโŸ๐”พ๐•ฉ}๐•จ@}ยด  # While 1โ€ฟ{... to run forever
@@ -18,23 +18,23 @@
 # Switch/case statements have many variations; these are a few
 Match   โ† {๐•๐•จ}ยด
 Select  โ† {(โŠ‘๐•ฉ)โ—ถ(1โ†“๐•ฉ)@}
-Switch  โ† {cโ†โŠ‘๐•ฉ โ‹„ mโ€ฟaโ†<ห˜โ‰โˆ˜โ€ฟ2โฅŠ1โ†“๐•ฉ โ‹„ (mโŠธโАโŒพ<C)โ—ถa@}
+Switch  โ† {cโ†โŠ‘๐•ฉ โ‹„ [m,a]โ†โ‰โˆ˜โ€ฟ2โฅŠ1โ†“๐•ฉ โ‹„ (mโŠธโАโŒพ<C)โ—ถa@}
 Test    โ† {fnโ†{Cโ€ฟA๐•Še:Cโ—ถAโ€ฟE}ยด๐•ฉโ‹„Fn@}
 

Blocks and functions

-

Control structures are generally defined to work with blocks of code, which they might skip, or execute one or more times. This might sound like a BQN immediate block, which also consists of a sequence of code to execute, but immediate blocks are always executed as soon as they are encountered and can't be manipulated the way that blocks in imperative languages can. They're intended to be used with lexical scoping as a tool for encapsulation. Instead, the main tool we will use to get control structures is the block function.

+

Control structures are generally defined to work with blocks of code, which they might skip, or execute one or more times. This might sound like a BQN immediate block, which also consists of a sequence of code to execute, but immediate blocks are always executed as soon as they are encountered and can't be manipulated the way that blocks in imperative languages can. They're intended to be used with lexical scoping as a tool for encapsulation. Instead, the main tool we will use to get control structures is the block function.

Using functions as blocks is a little outside their intended purpose, and the fact that they have to be passed an argument and are expected to use it will be a minor annoyance. The following conventions signal a function that ignores its argument and is called purely for the side effects:

-

Even with these workarounds, BQN's "niladic" function syntax is quite lightweight, comparing favorably to a low-boilerplate language like Javascript.

+

Even with these workarounds, BQN's "niladic" function syntax is lightweight, comparing favorably to a low-boilerplate language like Javascript.

fn = ()=>{m+=1;n*=2}; fn()
 Fn โ† {๐•คโ‹„  m+โ†ฉ1,nร—โ†ฉ2}, Fn @
 

Control structures are called "statements" below to match common usage, but they are actually expressions, and return a value that might be used later.

If

-

The if statement conditionally performs some action. It is similar to the Repeat (โŸ) modifier with a right operand returning a boolean: FnโŸCond ๐•ฉ gives Fn ๐•ฉ if Cond ๐•ฉ is 1, and returns ๐•ฉ without calling Fn if Cond ๐•ฉ is 0. Here is how we might make it behave like a control structure.

+

The if statement conditionally performs some action. It's similar to the Repeat (โŸ) modifier with a right operand that returns a boolean: FnโŸCond ๐•ฉ gives Fn ๐•ฉ if Cond ๐•ฉ is 1, and returns ๐•ฉ without calling Fn if Cond ๐•ฉ is 0. Here's how we might make it behave like a control structure.

{๐•คโ‹„a+โ†ฉ10}โŸ(a<10) @
 

The condition a<10 is always evaluated, so there's no need to wrap it in a function. However, the function {๐•คโ‹„a<10} could be used in place of (a<10), making the entire structure into a function that could be incorporated into other control structures.

@@ -48,21 +48,21 @@ }

The result of any of these if statements is the result of the action if it's performed, and otherwise it's whatever argument was passed to the statement, which is @ or 10 here.

-

BQN's syntax for a pure if statement isn't so good, but predicates handle if-else statements nicely. So in most cases you'd forego the definitions above in favor of an if-else with nothing in the else branch:

+

BQN's syntax for a pure if statement isn't so good, but predicates handle if-else statements nicely. So in most cases you'd forego the definitions above in favor of an if-else with nothing in the else branch:

{ a<10 ? a+โ†ฉ10 ; @ }
 

Repeat

There's no reason the condition in an if statement from the previous section has to be boolean: it could be any natural number, causing the action to be repeated that many times. If the action is never performed, the result is the statement's argument, and otherwise it's the result of the last time the action was performed.

Another option is to use a for-each statement with an argument of โ†•n: in this case the result is the list of each action's result.

If-Else

-

In most cases, the easy way to write an if-else statement is with predicates:

+

In most cases, the easy way to write an if-else statement is with a predicate:

{
   threshold < 6 ?
   a โ†ฉ Small threshold ;  # If predicate was true
   b โ†ฉ 1 Large threshold  # If it wasn't
 }
 
-

We might also think of an if-else statement as a kind of switch-case statement, where the two cases are true (1) and false (0). As a result, we can implement it either with Choose (โ—ถ) or with case headers of 1 and 0.

+

We might also think of an if-else statement as a kind of switch-case statement, where the two cases are true (1) and false (0). As a result, we can implement it either with Choose (โ—ถ) or with case headers of 1 and 0.

When using Choose, note that the natural ordering places the false case before the true one to match list index ordering. To get the typical if-else order, the condition should be negated or the statements reversed. Here's a function to get an if-else statement by swapping the conditions, and two ways its application might be written.

IfElse โ† {condโ€ฟTrueโ€ฟFalse: condโ—ถFalseโ€ฟTrue @}
 
@@ -99,7 +99,7 @@
 Test โŸจ
   (  a<b)โ€ฟ{๐•คโ‹„a+โ†ฉ1}
   {๐•คโ‹„a<c}โ€ฟ{๐•คโ‹„c-โ†ฉ1}
-  {๐•คโ‹„a-โ†ฉ2}
+          {๐•คโ‹„a-โ†ฉ2}
 โŸฉ
 

Switch-Case

@@ -114,7 +114,7 @@ ๐•ฉ: nโˆพโ†ฉ๐•ฉ } -

A simplified version of a switch-case statement is possible if the cases are natural numbers 0, 1, and so on. The Choose (โ—ถ) modifier does just what we want. The Select statement below generalizes IfElse, except that it doesn't rearrange the cases relative to Choose while IfElse swaps them.

+

A simplified version of a switch-case statement is possible if the cases are natural numbers 0, 1, and so on. The Choose (โ—ถ) modifier does just what we want. The Select statement below generalizes IfElse, except that it doesn't rearrange the cases relative to Choose while IfElse swaps them.

Select โ† {(โŠ‘๐•ฉ)โ—ถ(1โ†“๐•ฉ)@}
 
 Select numberโ€ฟ{
@@ -126,7 +126,7 @@
 }
 

To test against other possible values, the following statement takes interleaved lists of values and actions, and disentangles them. It searches through the values with โА.

-
Switch โ† {cโ†โŠ‘๐•ฉ โ‹„ mโ€ฟaโ†<ห˜โ‰โˆ˜โ€ฟ2โฅŠ1โ†“๐•ฉ โ‹„ (mโŠธโАโŒพ<C)โ—ถa@}
+
Switch โ† {cโ†โŠ‘๐•ฉ โ‹„ [m,a]โ†โ‰โˆ˜โ€ฟ2โฅŠ1โ†“๐•ฉ โ‹„ (mโŠธโАโŒพ<C)โ—ถa@}
 
 Switch โŸจvalue
   "increment" โ‹„ {๐•คโ‹„ v+โ†ฉ1}
@@ -137,13 +137,13 @@
 

Finally, the most general form of a switch statement is a chained if-else!

Loop forever

-

It's not a particularly common pattern, but this is a good simple case to warm up for the while loop. BQN primitives usually take a predictable amount of time, and none of them will run forever! Recursion is the tool to use here. If there's a particular function that we'd like to run infinity times, we can just add ๐•จ๐•Š๐•ฉ to the end:

+

It's not a particularly common pattern, but this is a good simple case to warm up for the while loop. BQN primitives usually take a predictable amount of time, and none of them will run forever! Recursion is the tool to use here. If there's a particular function that we'd like to run infinity times, we can just add ๐•จ๐•Š๐•ฉ to the end:

{
   # Stuff to do forever
   ๐•จ๐•Š๐•ฉ
 } arg
 
-

To convert this to a control structure format, we want to take an action A, and produce a function that runs A, then runs itself. Finally we want to call that function on some argument, say @. The argument is a single function, so to call Forever, we need to convert that function to a subject role.

+

It's important to note that this won't actually run that many times before it fails with a stack overflow. We'll address that later. Anyway, to convert this to a control structure format, we want to take an action A, and produce a function that runs A, then runs itself. Finally we want to call that function on some argument, say @. The argument is a single function, so to call Forever, we need to convert that function to a subject role.

Forever โ† {๐•Ša:{๐•ŠA๐•ฉ}@}
 
 Forever 1โŠ‘@โ€ฟ{๐•ค
@@ -153,7 +153,7 @@
 

A slicker method is to pass ๐•ฉ as an operand to a modifier. In a modifier, ๐•Š has the operands built in (just like {๐•ŠA๐•ฉ} above has the environment containing A built in), so it will work the same way with no need for an explicit variable assignment.

Forever โ† {๐•ฉ{๐•Š๐”ฝ๐•ฉ}@}
 
-

The syntax here is awkward enough that it's actually better to use a while loop, with a constant condition of 1!

+

But the calling syntax is awkward enough that it's actually better to use a while loop, with a constant condition of 1!

While 1โ€ฟ{๐•ค
   # Stuff to do forever
 }
@@ -174,9 +174,9 @@
 

The above version of While will fail in a fairly small number of iterations, because it consumes a new stack frame with each iteration. While tail call optimization could solve this, detecting the tail call in a compound function like ๐•Šโˆ˜๐”พโŸ๐”ฝ is technically difficult and would introduce overhead into a BQN interpreter. However, there is a method to make the number of required stack frames logarithmic in the number of iterations instead of linear:

While โ† {๐•ฉ{๐”ฝโŸ๐”พโˆ˜๐”ฝ_๐•ฃ_๐”พโˆ˜๐”ฝโŸ๐”พ๐•ฉ}๐•จ@}ยด
 
-

The innovation is to use {๐”ฝโŸ๐”พโˆ˜๐”ฝ_๐•ฃ_๐”พโˆ˜๐”ฝโŸ๐”พ๐•ฉ} instead of the equivalent {๐”ฝ_๐•ฃ_๐”พโˆ˜๐”ฝโŸ๐”พ๐•ฉ} or {๐•Šโˆ˜๐”ฝโŸ๐”พ๐•ฉ} (these are the same, as ๐•Š in a modifier is defined as ๐”ฝ_๐•ฃ_๐”พ). Here ๐”ฝ performs one iteration and ๐”พ tests whether to continue. The simplest approach is to perform one iteration and recurse with the same two functions. The modified approach replaces ๐”ฝ with ๐”ฝโŸ๐”พโˆ˜๐”ฝ, that is, it doubles it while making sure the condition is still checked each iteration. The doublings compound so that recursion level n performs ๐”ฝ up to 2โ‹†n times while using on the order of n additional stack frames. Only a hundred or two stack frames are needed to give a practically unlimited number of iterations.

+

The innovation is to use {๐”ฝโŸ๐”พโˆ˜๐”ฝ_๐•ฃ_๐”พโˆ˜๐”ฝโŸ๐”พ๐•ฉ} instead of the equivalent {๐”ฝ_๐•ฃ_๐”พโˆ˜๐”ฝโŸ๐”พ๐•ฉ} or {๐•Šโˆ˜๐”ฝโŸ๐”พ๐•ฉ} (these are the same, as ๐•Š in a modifier is defined to be ๐”ฝ_๐•ฃ_๐”พ). Here ๐”ฝ performs one iteration and ๐”พ tests whether to continue. The simplest approach is to perform one iteration, then recurse with the same two functions. The modified approach replaces ๐”ฝ with ๐”ฝโŸ๐”พโˆ˜๐”ฝ, that is, it doubles it while making sure the condition is still checked each iteration. The doublings compound so that recursion level n performs ๐”ฝ up to 2โ‹†n times while using on the order of n additional stack frames. Only a hundred or two stack frames are needed to give a practically unlimited number of iterations.

For

-

To begin with, are you sure you don't want a for-each loop instead? In BQN that's just a function with Each (ยจ), and it covers most common uses of a for loop.

+

To begin with, are you sure you don't want a for-each loop instead? In BQN that's just a function with Each (ยจ), and it covers most common uses of a for loop.

Fnยจ v      # for (๐•ฉ in v)
 Fnยจ โ†•n     # for (๐•ฉ=0; ๐•ฉ<n; ๐•ฉ++)
 Fnยจ kโ†“โ†•n   # for (๐•ฉ=k; ๐•ฉ<n; ๐•ฉ++)  with 0โ‰คk
@@ -194,8 +194,8 @@
   }
 }
 
-

The initialization can be a simple expression as shown; in fact it's a little silly to make initialization one of the arguments to For at all.Unlike in C, it's impossible to declare a variable that's local to the whole For loop but not its surroundings. Hopefully this is obvious from the structure of the code! Only curly braces can create a new scope, so to localize some variables in the For loop, just surround it in an extra set of curly braces.

-

The While loop alone allows syntax similar to the For loop. Perform any initialization outside of the loop, and compose the post-action with the main body using the reverse composition {๐”พโˆ˜๐”ฝ}. Because the composition binds less tightly than stranding, the bracketed list notation has to be used here.

+

The initialization can be a simple expression as shown; in fact it's a little silly to make initialization one of the arguments to For at all. Unlike in C, it's impossible to declare a variable that's local to the whole For loop but not its surroundings. Hopefully this is obvious from the structure of the code! Only curly braces can create a new scope, so to localize some variables in the For loop, surround it in an extra set of curly braces.

+

The While loop alone allows syntax similar to the For loop. Perform any initialization outside of the loop, and compose the post-action with the main body using the reverse composition {๐”พโˆ˜๐”ฝ}. Because that composition would bind less tightly than stranding, the bracketed list notation has to be used here.

cโ†27 โ‹„ nโ†0
 While โŸจ{๐•คโ‹„1<c}, {๐•คโ‹„n+โ†ฉ1}{๐”พโˆ˜๐”ฝ}{๐•ค
   Match (2|c)โ€ฟ{
-- 
cgit v1.2.3