2.6. VISUAL PROGRAMMING LANGUAGES

We now consider visual languages that can create code. In looking at a broad set of languages, we find that some concepts are especially difficult to represent visually. Repetition, whether through iteration or recursion, can be hard to communicate. Parameter passing is also difficult; the textual substitution of a parameter does not lend itself to simple visual representation. The representations of repetition or parameters differ according to the underlying language model. We distinguish between imperative and functional languages; we look at one functional language, PROGRAPH, in depth.

2.6.1. Imperative Visual Languages

Many of the visualization tools we discussed in the previous sections are based around imperative languages. And all flowchart techniques are diagrammatic descriptions of imperative languages.

PICT

PICT is a system developed by Glinert in his Ph.D. dissertation (1985). He strives for a system in which no text will be necessary.

Figure 2.50. PICT factorial example. From Glinert (1985).

The prototype is very restricted; only four 6-digit nonnegative decimal integers are allowed in each module. This essentially solves the parameter-passing problem. Each of the variables is assigned a color. The whole language is defined as

    <language primitive>

    ::=

    <sys control | <declarative op> | <boolean op>

    <sys control>

    ::=

    'start(entry)' | 'stop/return'

    <declarative op>

    ::=

    + | - | + 1 | - 1 | set to 1 | set to 0 |

    assign a copy |

    input from joystick

    <boolean op>

    ::=

    > | = | < | = 0 | = 1

Glinert writes:

PICT/D user programs look like flowcharts; although some professionals have questioned the usefulness of these diagrams, we find much to recommend the flowchart when it is the program itself rather than merely an aid to documenting it (page 53).

Using flowchart-like symbols, if and while statements can be constructed.

The physical construction of the program is done with a joystick, so the loops are drawn by steering a line around the screen.

BLOX

Blox (Glinert 1986) is a visual programming language made up of puzzle-like pieces that fit together.

Protrusions are fit into sockets on the pieces, which consist of program language statements such as while and if statements. Each piece can hide lower level constructs that are encapsulated within. (This is the H-graph component of the language.) The individual statements such as assignments, are typed in at the keyboard. Notable in the language is the way then and else clauses move off the main vertical line of the control.

2.6.2. Functional Visual Languages

Functional languages seem by their nature to lend themselves to visual representation. Most often, they have simple, uniform data structures. They do not have parameter passing, and they don't allow multiple assignment. This means that the data flow graphs previously described can be used for creating low level expressions. At the higher level, functional languages provide a set of functional forms, which allow for the combining of already defined functions. The function becomes an object that can be manipulated to form a higher-level function.

Once a value is determined in a functional system, that value flows through the rest of the system unchanged. This is called the principle of single assignment. In practice, this is very restrictive, and many languages relax this restriction in order to handle iteration in a nonrecursive manner. In the textual data flow language Val, one can increment a designated loop variable. In textual Sisal, one also has mechanism for incrementing a loop variable. We see these ideas carry over into visual languages discussed below.

PROGRAPH

PROGRAPH (Matwin 1985) is a data flow-like language. Matwin writes:

... graphical formulation of programs in PROGRAPH is based on a premise that non-linear concepts can be better expressed in a two-dimensional pictorial form rather than in sequential verbal script.

Basics

Here is a simple PROGRAPH program:

The line coming into the top of the box DEF SQUARE is the function's input. The line coming out of the box is the result. The branching of the input line feeds two identical values into the binary multiply function. SQUARE is used in the procedure VARIANT, which reads two numbers in, computes their squares, and adds the results together for display. Subroutine calls assume that the calling routine has the same number of input and output wires as the called routine.

Conditionals

Here are two examples of conditionals.

In all cases there is a boolean condition box. If the box tests positive, the THEN box is executed. In the case of the function ABSOLUTE, a number flows into the function; if the number is less than 0, the number goes through a unary minus function, converting the number into a positive number. This number flows out the bottom of the function. If the number is > 0, the IF condition fails, and the number that flowed into the box flows out unchanged.

