The SABLE language was designed to be flexible, infinitely extensible by adding library features. Delegate blocks can take on new roles as well. As a result, it's extremely unlikely that the SABLE language grammar will ever need to change.
C# 2.0 added a nice feature that makes it much easier to iterate through a sequence of values. To support the same concept, SABLE adds primitive methods: a method pragma message >#iterator, and >#yield: and >#yieldAll: on the ENUMERABLE and ENUMERATOR classes (which an iterator must use as the return type). For example:
NODE[T] {~ object} {~ fields} |leftNode rightNode| {NODE[T]}. |value| {T}. valuesInOrder ^{ENUMERABLE[T]}. {~ iterator} "The values in the (sub)tree rooted at this node, in left-to-right order." Result yieldAll: leftNode valuesInOrder; yield: value; yieldAll: rightNode valuesInOrder. ^Result
// C#class Node<T> { Node<T> leftNode, rightNode; T value; // The values in the (sub)tree rooted at this node, in left-to-right order. public Enumerable<T> valuesInOrder() { foreach (T item in leftNode.valuesInOrder()) yield return item; yield return value; foreach (T item in leftNode.valuesInOrder()) yield return item; } }
An iterator uses Result, the predefined identifier which represents the return object. It sends >#yield: and >#yieldAll: to place values into the iterator's result sequence. When the argument to >#yieldAll: is a recursive call, the recursion is replaced with an internal stack, significantly improving performance. The iterator returns Result which ends the sequence.
Visually and conceptually, iterators appear like any other method, where the enumeration sequence is populated and returned. Runtime is optimized as in C#; each enumeration value is returned immediately when discovered.
Anonymous types, created implicitly by usage, are easily expressed in SABLE syntax.
"SABLE""Assume a variable :emp of type {EMPLOYEE} with obvious selectors used below. Create an anonymously-typed instance." |anonEmp| := {?} new id: emp id; name: ('{0} {1}' format: emp firstName and: emp lastName); numDependents: emp dependents size + 1; yourself. "Now access one of the fields." CONSOLE writeLine: anonEmp name.
// C#// Assume a variable :emp of type {EMPLOYEE} with obvious selectors used below. // Create an anonymously-typed instance. var anonEmp = new { Id = emp.Id, Name = string.Format ("{0} {1}", emp.FirstName, emp.LastName), NumDependents = emp.Dependents.Length + 1 }; // Now access one of the fields. Console.WriteLine (anonEmp.Name);
We saw {?} previously with Nilable type conversions.
You are probably familiar with LINQ method calls using dot notation as follows. Assume GetCustomers() returns an ArrayList, so that typecasting each element is necessary.
// C#Company.Contact[] contacts = database.GetCustomers() .Cast<Company.Customer>() .Select(cust => new Company.Contact(cust.Id, cust.Name)) .ToArray<Company.Contact>();
Let's see how we would represent that in SABLE.
"SABLE"|contacts| := database allCustomers linq ~[Company.CUSTOMER].cast; collect: #[:cust | {Company.CONTACT} id: cust id name: cust name]; to_ARRAY.
Message >#linq on the enumerable ARRAY_LIST results in a virtual object of a primitive (compiler-implemented) type. That object receives the LINQ messages ensuring that the result of one message becomes the receiver for the next, starting with the receiving ARRAY_LIST.
The first cascaded message shows SABLE syntax for specifying the parameter type for a generic method. This ensures each object in the list is cast to a particular type.
As in Smalltalk, the SABLE idiom for converting each element of a collection to a different object is >#collect:. In this case the receiver is a delegate block, but instead of converting to a delegate, it converts to a LINQ expression. This opens the door to supporting compiler plug-ins, where delegate blocks can be compiled in a certain way depending on the target type. Blocks and delegate blocks are SABLE's syntax for lambda expressions.
Finally, the resulting enumeration is converted to an ARRAY[CONTACT]. Note that there is no need to specify the method parameter for >#to_ARRAY or the variable type for contacts; as usual, both can be inferred from the context. Here both derive from the result type of the delegate block,
The exact syntax for LINQ query expressions is still being worked on.