Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

Visual Programming with Prograph


Sep98: Visual Programming with Prograph

Garth is a technical manager and developer at Pictorius. He can be contacted at [email protected].


A visual language is more than a traditional text-based language with a graphical integrated development environment. True visual language programming is done by directly manipulating graphical representations of programs. In this article, I'll discuss visual programming by examining the Prograph visual language, developed by Pictorius (the company I work for). In addition to describing the core language, I'll show how to visually implement the familiar Quicksort algorithm.

Prograph is an object-oriented visual language that lets you manipulate iconic data-flow diagrams to create the executable source code for your application. Created by T. Pietrzykowski and P.T. Cox at Acadia University and the Technical University of Nova Scotia, the first commercial release of Prograph was for the Macintosh in 1988. At the time, the Mac was the only widely available system with a human interface advanced enough for visual languages. The Mac version went through several revisions, including the addition of the Application Building Classes (ABC) library for building applications, and a set of Application Building Editors (ABE) for laying out the application's GUI. In 1996, a Windows 95/NT version of Prograph was developed, and the ABCs were modified to be a cross-platform class library. (In conjunction with this article, Pictorius is providing a Prograph interpreter, available electronically; for more information see "Resource Center," page 3.) Over the years, many changes have been made to Prograph's development environment, editor, and debugger, but the language itself remains similar to the original version.

Prograph is a Turing-complete programming language, meaning any program that can be written in C++ (or another high-level language) can be written in Prograph. Since Prograph is a data-flow language, data flows along lines that are connected to operations. The operations manipulate the data and pass it along to other operations. Programs are created by constructing data-flow diagrams in the editor. Prograph classes, methods, and attributes are represented and manipulated graphically. To specify inheritance, for example, you connect two classes with a line, as shown in Figure 1.

The language has several built-in data types, including integer, real, Boolean, list, and string. Lists are homogeneous, ordered collections of any Prograph data type. One quality of a data-flow language is that data is immutable. You cannot change the value of a string object, for example, once it has been created. Any operations performed on the string (such as taking a substring or joining two strings) always create new string objects. The only exceptions are instances. Setting an attribute in an instance modifies the instance in place.

Since Prograph is weakly typed, you can write an entire program without specifying the type of an attribute or parameter. Recent implementations of Prograph support optional typing. The language supports automatic memory management, automatically freeing memory when an object is no longer referenced; the current implementation uses a reference-counting memory manager so circular links have to be broken programmatically to prevent memory leaks. The memory manager also looks after all built-in data types, instances, strings, and lists. Finally, Prograph supports threads, exceptions, and Microsoft's COM.

Data-Flow Diagrams

Prograph's data-flow diagrams are the code -- there is no underlying textual source code and Prograph is not translated into an intermediate textual language such as C or Java.

Figure 2 is a Prograph method that adds two inputs, multiplies the sum by 7, and returns the result. The rectangular objects are operations, and the lines are data links. The small circles are nodes. More specifically, nodes on the bottom of an operation are called "roots," and those on top are called "terminals." Roots are the sources of data, terminals the destination. Terminals connect to one root, roots can connect to any number of terminals.

The long, thin operations at the top and bottom are input and output operations, respectively, and specify the inputs and outputs of a method. Every method has one input and one output operation. The roots of the input operation pass the input parameters into the method; the terminals of the output operation pass the outputs out.

When a method executes, data flows from the roots of the input operation along the data links, through the other operations in the method, and eventually out the terminals of the output operation.

Basic Operations

Table 1 lists Prograph's basic operations and Figure 3 is a Prograph code fragment that illustrates these operations. In Figure 3, an instance of the class MyClass is created and passed to a get operation for MyClass's Name attribute. The get operation has two outputs -- the right output is the value of the attribute, and the left output is the value of the instance that was input. This lets you string together gets on the same instance. Next, the name is passed to the "join" primitive, which joins the name string with the string .txt. The resulting string is then put back into the Name attribute, replacing the old name. The set operation, like get, outputs the instance that was input. Finally, the Save method of MyClass is called.

The name of the "join" primitive includes quotes. Since visual languages do not need to be parsed, the rules for naming language elements (methods, classes, and attributes) can be relaxed. For example, you can use spaces and dashes to make long names readable.

The slash in front of the name of the Save operation indicates a virtual call to a method as opposed a static call, which would have the form MyClass/MyStaticMethod. Prograph determines the method to be called by the name of the method and the instance passed to the operation at run time. This is different from C++, where the static type of the variable is also used to determine the method called.

More Operations

Table 2 lists operations used to make code more organized and compact: match, evaluate, and local. A match operation compares a value with a constant. This is equivalent to using the equal primitive with a constant input, but it is expressed more simply. The evaluate operation lets you evaluate a complex mathematical expression in a single operation.

Most important is the local operation, which corresponds to a brace-delimited block in C/C++. The local operation combines a related group of operations into a single operation. The name of a local is purely descriptive; well-named locals help to improve the readability of code.

