A Visual Representation for Functional Programs - CiteSeerX

0 downloads 0 Views 202KB Size Report
literature, and is used in various forms in 3], 4] and 6]. The basic unit of program construction in our representation is the expression- graph: a function that has ...
A Visual Representation for Functional Programs Joel Kelso [email protected] December 1994 Abstract

Functional programming languages have features that make them attractive for software development and for the teaching of programming concepts. Like all conventional programming languages they are tied to an inherently one-dimensional textual programming style. Visual programming seeks to enhance the development and understanding of software, by utilizing the human brain's natural visual/spatial processing abilities. A visual representation for functional program expressions is de ned: expressions are represented as rooted, directed acyclic graphs. Various graph nodes represent functions, type information and sub-expressions. Complex expression-graphs are constructed by composing simpler expressions, and functions are de ned by collapsing expression-graphs to form new function nodes. This representation is intended to form the basis of a visual programming environment for functional languages.

1

Contents

1 Introduction

1.1 Functional expressions as graphs 1.2 Type-directed editing

4 : : : : : : : : : : : : : : : : : : : : :

: : : : : : : : : : : : : : : : : : : : : : : : : : :

2 Anatomy of an expression-graph 2.1 Functions 2.2 Types 2.2.1 Type contexts 2.3 Pattern-matching 2.3.1 Pattern frames 2.4 Guarded expressions 2.5 Subexpression sharing 2.6 Applying higher-order functions

5

: : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : :

: : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : :

: : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : :

: : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : :

3 Expression-graph construction 3.1 3.2 3.3 3.4

Expression attachment Function de nition Function declaration Code hiding

4 5

6 6 9 10 11 12 13 14

15

: : : : : : : : : : : : : : : : : : : : : : : : : :

: : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : :

: : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : :

4 Summary

15 18 18 18

20

2

List of Figures 1 2 3 4 5 6 7 8 9 10 11 12 13 14

A simple expression-graph Function nodes A signature expression-graph Sample type-nodes Sample pattern nodes A sample pattern-graph A sample pattern frame A sample guarded expression Example of a shared subexpression The \apply" primitive A simple expression-graph attachment Example of expression-collapse de nition Sub-expression hiding example A complex expression-graph

: : : : : : : : : : : : : : : : : : : : : : : :

: : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : :

: : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : :

: : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : :

: : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : :

: : : : : : : : : : : : : : : : : : : : : :

: : : : : : : : : : : : : : : : : : : : : : :

3

7 8 8 9 10 11 12 13 14 16 17 19 20 21

J.K.Kelso

A Visual Representation for Functional Languages

4

1 Introduction This report forms part of a project the aim of which is to produce a visual programming environment for functional programming languages. In view of this end, the report introduces a visual representation for functional program expressions, and outlines a visual editing system for the manipulation of these expressions. A overview of the representation and editing system is given below, along with its relation to the existing work on visual functional languages. Throughout the report we use Haskell [1] as an archetypal functional language, although the representation could be used for similar languages such Miranda1 [7] or Hope [2].

1.1 Functional expressions as graphs

The rationale behind using graphs for the representation of functional programs rests on two observations. Firstly, programming language expressions, because their syntax is de ned by a grammar, are essentially tree-like; and secondly, that the common model of functional program execution is graph reduction . Representing functional programs as graphs allows the programmer to perceive and edit the program's syntactical structure spatially, and o ers insight into the program's underlying semantics. We represent individual functions as graph nodes; the application of a function to an argument sub-expression is represented by an arc between nodes. This is the most common representation for functional languages in the Visual Programming literature, and is used in various forms in [3], [4] and [6]. The basic unit of program construction in our representation is the expressiongraph : a function that has been applied to zero or more arguments. An expressiongraph has an implicit value (which may be a ground data value or may be function valued) and an explicit type. Two advantages of this representation are: 1. Since most functional program objects, including constants, structured data and special forms (eg conditionals and case expressions) can be represented as functions, the syntax of the visual expressions is very simple. 2. Function de nition (which is the basic program construction operation of functional languages) can be accomplished simply by associating a name with an expression-graph, which then becomes the de ning expression of a new function. 1

MirandaTM is a trademark of Research Software Ltd.

J.K.Kelso

A Visual Representation for Functional Languages

5

1.2 Type-directed editing

