Block-based languages differ from text-based languages in a number of ways, primarily because they are designed for novice users. Here is a list of things to consider when designing your own block-based language.
Use one-based lists
Novice programmers react badly when they encounter zero-based lists for the first time. As a result, Blockly follows the lead of Lua and Lambda Moo by making list and string indexing one-based.
For more advanced uses of Blockly, zero-based lists are supported to make transitioning to text easier. For younger or more novice audiences one-based indexing is still recommended.
Recommendation: One is the first number.
Support liberal naming rules
Novice programmers do not expect that location_X
and location_x
are
different variables. As a result, Blockly follows the lead of BASIC and HTML by
making variables and functions case-insensitive. Scratch uses a more subtle
approach (as seen to the right) and is case-sensitive for variable names but not
for equality checks.
Also, Blockly does not require that variables and functions conform to the
typical [_A-Za-z][_A-Za-z0-9]*
scheme. If one wants to name a variable List
of zip codes
or רשימת מיקודים
that is perfectly alright.
Recommendation: Ignore case, allow any names.
Make all variables global
Novice programmers also have difficulties understanding scope. As a result, Blockly follows the lead of Scratch by making all variables global. The only down-side of global variables is that recursion is trickier (one has to push and pop variables onto a list), but that's a programming technique that's beyond the scope of Blockly's target users.
Recommendation: Scope is out of scope, leave it for later.
Consider how to handle optional return values
Many functions in text-based programming perform an action, then return a value.
This return value may or may not be used. An example is a stack's pop()
function. Pop might be called to get and remove the last element, or it might be
called to just remove the last element with the return value being ignored.
var last = stack.pop(); // Get and remove last element.
stack.pop(); // Just remove last element.
Block-based languages are generally not good at ignoring a return value. A value block has to plug into something that accepts the value. There are several strategies to deal with this problem.
a) Steer around the problem. Most block-based languages design the language to avoid these cases. For example, Scratch does not have any blocks that have both side effects and a return value.
b) Provide two blocks. If space in the toolbox is not an issue, a simple solution is to provide two of each of this type of block, one with and one without a return value. The downside is that this can lead to a confusing toolbox with lot of nearly identical blocks.
c) Mutate one block. Use a dropdown, checkbox, or other control allowing the user to choose whether there is a return value or not. The block then changes shape depending on its options. An example of this can be seen in Blockly's list access block.
d) Eat the value. The first version of App Inventor created a special pipe block that ate any connected value. Users didn't understand the concept, and the second version of App Inventor removed the pipe block and instead recommended that users simply assign the value to a throwaway variable.
Recommendation: Each strategy has pros and cons, choose what's right for your users.
Generate readable code
Advanced Blockly users should be able to look at the generated code (JavaScript, Python, PHP, Lua, Dart, etc) and immediately recognize the program they wrote. This means extra effort needs to be made to keep this machine-generated code readable. Superfluous parentheses, numeric variables, crushed whitespace and verbose code templates all get in the way of producing elegant code. Generated code should include comments and should conform to Google's style guides.
Recommendation: Be proud of your generated code. Show it to the user.
Accept differences between languages
A side effect of the desire for clean code is that Blockly's behaviour is largely defined in terms of how the cross-compiled language behaves. The most common output language is JavaScript, but if Blockly were to cross-compile to a different language, no unreasonable attempts should be made to preserve the exact behaviour across both languages. For example, in JavaScript an empty string is false, whereas in Lua it is true. Defining a single pattern of behaviour for Blockly's code to execute regardless of the target language would result in unmaintainable code that looks like it came out of the GWT compiler.
Recommendation: Blockly is not a language, allow the existing language to affect behaviour.