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
Comments
What no pre/post-increment operators (yet)? Nothing looks as silly as x := x + 1 ...
Jef, December 23, 2005 1:56 AM
Hi Damien
Your work looks amazing and its very clear you are on another level than most developers (well ok many levels).
I'm surprised a corporate has not got you doing this for them - or maybe they just don't listen (or you dont like corporates!).
One question I have - do you think you will have any difficulties from IBM over this stuff - being that you used to work for IBM/Lotus or Iris - and worked on the formula engine.
Can they claim you are copying the work you did whilst under them (and paid)? Obviously some parts you thought of whilst you were there which I think you can claim as your own IP - but there maybe others which IBM could claim to be their IP.
I'm sure you must have thought this out - so im interested. I get similar problems when doing projects when I think of some cool way of doing things but then they pay me to do it - how much of that work can I reuse etc are all unknowns to me.
Steve Castledine, December 23, 2005 6:20 AM
Jef, actually it does already have increment and decrement!
Steve - I don't have corporate backing, and I don't think I need it. Not for almost another year. I just keep living off savings and building...
I'm writing all this from scratch, so IBM can have no issue with me. Not only that, the new engine has a different internal design from the one I wrote a Iris/IBM, as does CouchDb from NSF. But even if they had the same high internal design, they can't prevent me from using general techniques and knowledge I gained on the job (but I'm sure they'd love to try).
Damien, December 23, 2005 10:28 AM
Nice work Damien. I like the slice. Will fabric be exclusively embedded in couch? Will it have document scope or can it select documents and iterate over them? I would really like to have a dictionary and/or a set. In the equivalent of column formulas, PreviousSibling and Parent would be nice. The simplest use case is running totals on the fly.
Dan Sickles, December 23, 2005 12:11 PM
Dan, Fabric query formulas will have document level scope, architectually it the only way I know design it so it has decent performance and a robust design. Also, I'm trying very much right now to keep the language and uses as simple as possible, but as this things progress and I start build Couch applications, I'll definitely be looking for useful and productive tweaks. I'm sure I'm give you the functionality you desire, but maybe not exactly in the form you're asking.
Damien, December 24, 2005 11:33 AM
OK, now tell us about the agent/macro functions :)
Rock, December 24, 2005 2:29 PM
Very exciting, Damien! Just a quick idea. In your section on Field Concatenation, you use the example
foo := date1..15
What if instead of concatenating date1 through date15, this syntax built a list out of them? Then you could perform concatenation, subsetting, looping, etc. on the list instead of just concatenation.
Keep up the good work.
Chris, December 24, 2005 9:14 PM
Sorry, I didn't make it clear, that operator is indeed for list concatenation, not string concatenation.
Damien, December 24, 2005 10:17 PM
Noice. Me likes. (Intentional Newfy spelling.) Especially the dissimilar types in a list thingy -- one of my pet peeves with the Notes formula language is that one "bad" value will make everything text. Expected behaviour and all that, but still an occasional pain.
Stan Rogers, December 25, 2005 10:13 PM
Yup Stan, that's exactly why I made a single heterogenerous list type. I'd hate when I'd have a formula like this:
numberList := Amount1 : Amount2: Amount3;
That would blow up if Amount3 was blank. So you have to recode it as this:
numberList := @ToNumber(@Trim(@Text(Amount1) : @Text(Amount2): @Text(Amount3));
Blecch!
Damien, December 26, 2005 1:59 PM
Field concatenation and custom %functions: stuff like that is priceless. I love it. Can't wait to try out this product.
Ken Pespisa, December 28, 2005 5:17 PM
this is exciting stuff Damien, i'l keep watching with great interest
Mark, December 29, 2005 11:15 PM
What about using the Regina Rexx engine? The language is well understood and capable.
ckh, May 13, 2006 3:54 PM
INCREDIBLE idea, Damien!
Could we call code modules in FFL "swatches"?
And Interfaces, "stitches"?
Can the language be "multi-threaded"?
Thom Parkin, September 7, 2006 5:00 PM
Post a comment