We are interested in representing strongly typed functional languages. The typechecking mechanism of strongly typed languages assigns a type to each object at compile time, so that no type checking need be done at execution time. Conventional implementations of these languages are usually implicitly typed: they use a type inference system to generate types for every subexpression in a program. Expressions need not be given explicit types by the programmer except in cases of ambiguity. Any type annotations that are made by the programmer are checked against these inferred types: discrepancies indicate that an expression is not being interpreted in the way the programmer intended. Strong typing enables such errors to be located at compile time. The representation described here is suitable for a strongly typed language, but is not implicitly typed in the sense described above. Strongly typed languages tend to be implicitly typed, relieving the programmer from having to provide a type declaration for every subexpression in the program. Type information is, however, an integral part of the proposed representation, and is used to direct the editing process. The tedious task of keeping track of types for every subexpression is handled automatically; the only time a type need be explicitly designated is when a function is used before it is de ned (eg. when de ning a recursive function). This is not seen as a serious limitation, since making type annotations for such functions is regarded as good programming practice in any case. VisaVis, introduced in [6], has been extended with an implicit incremental type system that allows a form of parametric polymorphism and function overloading [5]. Show-and-Tell, a visual teaching language, has also been extended to include an explicit polymorphic type system [4]. The proposed representation is based on Haskell's type system, with its parametric polymorphism and type class overloading scheme.

2 Anatomy of an expression-graph In this section we describe the visual syntax of our representation, starting with the basic arc-node structure. Figure 1 shows a simple expression-graph. The nodes of an expression-graph can be classi ed into three groups. 1. Every expression-graph has a root type-node , which shows the type of the entire expression and represents the value of expression. 2. Every expression-graph has one or more function-nodes , which form the body of the expression. Expression-graphs can also contain pattern graphs which

J.K.Kelso

A Visual Representation for Functional Languages

6

allow pattern-matching of structured data. 3. Expression-graphs may have zero or more parameter type-nodes , which represent un lled parameters; they are place-holders where other expressions may be attached.

2.1 Functions

Functions are represented by function-nodes : boxes labeled with the function's name and possibly containing other pertinent information. The name is assumed to be an ASCII string. 2 A single connector leads from the bottom of the function. This connector represents the value of the expression rooted in the function. A number of connectors lead upwards from the top edge of the function, one for each function parameter. Figure 2 shows some example function nodes. Constructor functions (and literal data values such as numbers or characters) are distinguished by having diagonal-sided boxes, and special forms by using a di erent font for their names. Note that function-nodes, like quarks and U.S. presidents, never actually appear by themselves. The smallest useful expression-graph is a single function node with its attendant type nodes. An expression graph of this type is referred to as a signature , and it shows the intrinsic type of its function. Figure 3 show the signature of the addition function.

2.2 Types

Each of the connectors mentioned above, if not connected to another function-node, will be terminated by a type-node . In all cases, a type-node denotes the type of the expression that appears (or could appear) above it. Root type-nodes, terminating the lower connector of a function-node, show the type of the entire expression-graph. These nodes are used as a handle for the entire expression-graph during an attachment operation (see later). Parameter type-nodes, terminating the upper connectors, show the type (and, optionally, the name) of a function's arguments. These nodes are targets for other expression-graphs during attachment.

2 It would be possible to use a graphic to identify the function rather than a textual name, but this raises the question of where this graphic would come from. Either the programmer would have to produce one for each function (a bad idea since it is recognized that icon-design is best left to graphic artists) or automatically generated. In the latter case the graphic would have to be at least a complex as the function itself, which rather defeats the purpose of using names for abstraction.

J.K.Kelso

A Visual Representation for Functional Languages

parameter nodes a

a

a 4 function nodes 2

^

*

*

-

Num a => a->a->a->a

root node

Figure 1: A simple expression-graph

7

J.K.Kelso

A Visual Representation for Functional Languages

if-then-else

special form

[]

:

+

42

ordinary function

numeric literal

constructor functions for the list data type

Figure 2: Function nodes

a

a

+

The minimal expressiongraph for the addition function

Num a => a->a->a

Figure 3: A signature expression-graph

8

J.K.Kelso

A Visual Representation for Functional Languages

9

The actual type is represented simply by the ASCII string of a Haskell type declaration (the type notation used, including type variables and the curried representation of functions with multiple arguments, is described in the Haskell report [1]). It is possible to represent types pictorially, but this su ers from the same problem as for function names, as mentioned in the footnote to the previous section. Figure 4 shows some sample type nodes.

Int

a

parameter of integer type

parameter of variable type

Num a=>a->a->a

f::a->b parameter of function type, with name ‘f’

expression-graph root node, with type class context

Figure 4: Sample type-nodes