External match, external constant, external method, external get, external set, and external address are operations for calling externally defined functions and accessing C structures and fields. These external operations (see Table 3) let you make operating-system calls directly from Prograph. You can, for example, write a complete Windows 95/NT or Macintosh application in Prograph by making direct calls to the Win32 API or Macintosh Toolbox.

Iterating, Looping, Flow of Control

Prograph has three looping annotations: repeat, list, and loop (see Figure 4). A repeat annotation is applied to an operation, causing it to repeat until the loop is terminated. A list annotation can be applied to the roots and/or terminals of an operation. On an input terminal, it causes the operation to iterate over a list, and on an output root, it causes the outputs to be collected into a list. A loop annotation is applied to a root/terminal pair and causes the operation to repeat. Each time the loop annotation repeats, the looped output value is passed back into the corresponding input value.

Each local and method in Prograph consists of one or more blocks of code, called "cases." Execution always starts in the first case and can be transferred to the next case depending on a condition. Figure 5 is a switch statement implemented in Prograph as a method with three cases. The first case is executed if the input is red, the second case when green, and the third for any other input. The square symbol with an x in it is a next case control. If the input does not match, execution is transferred to the next case. Any complex if/then/else or switch statement can be constructed using cases and next case controls.

The terminate, finish, and fail (see Table 4) controls work much like the next case control.

  • terminate stops a repeating operation immediately.
  • finish also stops a repeating operation, but lets the current case finish executing.
  • fail passes a failure result to the calling method.

The checkmark x within the controls in Table 4 indicate that the control will be executed when the condition is True or False, respectively.

Order of Execution

The order of execution of operations in a Prograph method cannot be determined by the position of the operation in the diagram. Figure 6(a) shows a method that opens a file, writes to it, then closes it. There is nothing in this method to indicate the open is to occur before the write or close. The order of execution is determined by the data links, not by the position of the operation in the diagram. If you need to enforce a certain execution order, you can use a synchro to enforce it. Figure 6(b) shows the method with two synchros, which ensure that the file is opened, then written, then closed. One other thing that determines order of execution is an operation with a control, which will execute before all other operations except those that must precede it due to data links or synchros.

In the current implementation of Prograph, execution order is determined at compile time based on the aforementioned conditions, and will not change unless you edit the method. This is not a general guarantee of the language, however. Future implementations of the language, for example, could allow parallel paths through a method to execute concurrently.

Reflection

The names of Prograph methods, classes, and attributes are available at run time, similar to C++ RTTI or Java reflection classes. You can, for example, programmatically obtain a list of the names of all subclasses of a class or the names of all methods or attributes of a class. You can also perform the basic operations -- get, set, method call, and instance generation -- using names determined at run time. This is done using the inject annotation; see Figure 7. The operations have no names. The name is taken from the value that is passed into the inject, the long input that extends down into the operation.

Incremental Compilation,Linking, and Debugging

Prograph programs are run in either debug or compiled mode. The debugger is integrated with the editor and executes programs while under development. Before shipping an application, you compile it into a stand-alone, native-executable file.

In debug mode, Prograph compiles methods incrementally, so programs can be executed at any point in the development process. The editor is aware of the Prograph language, and prevents you from creating incorrect code that cannot be compiled. Changes that do not affect the executable behavior of a method (for example, rewording a comment or changing the physical position of operations) do not force recompilation.

Classes are defined and declared in the same source file. All references to classes, attributes, and methods are executed using late binding. This means that when class definitions are changed, no recompilation is needed. References can be created to methods, attributes, and classes before they exist and code with references to nonexistent objects can be compiled. If you try to execute a reference to a nonexistent element, the debugger reports an error.

There is no compile/link cycle when developing Prograph applications. The incremental compiler and linker, in cooperation with the editor, limit compilation to only what needs to be compiled and keeps the program ready to execute at all times.

Prograph supports the usual set of debugging features -- stepping though code, setting breakpoints, and examining values. The debugger for a visual language needs to give the same kind of feedback as a textual language: where the current point of execution is, and how it changes as users step over, out of, or into methods. With a textual language, it is obvious which code has executed and which has not, simply by indicating where the current point of execution is. Everything above the point has already been executed and everything below has not. In a data-flow diagram, however, this isn't so, since execution does not necessarily flow from top to bottom or left to right. Prograph uses colors to explicitly indicate executed code. The operation about to be executed is red, the operations that have already been executed are blue, and the yet to be executed ones green. As you step through code, colors change to indicate the flow of execution. Figure 8 is an executing method in which the + operation is about to execute.

The debugger allows execution to be rolled back to any point in the stack. Methods are automatically popped from the stack, and all intermediate objects are freed from memory. Execution can also be rolled forward to any point in an execution method. If execution is rolled forward and data being passed into an operation is not yet available, a special data type is used to indicate that the operation executed out of order. Classes that can be modified as programs are running because of the combination of automatic memory management, the visual editing environment, and late binding.