In the function MAX, we see the use of another PROGRAPH wiring convention. An input can be grounded by terminating it with a small, filled square. If the first input is greater than the second input, the first input flows through, the second is terminated. Otherwise, the first is terminated, and the second flows through. Note that even though PROGRAPH does not have named parameters, the order of the wires performs the same function as parameter labeling does in textual languages.

Recursion

Here is a common example from list processing, REVERSE.


FIRST and REST correspond to LISP CAR and CDR, but APPEND is slightly different - it concatenates an element, not a list, on to the back of an input list. The recursion is accomplished by labeling a box inside the function with the name REVERSE. The inside of this interior box could be drawn as a smaller copy of the surrounding box. In such a case we would get a common visual metaphor for recursion, a picture within a picture, regressing all the way down to the boundary condition. In textual languages, the function translates to:

reverse(x)

if (nonempty(x) is TRUE)

return(append(reverse(rest(x)), first(x)))

else return(NIL)

Both the visual and the textual versions are fairly simple; however the visual version, through the crossing lines in the THEN box, draws attention to the basic idea of the algorithm.

Iteration

Unlike most visual functional languages, PROGRAPH supplies an iteration mechanism. A logical compartment sits on top of a transformation compartment. In a flow chart, the lines would be drawn explicitly. In PROGRAPH, the lines are implied. When the condition in the logical compartment is filled, data is passed to the transformation compartment. Results are then transferred back to the logical compartment. Even though lines are not drawn back up, the effect is very much like that of a flow chart, where a line is drawn explicitly back to the condition box.

Constant initialization is introduced in this figure:

The number 1 over the WHILE box is in effect only on the first test; from then on, the line has the value of the leftmost output of the DO loop. This line in effect accumulates the result. On the right side of the DO loop, the input number is decrement by one. This decremented number is output from the DO loop, then grounded. This means the number is fed back into the input line of the WHILE check, but stays internal to the

FACTORIAL box.

Psuedocode for this function reads

factorial (x)

result = 1

while (x >= 2)

result = result * x

x = x - 1

return(result)

Again, both the textual and the visual functions are fairly simple. The visual function needs no temporary variables. However, the introduction of an initialization of a line, and the implicit looping of the construct, make the representation difficult to decipher in a glance. A more complicated example is shown in this figure, a function for merging sorted lists.

Figure 2.57. PROGRAPH merge. From Matwin (1985).

The function uses three sub-functions, relying at the bottom level on CONCAT, the equivalent of LISP APPEND. Another new convention is introduced; an arrow on an in wire indicates the wire extends to the bottom. Notice this convention is necessary as a result of two other conventions - first, the order the lines exit a function is significant, and second, lines touching boxes is significant. The THEN box, without the arrow convention, would have needed to snake the second line around the boxes, and then out the bottom of the function.

The following is a textual algorithm for merging, in the SCHEME dialect of LISP, from Abelson (1985). It is recursive, not iterative:

(define (merge s1 s2)

(cond (( empty-stream? s1) s2)

( empty-stream? s2) s1)

else

let ((h1 (head s1))

(h2 (head s2)))

cond ((< h1 h2) (cons-stream h1 ( merge (tail s1) s2)))

((> h1 h2) (cons-stream h2 ( merge s1 (tail s2))))

(else

(cons-stream h1

(merge (tail s1) (tail s2)))))))

The typography is visually aligned, making it is easy to see the minor difference in the way the (< h1 h2) and ( >h1 h2) conditions are handled. As it is, the textual version seems easier to understand than the visual PROGRAPH version. The problem may be in the way the IF, THEN and ELSE clauses are stacked in the PROGRAPH version. If they were all drawn side-by-side, and the THEN clause was drawn the same size as the ELSE clause, the visual version would be somewhat clearer.

Parallel operations

