goals of a type system must differ for visual languages from those of ... The answer to this is best ... Milner was the first to apply it to programming languages. [5].
Proceedings 1993 IEEE Symposium on Visual Languages, Bergen, Norway, Aug. 24-27, 1993, pp. 238-243.
Types and Type Inference in a Visual Programming Language Margaret M. Burnett * Department of Computer Science, Oregon State University Corvallis, Oregon 97331 USA Abstract In this paper, the uses of types and type inference in visual languages are explored. First, we discuss how the goals of a type system must differ for visual languages from those of a type system for textual languages. We then present a type system developed under these goals for the visual language Forms/3. Within the context of this system, issues of particular importance in visual languages are examined, including maintaining the user's conceptual model, the avoidance of language restrictions solely to support a type system, and how the visual process of programming can provide additional information to the type system.
2.
Any type errors should result in immediate and meaningful feedback. A type error must follow accepted rules for good error reporting, appearing as soon as an error is entered, and stating exactly what is wrong and where it is wrong. From this, we conclude that the type system must be not only visible, but also understandable. 3. The type system should not impose new rules on the language that would otherwise not be needed. For example, it should not require declarations, or impose language restrictions solely for the purpose of supporting the type system. (In textual languages, such language restrictions are common.) Some of these goals may seem at odds with one another. For example, how can the type system not require new concepts of the user, and still be visible and understandable to the user? The answer to this is best shown via two contrasting examples. Suppose the system tells the user that the formula for some cell X must result in the (user-defined) type Stack; the user understands what a stack is because it is a type defined by that very user. On the other hand, if the system were to say that the formula for X must result in a subtype of type Stack, the user might become confused, because the concept of a subtype would be required. The answer we seek then is a type system that is at least partially visible to the user, with the visible part so obvious that the user doesn't need to learn new concepts to understand it.
1: Introduction One approach to types in visual programming languages (VPLs) is to hide them. The rationale would be to keep the simple, flexible spirit of programming in a visual language. This philosophy appears to be shared by many researchers, because few have introduced the concept of types into visual languages. But it seems contradictory to allow the user to unknowingly enter a type-incorrect program in a language that boasts of immediate feedback. The problem is how to introduce types into a visual language in a way that strengthens the simplicity and feedback capabilities, without introducing new concepts and mechanisms. Our approach to types for declarative VPLs is based on the incorporation of polymorphic type inference commonly found in modern functional languages. However, the goals under which our type system was developed depart from those of traditional approaches: 1. The user's conceptual model and the concreteness of the programming environment should be preserved. The user should not have to think abstractly about types, and the type system should not require new concepts of the user.
2: Background Languages using implicit types do not have type declarations and appear to be type-free, but the advantages derived from rigorous type-checking at program-entry time are still available. Although the theory behind implicit polymorphic types has its roots in combinatory logic, Milner was the first to apply it to programming languages [5]. Milner's work with various refinements is widely used in modern functional languages. There have been few visual languages which have attempted to incorporate implicit types, and they fall into two categories. In the first category, systems like
*This work was supported in part by the National Science Foundation under Grants CCR-9215030 and CCR-9396134.
-1-
Milner's are fully incorporated into the visual language, and soundness and completeness are preserved. ESTL [6], CUBE [7], and VisaVis [9] are systems in this category. ESTL, for example, supports tuple types, union types, function types, higher-order functions, and polymorphic types. The entire type system is visible to the user, including the polymorphic type variables. The second category, which emphasizes concreteness in type systems, can be seen in Fabrik [4]. Fabrik includes an interactive polymorphic type system with some type-checking and inference. Fabrik's type system is simple, concrete and highly visible, but is not as fully developed as the others. Commercial spreadsheets provide concrete, immediate feedback about type errors by eagerly evaluating a formula when it is entered, producing a special value such as *Error* if any errors occur. This approach features simplicity and immediate visual feedback, but does not detect all type errors. For example, the error in the formula "if cellA then 3+4 else cellA+4" would not surface if cellA had the value "true". Further, eager evaluation may report errors that are not errors at all. For example, a formula in a cell referencing the first entry in an empty table is not a type error. Nor is it a logic error if there is another formula which only references the cell in the event that the table is non-empty. Yet, eager evaluation would report it as an error.
In Forms/3 a cell's formula may define a single value or a sequence of values over time, termed a temporal vector. The cells are arranged on forms as desired by the user. Cells can also be grouped into matrices or abstraction boxes which can be referenced like cells from other formulas. See figure 1. 3.1: The basic inference system An informal summary of the reasoning mechanism is as follows using the notation given in Table 1. Every object has one and only one type, and the universe of objects is divided into equivalence classes based on object type. (The term object describes any typed entity.) Thus, if it is known that cell X and cell Y are both of type α, i.e., that they belong to the same equivalence class, and it is later discovered that cell X is of type , then it can be deduced that cell Y is also of type , as are all other objects with type α . (This notation denotes a temporal vector of 0 or more components, each of which is of type n u m b e r .) If more than one monomorphic type can be derived, a type error is present. The equations in Table 2 provide a more precise description of the type system, and are explained in the following sections. It is important to note that the equations, type variables, and notation are never seen by the user, and are not necessary to his/her understanding of the type system. They are solely for the purposes of exposition in this paper. In this paper, the term component means a component of a temporal vector. For example, "A" is a component of the temporal vector . A temporal vector resulting from evaluation of a formula has a type, e.g., τ() = . The cell in which the vector resides has the same type as its vector. Each component of the vector also has a type, e.g., τ(3) = number. A referenceable object is a cell, a matrix, or an abstraction box, i.e. anything that can be referenced. For type purposes, a form is defined to be a set of referenceable objects.
3: A concrete and visible type system Our system borrows heavily from Milner's approach, but is more concrete. Our aim is a concrete approach to types analogous to "naive physics", where the user sees and experiments with certain concrete entities and draws conclusions about the way things work, without proving theorems or dealing with abstract concepts. Our work on types was done in the context of the visual language Forms/3 [1, 2]. Forms/3 is a demanddriven form-based VPL with textual and graphical data, procedural abstraction, data abstraction, event-handling and files. All programming is done by defining formulas for cells, which can be referenced by formulas in other cells. 1
(a)
(b)
(c)
(d)
1
2 time
3
5
Notation Courier font Greek letters τ( )
Monomorphic types Type variables Mapping from objects to types On F, due to ∈ membership in F, or due to membership in a cell group on F.
8
Figure 1: (a) A cell. (b) Its temporal vector of values. (c)ּA matrix of 6 cells. (d) An abstraction box whose cells constitute the parts of an abstract data type.
Table 1: Notation
-2-
Example number α τ(Y) denotes "the type of Y" N∈F denotes referenceable object N on form (or referenceable object) F.
(ADT): τ(AbsBox) = where ADT is the name of an abstract data type definition form, ADT′ is any instance of form ADT, and AbsBox is an abstraction box ∈ ADT′ (MATRIX): τ(X) = matrix where X is a matrix referenceable object (MATNUM): τ(X[numRows]) = τ(X[numCols]) = where τ(X) = matrix (☞): τ(X) = τ(Y) where Y≡☞X (MAT☞): τ(X[ri]) = τ(Y[ri]) ∀ referenceable objects ri ∈ X where Y≡☞X, τ(X) = matrix (ADT☞): τ(X[ri]) = τ(Y[ri]) ∀ referenceable objects ri ∈ X where τ(X) = , αj is a user-defined ADT, and Y≡☞X (EARLIER): τ(xi) = τ(xj) ∀ components xi and xj of cell D ∈ an instance of form Earlier (PREV): τ(xi) = τ(xj) ∀ components xi and xj of cell D ∈ an instance of form Prev (REC): τ(aCell) = τ(aCell′) where F1′ is any recursive instance of originating calling instance F1, aCell is a referenceable object ∈ F1 and aCell′ is the corresponding object ∈ F1′
later be unified with other information. But there are some referenceable objects for which the system provides more specific information. In particular, on many of the built-in forms, the system declares the types of some referenceable objects to be some particular monomorphic type, or to be the same as the type of some other object. The complete list of type declarations for each built-in form is given in [1]. In addition, the system declares the types of the two distinguished cells on matrices defining the number of rows and columns, as stated in (MATNUM), and declares the types of all constants. 3.1.3 Formula references: For simplicity of reasoning, we introduce a primitive version of the language. In this primitive version, the only legal formula is a simple reference or constant vector–there are no arithmetic operations, conditionals, etc. Instead, in this primitive version there are a number of built-in primitive forms (e.g., +, IF) that perform these operations. For example, the built-in form + defines a cell C to be the result of adding the values of cells A and B together. To use it, the form is copied, the formulas for cells A and B on the copy are defined to be the numbers desired, and the formula for cell C can then be referenced. The full version of Forms/3 eliminates this tedious process by providing a broad range of formula operations, but the primitive version serves as a useful vehicle for reasoning about the properties of the full version. In this paper, the fact that formula references are entered by pointing with the mouse is reflected by denoting such references with ☞ . For example, if the user enters a formula for cell Y which is simply a reference to cell X, we will say that Y≡ ☞ X. In this example, such a reference says that if X is a temporal vector of numbers, then Y must be also (Equation ☞). The ☞ operation not only causes two objects to have the same types, but also compound objects' parts to have the same types (Equations MAT☞ and ADT☞). For type reasoning purposes, a cell containing an instance of a user-defined ADT is itself a group of virtual cells (similar to an abstraction box). This is only for type reasoning, and is not seen by the user.
Table 2
3.1.1 The types: Like all programming in Forms/3, abstract data types are defined and used via forms with cells and groups of cells on them. Equation (ADT) says the type of an instance of an abstract data type is the name of the form on which the type is defined. The following primitive types are built into the language: boolean, box, error, eventReceptor, file, glyph, line, number, and text. Equation (MATRIX) simply says that matrix is not only a kind of referenceable object, but also a type. A significant difference between our type system and other systems is that matrices, user-defined ADTs, and the primitive types listed above, are the only types in our system. There are no function definition types, tuple types, subtypes, recursive types, union types, higher-order types, or type constructors. This is not to say that the functionality associated with many of these concepts is not present in the language, but simply that the type system does not require them.
3.1.4 Forms a n d f u n c t i o n s , i t e r a t i o n a n d recursion: Milner-like systems assign a type to each function definition and a type to each function invocation. For example, number→α→α means "a function which takes a number and an α and returns an α". The type of a function invocation is the type of the result. In our system, forms provide the functionality of functions. To invoke a form, the user makes a copy (instance), enters new formulas if desired for cells on this new copy, and references cells on it in the usual way. Recursive forms simply reference cells on other copies of
3.1.2 System declarations of types: For many referenceable objects, the system initially declares the types to be unique polymorphic type variables, which may
-3-
the same form. The system also has the ability to invoke forms by copying in this way. Thus, our approach to types does not use function types. Instead, type analysis is performed for every cell referenced. Unfortunately, this approach to static type analysis would be non-terminating for recursive forms, because without evaluating the termination condition, it cannot be determined how many cell references there will be. In fact, iterative formulas, which are simply references to earlier components of a temporal vector (via direct manipulation in the full version, or using forms Earlier or Prev in the primitive version) are not required to have termination conditions at all and can be used to construct potentially infinite vectors of data. Although a goal was to avoid restrictions, language restrictions were added requiring the temporal vectors of iterative cells to be homogeneous, and all objects on recursive instances of a form to have the same type characteristics as the originating calling instance (equations EARLIER, PREV, and REC). This allows termination of type inference, while still retaining polymorphism. This restriction is the same as in Milnerlike systems. However, in our system non-iterative, nonrecursive formulas are not restricted to homogeneity. The 9 equations in Table 2 plus the declarations of the primitive forms constitute a simple type inference system. It does not introduce new concepts to the user, and imposes few restrictions on the language. There is no difficulty in providing simple yet meaningful error messages in the event of a type error, because the only types to which it refers are concrete and visible to the user. It handles user-defined abstract data types and all the primitive types, and most user-defined and built-in operations (forms, in our language). However there are two significant omissions–conditionals and matrices or lists. These will be considered in the next sections.
(TYPERES): α=β ⇒ α∈ρ(α)∩ρ(β) (TYPERES1): ρ(α) = {χ} ⇒ α=χ (IF): ρ′(τ(D)) = ρ(τ(B)) ∪ ρ(τ(C)) ∩ ρ(τ(D)) ρ′(τ(B)) = ρ(τ(D)) ∩ ρ(τ(B)) ρ′(τ(C)) = ρ(τ(D)) ∩ ρ(τ(C)) where IF′ is any instance of form IF, B ∈ IF′, C ∈ IF′, D ∈ IF′, ρ′(α) is the new constraint set for type α (SUBMATRIX): τ(X[i@j]) = τ(X[k@m]) ∀ i, j, k, m subscripts in the same submatrix of X where τ(X) = matrix Table 3
Type constraints subsume the usual treatment of "union types" used in some type systems, in which for example type α = number | text, and β = text | stack. Union types are generally supported only hierarchically, implying that number is a subtype of α and is therefore type-correct wherever α is valid, but that β is not a subtype of α and thus if τ(X)=α and τ(Y)=β, X and Y are not type-compatible. Type constraint sets can clearly be used to express the above notion (ρ(α)={number, text}, ρ(β)={text, stack}), and also allow non-hierarchical relationships because intersection of the constraint sets above shows that X is not necessarily type-incompatible with Y. In the full version of Forms/3, the formula 'if X=35 then 35 else "not 35"' is valid. In the primitive version, this is represented by a primitive if-then-else form in which cell A references the result of the comparison of X to 35, cell B contains the formula ☞ 35, and cell C contains the formula ☞ "not 35". Cell D is internally defined to contain the result. On this primitive form, the system declares cell A to be of type . The issue is what, if any, the type constraints for cells B, C, and D respectively should be (denoted β, χ, and δ here). Our approach does not require the traditional restriction that β = χ = δ as long as constraints imposed by other uses of D can be satisfied by both B and C, as given in (IF) in Table 3. For example, if D is only used for display purposes, it is acceptable for β to be and χ to be in the formula in the previous paragraph. On the other hand, if a later reference to D performs a numeric operation, then the formula is illegal.
3.2: Constraints and conditional execution Milner-style type systems impose a homogeneity restriction requiring that the "then" and "else" return values of an if/then/else must be of the same type. In order to avoid such a restriction, we employ a reasoning mechanism similar to Fairbairn's sets of valid types [3]. A set of type constraints on type α, denoted ρ(α), is the set of the types possible for α. Some type constraints are stated as rules rather than equations, because they are not equivalence relationships. If α cannot satisfy its type constraints, i.e. if α∉ρ(α), then a type error has occurred. If α=β, then α must satisfy both ρ(α) and ρ(β), i.e. if α∉ρ(α)∩ρ(β), then a type error has occurred. See Table 3. Unless otherwise stated an object's type is restricted by the trivial constraint set ρ(α)={α}.
3.3: Visual information about matrices The problem with matrices (and lists) in traditional languages is that, prior to runtime, it is not known to which element subscripted references such as M[i] refer, so type inference would not be possible if different elements of the same matrix could have different types. To solve this problem, traditional systems require them to be
-4-
homogeneous. This is not necessary in our system. One reason for this is that matrices themselves are different in Forms/3 than in traditional systems. The matrices described here are those developed by Viehstaedt and Ambler [11] with slight modifications. These matrices are not of fixed size. Instead, the formulas for the size cells determine dimensions dynamically. This allows matrices to serve the same purpose as both matrices and lists in other languages. To use a matrix, the user places one on a form. To specify formulas for it, the user selects the entire matrix or just part of it (called a region) and specifies a formula for a typical cell in that region (figure 2). Full details of the ways matrices can be partitioned into regions, and how the system can assist and enforce the process can be found in [11]. A characteristic of regions which is important to the type inference system is that regions cannot overlap. For example, if a matrix defined to have two regions side-by-side, has its total column dimension evaluate to one at runtime, the first region will have one column but the second region (containing zero columns) will also exist. This process of dividing up the matrix tells the inference system that the cells within one region, since they share the same formula, share the same type characteristics. Also, because subscripting is done very concretely by pointing with the mouse, use of subscripts is more limited in Forms/3 than in traditional languages. (To provide additional power, the language includes primitive operations supporting construction of new matrices from old ones by transposition, application of predicates, etc.) In Forms/3, a row or column subscript may only be (1) a constant (e.g., the first element of a region) or the pseudo-constant last (e.g., the last element); or (2) the corresponding cell in the same or a different region or matrix, offset by a constant (e.g., the cell in this row, one column before). For type inference purposes, a matrix is regarded as a collection of submatrices. A submatrix is a contiguous 33
3
14
12
52
a
b
73
48
c
d
Matrix1
group of cells. A submatrix's boundaries are determined by the physical division into regions described above, or transitively by references to different regions within another matrix (figure 3). Each submatrix is homogeneous in the static sense, allowing reasoning for one cell in a submatrix to apply to all cells in that submatrix (rule SUBMATRIX). Since each cell in the submatrix is defined by the same formula (either physically or transitively via references), this homogeneity is a natural consequence. Note that static homogeneity implies only that all the elements must follow the same type constraints. Suppose a region represented by cell X[i] is defined by the formula 'if Y[i]>100 then 35 else "not 35"'. It is required that each element of that region satisfy the constraint set {number | text}. It is not required that if one of the elements eventually is computed to be 35, all the other elements must be numbers. Thus it is not necessary to know which element is being referenced for static type inference because an element's type can be determined from the submatrix to which it belongs. This allows type inference to occur without the restrictions found in traditional approaches. The fact that submatrices are homogenous is not an artificial restriction superimposed on the language for the purposes of type inference, but rather a natural consequence following from the way the user manipulates and defines the regions.
4: Putting the system to work In this section, the process of type inference for the purpose of type-checking in an incremental, visual programming environment will be summarized. For the full version of Forms/3, our algorithm uses unification and intersection and has a time complexity of O(msnA), where m is the number of sets being intersected for 1 operand, s is the size of the smallest set being intersected for 1 operand, n is the number of operands in the formula, and A is the inverse Ackermann function, an extremely slow-growing function of n. (A