Adding an attribute to a class does not force code to be recompiled. Instead, the memory manager locates all existing instances of a class and expands them to make room for the new attribute. This is also true for deleting or reordering attributes -- the memory manager updates all of the existing instances in memory to reflect the change. Renaming an attribute has no effect on running code. Deleting a class is not allowed if the running program has any instances of the class in existence. However, it is possible to adjust the class hierarchy.

Methods can be added, renamed, deleted, and edited while a program is running. Renaming/deleting is not allowed if the method is currently on the stack. Instead, you are given the opportunity to roll execution back to the method's caller before deleting/renaming the method.

If a reference to a class, method, or attribute is encountered during execution before the element exists, you are prompted to have the element automatically created. After creating the element, execution is resumed.

When a change is made to a method, thereby forcing recompilation, Prograph first checks to see if the method is on the execution stack. There are three possible scenarios:

  • The method is not on the stack.
  • The method is on top of the stack.
  • The method is in the middle of the stack.

For simplicity, I'm assuming that there is only one stack, but this can easily be generalized to deal with multiple stacks.

The simplest case is when you edit a method that is not on the stack. The change is allowed, and the method marked for recompilation. When the method is called, it is recompiled and executed with the change.

When you edit a method that's on top of the stack, execution is moved to the point of the edit, and execution is stopped as if at a breakpoint. Changes that do not affect execution (such as rewording a comment or moving an operation) do not cause rollback. If you edit code that has not yet executed, no rollback is needed. If there are parallel paths through a method and only one of the paths is affected, other paths can be left executed and only the one path rolled back.

Editing a method that's in the middle of the stack isn't allowed because you might make changes that affect the called methods. For example, you could delete the operation that invokes the call chain or change one of the inputs to the methods in the call chain. Instead, Prograph warns you that the change requires execution to be rolled back, and asks if you want to proceed. If so, the execution stack is popped until the edited method is on top, then execution is moved to the point of the edit. Execution is stopped as if at a breakpoint.

Quicksort Example

For illustration, I implemented the well-known Quicksort algorithm that recursively sorts a list of strings or numbers. The code consists of one method (Quicksort), which has one input (the list to be sorted) and one output (the sorted list).

Figure 9 shows the top level of the Quicksort method. The window on the left, titled "1:2 Quicksort," is the first of two cases, and that on the right, 2:2 Quicksort, is the second case. The first case of the Quicksort method tests whether the incoming list has more than one element. The (length) primitive, or built-in, operation returns the number of elements in the list, and the > operation compares the length to 1. If the > operation returns False, the list has only one element, and is passed to the output. For longer lists, execution proceeds to the second case where the actual work is done.

There are five operations in "2:2 Quicksort," not counting the input and output operations at the top and bottom. The first is a local operation called "pivot" and contains the code to choose a pivot value from the list and pass the pivot value and a list with the pivot value removed. The next operation (called "partition") takes the pivot and the pivotless list and produces two lists -- on the left, a list of all items less than or equal to the pivot, and on the right, a list of all items greater than the pivot. partition has a stacked look that is slightly different from the other locals in the case; this is to show that it executes repeatedly. The list annotation (which looks like triple dots) on the first input causes the operation to be executed once for every element in the list. The list annotations on the outputs cause output values to be collected into lists.

The next two operations -- recursive calls to Quicksort itself -- sort the left and right sublists. The final operation, "join," rejoins the two lists and the pivot value into a single sorted list.

Figure 10 shows the internals of the pivot local. I use the first element of the list as the pivot. detach-nth is a primitive operation that returns the nth element of a list (using one-based indexing) and a copy of the list with that element removed. Recall that data (except for instances) is immutable in Prograph's data-flow system, so the program never modifies the original list. The elements of the list are not copied -- they are the same elements referenced multiple times.

Figure 11 shows the internals of the partition local, which is executed once for each element in the pivotless list. Each time it executes, its outputs are appended to the left and right output lists. The first case determines whether the current list element is greater than the pivot value using the > primitive. If it is greater, execution proceeds to the next case. If it is less than or equal, execution continues in the first case. In the first case, the list element is passed to the left output, which causes it to be appended to the left list. In the second case, the list element is passed to the right output, which causes it to be appended to the right list.

Figure 12 shows the internals of the join local. It uses the pack primitive to put the pivot element into a list, then the (join) primitive to join the three lists into a single sorted list.

Conclusion

Visual programming is still programming; a poorly designed program will have the same problems no matter what kind of language it is written in. It is possible to write faster, more-memory-efficient code in C than in Prograph, but a bad algorithm will be slower in C than a good one in Prograph.

DDJ

For More Information
2000 Barrington Street, Suite 401
Halifax, Nova Scotia
Canada B3J 3K1
902-492-2880
http://www.pictorius.com/


Copyright © 1998, Dr. Dobb's Journal

Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.