2.2.1 Type contexts Parametric polymorphism allows functions with types that are parameterized by type variables. The scope of type variables in a polymorphic function is a single expression graph. For example the type variable a appearing in two di erent type nodes in an expression denotes the same type. The same type variable appearing in a di erent expression is not related to the rst, only to type variables in its own expression. When one expression is attached to another, this naming convention is taken into account and variables in the composite expression are consistently renamed. Type class polymorphism, as featured in Haskell, allows type variables to be quali ed by a class context. We allow the scope of a type class context to be a single expression, and the context bindings are shown in the root type-node for the expression. Figure 4 shows an example of a root node with a type class context: it binds the variable a to the Num numeric class.

J.K.Kelso

A Visual Representation for Functional Languages

10

2.3 Pattern-matching

The visual pattern-matching representation described here is a generalization of the pattern-matching schemes in textual functional languages. It performs the functions of testing and decomposing structured data. Pattern matching is represented by a visual pattern-graph . Where a normal expression-graph composes incoming data using functions, a pattern-graph decomposes incoming data. The constituents of pattern-graphs are pattern-nodes : \upsidedown" function nodes that have a single upper connector and multiple lower connectors. Figure 5 shows some sample pattern-nodes, including a numeric literal pattern, a \wildcard" pattern and the patterns for the list data type.

42

[]

’a’

patten-nodes for the list data type

wildcard

Pattern-nodes for an numeric literal, a character literal, and a wildcard pattern

:

Figure 5: Sample pattern nodes At the top of a pattern-graph is the pattern's root-node, which shows the type of the incoming data. The body of a pattern is built from pattern-nodes in much the same way as an expression-graph is build from function nodes. The leaves of a pattern-graph (appearing at the bottom) are either constant data values or pattern type-nodes, which show the type of the decomposed data. These correspond to the variables appearing in a textual pattern expression, and can be optionally named. Figure 6 shows a example pattern-graph. The exact semantics of the evaluation of an expression containing a patterngraph is dependent on the underlying language, but generally when a pattern is encountered, the incoming expression is evaluated rst, and then its structure is matched against that of the pattern expression.

J.K.Kelso

A Visual Representation for Functional Languages

11

[Int] An pattern-graph that will match any list beginning [ 1, 2, ... ]

:

1

:

[Int] 2

Figure 6: A sample pattern-graph

2.3.1 Pattern frames

Pattern matching is also used in functional languages to specify conditional definitions. To allow this is our visual representation we introduce a special form of expression-graph, the pattern frame , which appears as an outline around an expression-graph that contains a pattern-graph. The pattern frame is a generalization of the case expression in Haskell. A pattern frame is similar to an ordinary expression-graph, in that it has a root node and possibly several parameter type-nodes. The expression-graph that forms the body of the pattern frame must contain at least one pattern-graph. A pattern frame de nes the value of the expression in the case that the incoming data matches the pattern. Once a pattern frame has been introduced, additional pattern frames can be added to deal with alternative patterns of incoming data. These pattern frames appear stacked in a pile (with one showing at a time), and must have the same type as the rst. The mechanism by which a pattern frame is chosen from the alternatives to be evaluated depends on the pattern matching semantics of the underlying functional language. Depending on the requirements of the mechanism, some additional syntax may be required. For instance, in order to correspond to Haskell, the alternatives

J.K.Kelso

A Visual Representation for Functional Languages

12

could be numbered by the programmer, and tested in order until a match is found. Alternative pattern matching schemes are possible: for instance, a check for overlapping patterns or pattern completeness could be introduced, or some form of best- t pattern matching could be employed. Figure 7 shows two views of a pile of pattern frame alternatives, each with a di erent pattern uppermost. The body and root of the pattern frame is not shown.

[a]

pattern 1

[a]

pattern 2 []

:

A pattern frame with two alternative patterns

Figure 7: A sample pattern frame

2.4 Guarded expressions

An alternative method of specifying a conditional expression, featured in Haskell and Miranda, is a set of guarded expressions . Guarded expressions are in two parts, the guard (which is itself a boolean expression) and a value expression. The usual semantics involve evaluating the guard expressions in order until one is found to be true, whereupon the corresponding value expression becomes the value of the expression. The visual representation of a guarded expression is the guard frame . A guard frame is similar to a pattern frame, with the root node protruding from the bottom and parameter nodes from the top. Inside the frame are two expression-graphs: on the left is the guard expression, which has the same parameters as the guard frame

J.K.Kelso

A Visual Representation for Functional Languages

13

but evaluates to a boolean value, and on the right is the value expression, which shares the same parameters but has the type of the guard frame. As with pattern frames, alternative guard-value pairs are stacked in a pile, with one visible at a time. Figure 8 shows a guard frame expression.

Int

guard 1

0

-1


Bool

Int->Int

The top frame of a guarded expression.

Figure 8: A sample guarded expression

2.5 Subexpression sharing

Textual languages generally have a method of introducing local de nitions for naming subexpressions that appear more than once in an expression. Apart from their

J.K.Kelso

A Visual Representation for Functional Languages

14

value in abbreviating and clarifying expressions, this allows languages that employ lazy evaluation to ensure that the computation of shared subexpression occurs only once. In some cases it is valuable to be able give these subexpressions meaningful names, in other cases programmers use \place-holder" names. Since in our representation a subexpression is also a sub-graph, the sharing of a subexpression can be represented by the multiple use of a graph connector. This is shown by the use of a sharing node , which has a single upper connector (which is attached to the sub-graph being shared) and multiple lower connectors, all of which share the value of the subexpression. Figure 9 shows an example of an expression-graph with a shared subexpression. This mechanism is similar to the where and let clauses in textual languages, but does not allow recursive local de nitions. An extension to the visual syntax allowing arbitary recursive local de nitions is being considered.

Float sharing node sqrt

Expression-graph equivalent to: +

\x -> let y = sqrt x in y + y

Float->Float

Figure 9: Example of a shared subexpression

2.6 Applying higher-order functions

The system described so far allows an expression-graph to represent a higher-order function, and allows higher-order function to be passed as parameters to functions.

J.K.Kelso

A Visual Representation for Functional Languages

15

However, there is no way to actually apply a higher-order function parameter to an argument. In particular, there is no way to specify the \apply" function: apply f x = f x

If we supply a special apply primitive , writing such functions becomes possible.3 The apply primitive makes possible the representation higher-order functions such as the compose function. compose :: ( b -> c ) -> ( a -> b ) -> a -> c compose f g x = f (g x)

Figure 10 show the construction of the compose function from two apply primitives. This gure also introduces expression-graph construction operations, which are the subject of the next section.

3 Expression-graph construction Having given the visual syntax of expression-graphs, we now describe the operations by which they are constructed, and how they are used to de ne new functions. We assume that individual nodes (functions, patterns, apply nodes etc) are available in a container of some sort from which they can be dragged onto a worksheet area.

3.1 Expression attachment

The basic expression building operation is attachment , whereby two expressiongraphs are joined. This takes place when the programmer drags a root node of one expression-graph onto a parameter node of another. If the types of the two expressions are not compatible, then no action takes place and the programmer is warned that the operation is illegal. In this way, only expression graphs that are syntactically and type correct can be constructed. Figure 11 shows attachment taking place between the addition function and a numeric literal. The attachment operation has three main e ects.

3 Actually, di erent apply primitives are needed for functions of di erent arities. We assume that the programmer can select or specify the arity needed when the apply node is created

J.K.Kelso

A Visual Representation for Functional Languages

The raw materials are two arity-1 apply nodes.

a->b

a (types have been altered for clariy)

apply These nodes are joined ... (a->b)->a->b c->d

c

a->b

apply

(c->d)->c->d

apply

b->d

... which results in this expression ...

a

apply

(b->d)->(a->b)->a->d

b->d

a->b

a

compose

...which can be collapsed into the compose function

(b->d)->(a->b)->a->d

Figure 10: The \apply" primitive

16

J.K.Kelso

A Visual Representation for Functional Languages

17

1. The parameter type-node of the target expression and the root node of the source node disappear; the source expression is grafted onto the parameter connector of the target.4 2. The root type-node is updated to re ect the new type of the combined expression graph, as the arity and the type of its parameters may have changed. In the example gure it can been seen that the target expression-graph (the signature of the addition function) has two parameters while the resulting expression-graph has one. 3. Type variables may be re-named and type class contexts may be updated due to type uni cation. In the example, the target expression contains the type variable a, which is restricted to be of the Num numeric class. During attachment, the type variable becomes bound to the type Int.

These two nodes are joined. 1

1 Int

a

a Int + +

The resulting expression-graph

Int->Int

Num a => a->a->a

Figure 11: A simple expression-graph attachment Since each subexpression still has a type even when part of a larger expression, the root node of the subexpression can be considered hidden rather than removed. An option may be given to the programmer to view hidden root nodes on demand. 4

J.K.Kelso

A Visual Representation for Functional Languages

18

3.2 Function de nition

Fundamental to the construction of functional programs is the ability to de ne new functions. Any expression graph (or possibly part of an expression graph) may be collapsed , to form a new function node. The function has the same root type-node as the original expression, has a parameter type-node for each parameter type-node in the original expression. The new function node is named by the programmer. Figure 12 shows an example of collapsing an expression graph to de ne a function.

