Skip to the main content of this page.
The sable mammal, Martes zibellina
SABLE Programming Language

Boost Your Productivity

SABLE's predecessor, Smalltalk, is widely known for its productivity versus C-based languages. Opinions differ as to why. I believe this blog post has identified it correctly: "There is no mechanism available to the Smalltalk programmer to create programs other than the creation of [Extended Domain Specific Languages]." (It's more accurate to replace "language" with "dialect" here, but that doesn't alter the argument.) The same observation holds for SABLE.

In plainer terms, every class has a meaning; it establishes a conceptual domain, and it provides operations with names that have meaning within that domain. Furthermore, (with SABLE macros) you can always add new operations — even control structures — that have value within the domain and name them accordingly. As noted here, this raises the level of abstraction that's available when entities of the class are used, and that increases productivity.

In SABLE, you're not limited to those methods which a library class happened to provide, or to a small set of control structures. You don't have to write the same usage patterns repeatedly, or bend computing the value you need around the few control structures the language makes available, as with our loops example.

Roll Your Own Control Structures

The easiest place to show the benefits of macros are with the collections classes, including ENUMERATOR and ENUMERABLE. (Without resorting to delegates) The C-based languages provide three ways to iterate through a collection: 1) loop and retrieve elements by index, 2) create an enumerator to advance through and get the current element, and 3) use a language statement which does #1 or #2 for you. These all specify how to get through the collection (implicitly for #3), now what you're trying to accomplish. That's not very abstract.

SABLE permits such methods as well, but so many more...

aList do: [:elem | ACTION].
    "-Perform actions with each element."
aList doWithIndex: [:elem :index | ACTION].
    "-Perform actions with each element and a 0-based index."
aList do: [:elem | ACTION] betweenDo: [BETWEEN_ACTION].
    "-Perform actions with each element; do something additional between any two."

aList pairsDo: [:elem1 :elem2 | ACTION].
    "-Peform actions with even/odd-indexed elements from the list."
aList1 with: aList2 do: [:elem1 :elem2 | ACTION].
    "-Peform actions with same-index elements from two lists."
aList prefixing: additionalElem do: [:elem | ACTION].
    "-Peform actions with an additional object, then with each element."
aList suffixing: additionalElem do: [:elem | ACTION].
    "-Peform actions with each element, then with an additional object."

aList allSatisfy: [:elem | CONDITION].
    "-Do all elements meet the condition? Stop when we know."
aList anySatisfy: [:elem | CONDITION].
    "-Does any element meet the condition? Stop when we know."
aList select: [:elem | CONDITION].
    "-Get a list of all elements which meet the condition."
aList detect: [:elem | CONDITION].
    "-The element which meets the condition. Stop when we find it."
aList detect: [:elem | CONDITION] ifNone: [DEFAULT_VALUE].
    "-The element which meets the condition, or the default if none."
aList indexWhere: [:elem | CONDITION].
    "-The 0-based index of the element which meets the condition, -1 if none."

aList collect: [:elem | FUNCTION].
    "-A new list with each element converted to a different value of some type."
aList1 inject: initialValue into: [:accumulatedValue :elem | FUNCTION].
    "-Starting with an initial value, the result of modifying it using each element."

Etc. Collections therefore provide a rich vocabulary of terms to specify what information you want. For example:

"Are all characters in STRING loanIdentifier digits 0-9?"
|allDigits| := loanID allSatisfy: [:char | char isDigit].

"Get all employees who are managers."
|managers| := employees select: [:emp | emp isManager].

The same logic applies to the DICTIONARY interface. Each of the following is a macro, defined using >#tryGet:into:.

aDictionary atOrNil: key.
    "-The element at the key, or Nil if it's absent."
aDictionary at: key ifAbsent: [DEFAULT_VALUE].
    "-The element at the key, or the default value if it's absent; cannot be Nil."
aDictionary at: key ifAbsentPut: [DEFAULT_VALUE].
    "-The element at the key, or the default after inserting it if absent."
aDictionary at: key ifPresent: [:elem | ACTION].
    "-The result of ACTION given the element at key, or Nil if absent."

Etc. For example:

"Group types by their namespaces."
|typeMap| := {DICTIONARY [STRING, LIST[TYPE]]} new.
typeList do:
    [:type |
     (typeMap at: type namespace ifAbsentPut: [{} new])    "Type inferred as {LIST[TYPE]}"
        add: type].

For fun, rewrite these examples in one of the standard CLR languages.

Control structures are not just for collections. Consider this message which applies to all DISPOSABLE objects.

platform newDevice disposeAfter:
    [:device |
     "Use the device, then dispose of it."].

C# has such a language construct. SABLE just has this little message — and thousands more like it.

In Summary

SABLE macros (inlined methods), especially in combination with blocks, can greatly improve productivity during software development and maintenance.