SABLE's distinguishing characteristic is that it is a highly readable language. This was the primary driving principle in its design since a program is read many more times than it's written or modified. Certainly, to modify a segment of code, you must first read it at least once. (Well you should!) Even when writing a throwaway program, you must constantly keep in mind, i.e. read and grasp, whatever portion was already written. More commonly, programs are written and maintained over a long period, read again and again, usually by multiple people. Readability, meaning "the quality of being quickly and easily understood by a human reader," is crucial, more important than brevity or efficiency (although SABLE makes no sacrifices in those camps).
The features of SABLE combine so that its programs are clearer, simpler, and usually shorter than those written in C-based languages. They will be easier to read, understand, and maintain. That's a bold statement which I'll support with a few examples.
Suppose class PACKAGE has three lists of CLASS objects that it needs to combine into a single list. Furthermore, to gain use of some additional operations, it will convert the list into an array. In C# this is about the best we can do (using the #System.Collections.Generic namespace). In SABLE, it's a one-liner (but I'll use two).
// C#List<CLASS> allClassList = new List<CLASS>(); allClassList.AddRange(definedClasses); allClassList.AddRange(extendedClasses); allClassList.AddRange(removedClasses); CLASS[] allClasses = allClassList.ToArray(); "SABLE"|allClasses| := (definedClasses + extendedClasses + removedClasses) to_ARRAY.
In SABLE, you don't need to specify the type of allClasses; SABLE infers that it's an {ARRAY[CLASS]}.
By the way; the implementation of these two is exactly the same. SABLE creates the temporary LIST to aggregate the contents of any number of lists. The + operation on LIST is a macro that creates an "operator by aggregation," a SABLE concept to complex to go into here. Anyone could have created this macro.
Many methods accept multiple arguments, and we often want to pass complex information into those arguments. SABLE is much better at displaying such information in a meaningful way than the C-based languages. I'll illustrate this with an only mildly complex example from the .NET documentation of method System.Reflection.Emit.ILGenerator.BeginCatchBlock().
// C#Type[] adderParams = new Type[] {typeof(int), typeof(int)}; MethodBuilder adderBldr = myTypeBuilder.DefineMethod("DoAdd", MethodAttributes.Public | MethodAttributes.Static, typeof(int), adderParams); "SABLE"|adderBldr| := myTypeBuilder defineMethodNamed: 'DoAdd' attributes: #Public | #Static returnType: INT32 argumentTypes: #(INT32, INT32).
The SABLE code may not be more concise, but it is much more clear. In messages with multiple arguments, each argument is introduced by a keyword which tells you what role it plays in the message. What is typeof(int) contributing to the method call in the C# code? If you're not intimately familiar with the method, the code doesn't help you, and you'll have to go look it up. SABLE tells you it's the return type of the method being defined.
As method arguments become more complicated, there's just no really good way to format them in C#. Usually programmers "simplify" by extracting some argument expressions and assigning them to a temporary variable which will only be used once, as the programmer did in this case. SABLE's design lends itself to being nicely-formattable even when arguments get very complex, and in fact, SABLE includes an excellent automated formatter.
In C# you have to specify the class for each enumerated data value; in SABLE, you almost always omit the type for enumerated values, letting the compiler infer it from the context. Here, the second argument has type {METHOD_ATTRIBUTES}, so the compiler looks in that class for the literals and constants. For the human reader, the attributes: keyword tells you what those items are for.
This example looks through a group of MEMBERs looking for one who is a leader; if not found, we will compute a default leader result. Finally, this result will be passed as an argument to another method. Computing a default leader may be expensive, so we want to do that only if no leader was found in the group. For maximum generality, let's not assume that there is a valid no-value value (such as null) for type MEMBER, so we need another way to know if it finds a match.
// C#MEMBER leader; // Don't preassign the default; it may be expensive. bool haveLeader = false; // Don't assume MEMBER is a reference type. foreach (MEMBER member in groupMembers) { if (member.IsLeader()) { leader = member; haveLeader = true; break; } } if (!haveLeader) { leader = MEMBER.DefaultLeader(); } Console.WriteLine ("Leader: {0}", leader); "SABLE"CONSOLE writeLine: 'Leader: {0}' with: (groupMembers detect: [:member | member isLeader] ifNone: [MEMBER defaultLeader] ).
In most CLR languages, loops can only be used as statements; they cannot return a result. In SABLE they can, leading to a more functional style and more compact code. The same qualities hold for conditional (if) expressions and case-switching.
Also, in the above code snippits, the central actor is the groupMembers collection. Notice its relatively prominent position in SABLE, leading an expression rather than buried within a bunch of gobbledygook.
This example is derived from a .NET book using VisualBasic. The VB.NET is formatted as in the book, so one may assume it is written as an expert would train aspiring VB.NET programmers to write code. Our alternative shows how one would naturally write the same thing in SABLE. Which version is more quickly understood?
// C#Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs) Dim graphics As Graphics = e.Graphics Dim pen As New Pen(Color.Bisque) Dim area As New Rectangle(0, 0, 100, 80) graphics.DrawEllipse(pen, area) Dim font As New Font("Verdana", 12) Dim brush As Brush = New SolidBrush(Color.Black) graphics.DrawString(Me.Text, font, brush, 12, 40) End Sub "SABLE"onPaint: paintArgs {PAINT_EVENT_ARGS}. {~ override} paintArgs graphics drawEllipseIn: #Bisque at: #(0, 0, 100, 80) as_RECTANGLE; drawString: My text ` font: ({FONT} named: 'Verdana' emSize: 12) ` brush: #Black x: 12 y: 40.
Some have criticized Smalltalk for it's "weird" syntax, mainly regarding keyword message expressions, which would apply equally to SABLE. However, "weird" is a relative term. C-style method calls are patterned after function expressions, which university students see in mathematics classes: [f(x,y,z) = ...]. Keyword messages are patterned after forms, which everyone sees from time to time.
If you're used to C-based languages, remember that its method calling syntax was strange when you first learned it, but you got used to it. Just give keyword messages a similar chance. After a few days, you probably won't want to go back. If you need help, check out this article1.
I've shown a few examples comparing SABLE with the flagship CLR languages and argued that SABLE is more natural and readable. Each example in isolation is, perhaps, not awe-inspiring, but when a program reaches hundreds of lines (to say nothing of thousands or millions!) the improved readability becomes significant. We've also found that using the language changed the way we thought about a problem, which led to better algorithms for programs converted from Java and C#. Examples are found in the Substituter and Poker programs.
A more readable language results in increased productivity, especially over the long run as the code is reread and modified many times. SABLE's readability comes mainly from four factors: