Connector Shapes that Represent Types
In the blocks languages we've created, we've followed the lead of StarLogo TNG, and use the shape of a plug/socket to indicate the type of value produced/consumed. For example, here are the connector shapes used for types in PictureBlocks:
These shapes help programmers to reason about types. For example, they indicate that the clockwise block takes two arguments (a number and picture) and returns a picture:
Since plugs can only fit into sockets of the correct shape, connector shapes are a visual form of static type checking.
Polymorphism and Incremental Type Reconstruction
In TurtleBlocks and PictureBlocks, the indeterminate "polymorphic" shape can change to a concrete shape to reflect the result of solving type constraints.
For example, the choose block evaluates and returns the result of the then or else expression depending on whether the test expression evaluates to true or false.
The
then and
else sockets and the
choose plug must all have the same type, but this can be any type. When a plug or socket with a concrete type constrains one of these, the other two change accordingly. Here are some examples:
Plugging a number into the then socket constrains the else socket and choose plug to have the number shape. Unplugging the number from the then socket returns the polymorphic sockets and plug of the choose plug to their initial polymorphic shape.
Plugging the choose plug into the boolean input socket of not forces the then and else sockets to assume the boolean shape.
Connecting the plug of one choose into the else socket of another choose doesn't change any connector shapes, but now the polymorphic connectors of both choose blocks are constrained to have the same type. For instance, plugging a string into the else socket of the rightmost choose changes two plugs and four sockets to have the string shape.
Polymorphic Variable References with Drop-Down Menus of Names in Scope
In TurtleBlocks and PictureBlocks, there are single polymorphic get and set blocks that can reference and change the value of a named variable. An innovation of these blocks is that they have a drop-down menu that lists only the names in scope of the correct type where the block is used. The notation ??? means that no name has been chosen yet.
For example, consider the testScope procedure. It takes five arguments, whose shapes indicate their types: a number n, a string str1, a boolean bool1, a picture pic1, and a color color1. In its body, it binds a local number name num2 to the value 3 and a local string name str2 to the string "abc" before printing out three values.
The first println can display any type of value, so the drop-down menu for the first get list all variables in scope:
In the second println, the num to string block forces the get to reference a variable with type number, so its menu lists only the two number variables in scope:
In the third println, the join block forces the get to reference a variable with type string, so its menu lists only the two string variables in scope:
All Together Now: pushRight Example
Here we present an example in which connector shapes, polymorphism, and variables all play a role. We will define a PictureBlocks function named pushRight that takes two arguments, a number n and a picture pic, and returns a picture that consists of n copies of pic horizontally juxtaposed, where the leftmost copy is drawn in the left half of the frame, and each successive copy to the right is given half of the remaining space. For example, the invocation
should denote the picture
We begin by populating the workspace with blocks that we'll need for our definition. The pushRight in the upper left corner is a function declaration block specifying a function that takes a number parameter n and a picture parameter pic. The socket labeled return is for the body expression of the function; its type is initially unconstrained.
The two other blocks labeled pushRight are invocations of the pushRight function. Although their argument types are known, their result plugs have indeterminate shape because the function body is unspecified.
Next, suppose we fix the test and then sockets of the choose block. Type propagation forces the else socket and plug of the choose to have picture type.
If we now connect the choose plug in the return socket of the pushRight declaration, this forces the plugs of both pushRight invocations to have picture type.
Alternatively, if we unplug the
choose from the
return socket and instead connect the indeterminate plug of the rightmost
pushRight invocation into the second
pic socket of the
beside block, this forces the return socket of the
pushRight declaration and the plug of the other
pushRight invocation to have picture type.
In either case, we can complete the definition of
pushRight by connecting the remaining blocks and selecting the variable references. In this example, there is only one variable reference of the correct type for each
get.