Skip to content

3. Variables and control flow

David Loscutoff edited this page Jan 2, 2022 · 5 revisions

The programs we've seen thus far consist of a single expression. Some programs require multiple statements—in particular, loops. Pip has a handful of control-flow commands for this purpose. But first, let's talk about variables.

Variables

Variables in Pip behave much like variables in other imperative languages: they hold a value, and they can be changed by assigning a different value to them. Most of the variables you'll use in Pip programs are lowercase letters, although there are also variables that aren't lowercase.

Initial values

A Pip variable always has a value, even before it is explicitly set. There is no such thing as an undefined variable. Different variables have different initial values; some of them are constants, while some of them depend on the program and the arguments.

We've already seen one example of this when we used a to access the first command-line argument to the program. Similarly, if we pass two arguments to the program, we can access the second one as b:

a*b

(ATO)

In fact, the first five command-line arguments can be accessed as a, b, c, d, and e. (We'll cover what f and g do when we get to functions and lists, respectively.)

Most of the remaining lowercase letters are preinitialized to useful constants:

Var Value
h 100 (hundred)
i 0
k ", "
l [] (empty list)
m 1000 (Roman numeral m)
n newline
o 1 (one)
p "()" (parentheses)
s space
t 10 (ten)
v -1 (negative 1)
x ""
y ""
z lowercase alphabet (a to z)

For instance, here's a program to calculate the first argument as a percentage of the second argument:

a/b*h.'%

(ATO)

Assignment

The value of any variable can be set using the assignment operator :.

h:6*7

Instead of 100, h is now 42. (ATO)

An assignment expression is an ordinary expression; its value is the new value of the variable. It can be used anywhere an expression can be used, including as a subexpression in a larger expression:

(h:6*7)*2

This assigns 42 to h and then multiplies that value by 2 to get 84. Note that : has very low precedence, which is why we need the parentheses. (ATO)

The assignment operator is right-associative, which means we can assign the same value to two different variables like this:

s:t:42

(ATO)

Variations on assignment

Assignment gets interesting when we combine : with another operator to create a compute-and-assign operator. For instance:

h+:3

This expression adds 3 to h in-place, modifying the value of h. It's equivalent to h:h+3. (ATO)

The compute-and-assign concept is found in many C-like languages, but in Pip there's a crucial difference: it works with any operator. That's because : is acting as a meta-operator, a symbol that combines with an operator and modifies its behavior. The : meta-operator even works with unary and ternary operators:

SQ:a

(ATO)

aR:s"..."

(ATO)

If you need to increment or decrement a variable in-place, the U and D operators (mnemonic: Up and Down) do just that:

Ut
Dh

(ATO)

Finally, if you need to store a value in a variable and it doesn't matter which variable you use, use y. Instead of assigning to it using y:, you can use the yank operator Y:

Y6*7

(ATO)

Statements and control flow

In general, a Pip program consists of one or more statements. Any expression counts as a statement, and (as we've seen) if a program ends with an expression, it is autoprinted. There are also several kinds of statements that are not expressions; chief among them are loops.

While loops

A while loop is introduced by the command W, followed by two things: a loop condition and and a loop body.

W i<a {
  Ui
  o*:i
}
o

Here is a simple ungolfed program to compute the factorial of the input number. (ATO) Let's walk through how it works.

The expression after W is the loop condition. At the beginning of each time through the loop, the condition is evaluated; the loop continues running as long as the condition is true. In this case, our condition is i<a, which means the loop will continue as long as i is less than the program argument a. Since i is initially 0, we will execute the loop at least once if the argument is positive.

The loop body is a series of statements surrounded by curly braces. If the loop body contains only one statement, the curly braces are optional. Here, we have two statements: Ui increments i, and o*:i multiplies o (initially 1) by the current value of i in-place.

Once the loop exits, the desired value is stored in o, but it hasn't been printed. Therefore, we end the program with the expression o, which is autoprinted.

Let's golf this factorial program a bit. First, we can get rid of all the whitespace:

Wi<a{Uio*:i}o

(Since lowercase variables are one letter each, the parser knows that io is two separate variables i and o.) Now, we can also get rid of the curly braces if the loop body is one statement. Since U is just an operator, we can rewrite Uio*:i to o*:Ui, and we have:

Wi<ao*:Uio

There are, of course, shorter ways to compute a factorial, but this is the shortest using a while loop. (ATO)

Till loops

A variation on the while loop is the till loop. It uses the command T and functions the same way, except that the logic is reversed: the loop continues as long as the condition is false. We could rewrite our factorial program using till:

Ti=ao*:Uio

Instead of looping while i is less than a, we now loop until i is equal to a. The result is the same in this case, but for more-complicated conditions it can sometimes be advantageous to use till. (ATO)

Excursus: the print and output operators

What if we're in a loop and we want to generate output each time through? Enter the P and O operators. P prints a value with a trailing newline, and O does the same but without the trailing newline. These operators (together with the yank operator Y) are the only operators with lower precedence than assignment, which means we can show the steps of our factorial program like this:

Wi<aPo*:Ui

Each time through the loop, the value of o*:Ui (that is, the new value of o) gets printed. Now we don't need the extra o at the end, since the final value of o gets printed explicitly in the loop. (ATO)

P and O work anywhere, not just in loops. We can use them to print multiple values:

Pa+b
Pa-b
Pa*b
Pa/b

(ATO)... Whoops, why did we get the last result twice? Because P and O are operators, not statements, so Pa/b is the final expression in the program. P prints its operand, but it also returns its value, which means that the same value gets autoprinted. To avoid the duplicate, don't use P on the last expression:

Pa+b
Pa-b
Pa*b
a/b

(ATO)

Fixed-iteration loops

Sometimes you just want to loop a certain number of times. The fixed-iteration loop L is your friend here. It takes a loop count expression and a loop body. Whatever number the loop count evaluates to, that's how many times it loops.

L5P"Hello!"

(ATO)

That's a silly example; but we can actually rewrite our factorial program using L and save two bytes:

Lao*:Uio

(ATO)

For loops

Pip's for loops are modeled after Python's for x in y. They start with F followed by a loop variable, an iterable expression to loop over, and a loop body. For each element of the iterable, the loop variable is bound to that element's value, and the loop body is executed. Scalars are iterable; the elements of a Scalar are its characters:

Fi"Hello, World!"Pi.s.Ai

For each character i in the string "Hello, World!", print the character concatenated with a space and the ASCII code of that character. (ATO)

If statements

Usually, the ternary operator ? is the best way to do conditional logic in Pip. However, sometimes you need a statement (for example, if you want to conditionally a statement such as a loop). Pip's if statement is introduced with I followed by a condition and a block of statements like the while loop. Else-if clauses are introduced with EI followed by another condition; a final else clause is introduced with EL. All of these extra clauses are optional.

I a<0
  P"Cannot take the factorial of a negative number"
EI a=0
  P"By convention, the factorial of 0 is 1"
EL {
  W i<a
    o *: Ui
  Po
}

(ATO)

Example challenge

TODO

Clone this wiki locally