PROGRAPH includes a convention for handling operations on lists in parallel. If an input line has a horizontal thick bar attached, then the input is considered multiple. This convention is used in conjunction with the APPLY TO ALL function.

Two vectors are fed in as multiples to the APPLY TO ALL function. The corresponding elements of each vector are added together, and the resulting list output. In the right-hand diagram, one list is fed in normally, the other as a multiple. Each sublist of the multiple input will be multiplied by the single list fed in on the left input line.

Above, matrix multiplication is defined. At the bottom of the second box, a multiple is fed into a PLUS operation. When single multiples are fed into PLUS, TIMES, AND, OR, the result is a reduction. So in this example, the numbers in the list are added together, producing a single number.

Matrices in PROGRAPH as represented as a list of lists. In following the logic of the matrix multiplication, attention must be paid to which in wires are multiples. Initially, the left matrix is treated as a multiple; this means that a row at a time is processed against the entire transposed right matrix. For every one of the left rows, we treat the right matrix as a multiple. This means that we multiply row 1 by column 1, then row 1 by column 2, etc. The third box performs this multiply, and the bottom of the second box performs the addition. By the sequence of multiples, we end up constructing an output of lists of lists in the correct order.

This does not seem to be an extremely intuitive way of writing a matrix-multiplication function. However, it does work without named parameters, subscripts, or temporary variables.

In this figure a recursive algorithm for finding a subtree is shown.

The tree itself is treated as a multiple (it is fed in as list of nested lists). The tree to be matched is also fed in. The APPLY TO ALL function serves to supply the set of subtrees of the list to the recursive call in the THEN clause. A multiple OR at the end catches any successful find. Matwin notes that the OR can be implemented using lazy evaluation.

SHOW-AND-TELL

Show-and-Tell (Gillet 1986) solves the iteration problem by using the conventions of a textual data flow language, Lucid. In Lucid, a sequence of values is kept for each variable. This historical sequence can be referenced the same way an array can be referenced. Ambler (1989) calls this a temporally-dependent iteration construct.

Show-and-Tell spatializes this idea by unfolding the multiple instances of a variable, accordion style. Show-and-Tell contains another interesting characteristic. If contiguous boxes have conflicting values, they are considered inconsistent. However, the inconsistency is isolated to the containing box.

PROGRAMMING IN PICTURES (PIP)

Raeder, in his Ph.D. dissertation (1984) describes his system, which uses FP as its main model. Raeder writes:


A function is defined by drawing a picture of the functions input data, the functions output data and the actions on the data. The underlying convention here is the data flow graph. The higher level of abstraction will be an iconic picture of the function. Then FP-style functional forms are used to combine functions.

A typing mechanism is added; this is outside of the FP model.

VISUAL FP

Pagan (1977) describes a visual version of FP.

It is different in appearance from the other functional languages we have just looked at. Instead of using data flow graphs as the visual conventions, it uses a modification of Nassi-Shneiderman charts. The box-shapes in Nassi-Shneiderman diagrams represent control structures; in graphical FP they represent different functional combining forms. The six combining forms are:


The system is very simple. As with other Nassi-Schneiderman based systems, text still predominates much of the representation. However, some of the linear terseness of the functional forms of FP seems to have been relieved by the 2-dimensional representation.

2.6.3. Miscellaneous Visual Languages

Our analysis has not been exhaustive; there are many other systems that are documented and discussed in the literature. We mention briefly some of the systems that often are cited.

VENNLISP

Lakin (1986) describes a LISP system in which expressions have been replaced by graphics using the convention of enclosure. It is a variation on a parse tree. The functions themselves are encoded as different enclosing shapes.

GARDEN

Reiss (1987) describes a system for building visual languages. In a sense it is a meta-visual programming language. The system is shown building an FSA language, a flowchart language, and a data flow language. The underlying language is LISP. The pictures can be executed.


Return to Chapter 2 of Visual Programming