3.3 Function declaration

The expression-collapse method of function de nition allows bottom-up program construction, but not top-down construction or the construction of recursive functions. To allow a function to appear in an expression-graph before it is de ned, an alternative function declaration is needed. To declare a function, the programmer speci es a name and type (either textually or through a visual type-construction mechanism), and a new function node is made available for use. At a later time the function may be de ned by building an expression, collapsing it, and giving it the name of the declared function.

3.4 Code hiding

It is expected that the expression-graphs for complex functions could become quite large and unwieldy. A programming environment implementing the visual representation could also implement dynamic code hiding , a feature that has no equivalent in static textual languages. By hiding a large expression-graph and expanding only the current working subexpression, the programmer can concentrate the visual extent on the area of interest. Code hiding allows any subexpression in an expression-graph to be hidden by folding it up. Each subexpression joined to function as an argument can be folded or unfolded 5. All expressions shown so far have been in the unfolded state. When a sub-expression is folded (by clicking on an area at the bottom of it's lowest function node, perhaps), it is shown by a bubble inside the function-node below it. In the case that the sub-expression is a parameter type-node, the bubble is clear (indicating that it is a site to which another expression may be attached); if the subexpression is a ground expression the bubble is lled (indicating that this attachment Not to be confused with the Haskell list processing function fold, or the fold/unfold program transformations 5

J.K.Kelso

A Visual Representation for Functional Languages

Float

Float

Float

4.0

2.0

*

^

*

-

Float->Float->Float-> Float

Float

Float

Float

A section of expression graph is tagged for collapse, named, and ...

discriminant

Float->Float->Float-> Float ...the result is a new function.

Figure 12: Example of expression-collapse de nition

19

J.K.Kelso

A Visual Representation for Functional Languages

20

site is already lled); and if the sub-expression is a function with un lled upper type connectors then the bubble is shaded (indicating that the site is lled but can be unfolded to reveal more attachment sites). Figure 13 shows a function with various arguments in each state.

After these subexpressions are folded ...

Int

42

inc

...they appear like this.

Int

foo

Int->Int->Int

foo

Int->Int->Int

Figure 13: Sub-expression hiding example

4 Summary We have de ned a visual representation for functional language expressions, and outlined a type-directed editing system for their manipulation. As an example of how all the various components might be combined in a complex function de nition, gure 14 shows the expression-graph for the recursive, higher-order, polymorphic map function, de ned with pattern matching and sub-expression sharing. This report represents an initial set of design choices around which to base further work. It is unknown at this stage how much of the visual syntax described will survive to the stage of a full programming environment implementation, or what additional constructs will be necessary.

J.K.Kelso

A Visual Representation for Functional Languages

a->b

[a]

a->b

wildcard

[a]

[]

:

apply

map

[]

: pattern 1

(a->b)->[a]->[b]

The definition and declaration of the map function. map :: (a->b) -> [a] -> [b] map f [] = [] map f (x:xs) = f x : map f xs

pattern2

(a->b)->[a]->[b]

[a]

a->b

map

(a->b)->[a]->[b]

Figure 14: A complex expression-graph

21

J.K.Kelso

A Visual Representation for Functional Languages

22

References [1] Report on the programming language Haskell, 1992. [2] R.M. Burstall, D.B. MacQueen, and D.T. Sanella. Hope: and experimental applicative language. Technical Report CSR-62-80, University of Edinburgh, 1980. [3] M. Edel. The Tinkertoy graphical programming environment. In IEEE Proceedings COMPSAC, pages 466{471. IEEE Computer Society Press, 1986. [4] M. Najork and E. Golin. Enhancing Show-and-Tell with a polymorphic type system and higher-order functions. In Proceedings of the 1990 IEEE Workshop on Visual Languages, pages 215{220. IEEE Computer Society Press, 1990. [5] Jorg Poswig and Claudio Moraga. Incremental type systems and implicit parametric overloading in visual languages. In Proceedings of the 1993 IEEE Symposium on Visual Languages, pages 126{133. IEEE Computer Society Press, August 1993. [6] Jorg Poswig, Guido Vrankar, and Claudio Moraga. VisaVis - contributions to practice and theory of highly interactive visual languages. In Proceedings of the 1992 IEEE Workshop on Visual Languages, pages 155{161. IEEE Computer Society Press, September 1992. [7] D.A. Turner. Miranda { a non-strict functional language with polymorphic types. In Proc. Conference on Functional Programming Languages and Computer Architecture, pages 1{16. Springer Verlag, 1985.