A brief introduction to Fabric

Fabric is a simple programming language I'm building as a query and validation language for my Couch project. Fabric is intended to be a pragmatic and simplified language for the purpose of querying and processing data in CouchDb.

Fabric will have many of the same operators and functions as Notes Formula Language and should be most familiar to those who already know Formula language.

This is brief introduction to Fabric. I'm sure it help if you know Notes Formula Language already.

Basics
Hello World in Fabric:

"Hello World!";

Ok, not terribly useful, its sort of like a Hello World In SQL. Here is an expression that concatenates fields FirstName and LastName and assigns it to FullName.

FullName := FirstName + " " + LastName;

The colon-equals (:=) is the assignment operator and plus (+) is the string concatation operator (and it's the number addition operator when used with numbers). The semicolon (;) indicates the end of the expression.

Here is the same expression, but it converts the full name to uppercase:

FullName := Uppercase(FirstName + " " + LastName);

It has integer and floating point math support, some simple examples:

x := 5;
squared := x*x; // squared is 25
foo := (x + 7)*2 // foo is 24
bar := 5/2.5; // bar is 2

The syntax is similar to Notes Formula Language, with the most noticable difference is the lack of the @ sign in function identifiers. Also, argument separators are commas, not semi-colons:

SomeFunction(arg1,arg2,arg3)

Lists
All values are lists (or arrays if you prefer) and they may contain many different types of elements. One difference is instead of having typed lists like Notes Formula Language (string lists, numbers lists, dates lists, etc), there is only one list type, and it can contain any mix of strings, numbers, dates, etc.

Colon is the list concatenation operator. This example concatenates the lists in quantity1 through 4 into one list. Then it sums the total of the elements in the list:

foo := quantity1 : quantity2 : quantity3 : quantity4;
Sum(foo);

A mixed type list:

Foo := 1 : "123 Fake st" : 3 : 5.67 : [10/24/1973];

Type Conversion
Fabric has implicit type conversion:

Foo := "1" + 3; // Foo is "13"
Bar := Number("1") + 3; // Bar is 4
Baz := Sum("1" : 2); // Baz is 3

Functions and operations that operate on elements of one type will automatically convert, if possible, elements to the correct datatype. Otherwise an error is generated.

Field concatenation
To concatenate a bunch of numbered fields is a very common operation in formula language. I've created a simple syntax for that:

foo := date1..15

Will take the values in fields date1 through date15 and combine them into a single list.

This will combine all fields date1, date2, date3 and so one until a field in the sequence is missing:

foo := date1..*

Branching

foo := if(cond)(
   bar1
)(cond2)(
   bar2
)(
   bar3
);

and the terse form of the same expression:

foo := if(cond; bar1; cond2; bar2; bar3);

User Defined Functions
You can define and call your own functions:

// Foo is a function that multiplies two number lists
// and converts them to text
Function(Foo)(
   Text($1 * $2);
);


Bar := %Foo(2, 8); // Bar is "16"
x := 2 : 3;
y := 8 : 9;
Bar2 := %Foo(x, y); // Bar2 is "16" "27"

All user defined functions need the identifier prepended with "%". I'm doing it this way so there is no collision between the core functions. I've also considering using the "@" symbol, but I fear it might confuse Notes people. Although it would be cool to be able brag that Fabric lets you create your own @Functions...

So inside of function bodies, you reference the args passed in by the predefined argument variables $1, $2...$n, or by using the Arg(n) function, with n being the 1 based index to passed arguments. $ArgCount contains the number of arguments passed.

Looping
The only looping construct I'm including for iterating over elments in a list using a forall construct. The expression is evaluated for each element and the result is used to construct a new list.

In this example, it checks each element in List to see if it starts with the letter "b", if it does, it returns the Uppercase version of the element, otherwise it returns the element unchanged. These elements then go to constructing a new list and assigning it to Foo:

Foo := forall(Element in List)(
   if( Begins(Element, "b") )(
      Uppercase(Element)
   )(
      Element
   )
);

You can process multiple lists in parallel. This example, compares each element in two lists(1st compared with 1st, 2nd compare with 2nd, etc), and returns a third list that is composed of the largest element in each corresponding list:

Foo := forall(A in ListA, B in ListB)(
   if(A > B, A, B)
);

And if one list is shorter than another, the last element is reused in subsequent iterations.

So for example:

listA := 5 : 8 : 2 : 10 : 3;
listB := 6 : 7 : 9;
Foo := forall(A in ListA, B in ListB)(
   if(A > B; A; B)
);

This produces the result of Foo 6 : 8 : 9 : 10 : 9;

Array/List indexing
Brackets are used for array addressing, and is 1 based;

colors := "red" : "blue" : "yellow";
foo := colors[1]; // foo is "red"
foo := colors[2]; // foo is "blue"
foo := colors[3]; // foo is "yellow"

2 arguments has a special subset meaning:

colors := "red" : "violet" : "blue" : "green" : "yellow" : "orange";
foo := colors[2, 4]; // foo is "violet" : "blue" : "green"

The first argument is the start position, the second is the end position and a new list is created with all the elements from start to end, inclusive.


Ok, even though this isn't a very complete description of the language and how it can be used, I'm going to stop writing about it for now anyway. I'd love to hear any feedback about the language and and its design to shape it as I go forward. Everything I've described above is already working, but I still have a lot of work to do, and I'm sure as I integrate it back into CouchDb I'll be revisiting some of my previous decisions. Thats just how it goes.

A couple of people have inquired as to why I'm creating a new programming language. Why not just use some other popular language?

The answer is simplicity.

First of all, Fabric isn't a general purpose programming language, it's a domain specific language (DSL to programming language geeks). It is designed to be tightly integrated into the CouchDb data model and make querying and processing it easy. I couldn't find another language that would be a good match without also burdening the user with lots to learn.

These queries should to authorable by people with spreadsheet level programming experience. I'm creating a language that is similar in syntax to Excel Formula Language and very much like Notes Formula Language. By sticking to simplified language demands and a familiar syntax, the learning curve is lowered and people should be productive quickly. But I'm also choosing to base this language off Notes Formula Language because it has been proven very powerful and appropriately concise for these types of uses. I'm hoping to tweek its usefulness a little bit for the better.

It will become more clear once I create some real world examples with CouchDb. Its kind of hard to see the usefulness of a single part of Couch, it will become more clear once I demonstrate how they work together.

Posted December 22, 2005 11:02 PM