Frink Documentation * What's New * FAQ * Download * Frink Applet * Web Interface * Sample Programs * Frink Server Pages * Donate

Frink -- A Language for Understanding the Physical World

Alan Eliasen, eliasen@mindspring.com

Overview

I was invited to give a talk about Frink at the Lightweight Languages 4 (LL4) conference at MIT in December 2004. The following are the slides and notes from that talk, including some of the points that I didn't get to address due to time constraints.

This talk is intended to give a brief overview of Frink, its capabilities, reasons for creating Frink, and implementation details, all to a somewhat technical audience of professional language designers. It certainly does not address all of Frink's capabilities. Further information on Frink can be found in the Frink documentation.

This document is presented as a single file to make it easier to search, save, or print.

Watch the Presentation

You can also watch a recorded webcast of my talk with RealPlayer. My talk is in the second half of the Afternoon Session 2 file, beginning 33 minutes and 40 seconds into the file and continuing to the end. (You'll have to manually fast-forward to get there.)

Other presentations are available at the LL4 website.

Try Frink As You Read

If you wish to try some of these samples while reading the document, you can try Frink in a Web-based Interface, as an applet, or you can download Frink onto your own computer. (Links open in a new window.)

Table of Contents

You can jump to a slide directly using this table of contents, or simply scroll down for each slide.


Abstract

Physical calculations, when implemented in most programming languages, often become an impenetrable mishmash of dimensionless numbers and implied units of measure. Incorrectly mixing units of measure may lead to incorrect results that are difficult to detect, or may lead to catastrophic results, as evidenced in the failure of the Mars Climate Orbiter.

Frink was designed to track units of measure through all calculations, allowing the computer to perform much of the validation and bookkeeping necessary to ensure that answers are correct. Back-of-the-envelope calculations become trivial, and more complex physical calculations become simpler to write and read, and allow transparent use of any units of measure.

Frink was also designed to be a high-level language that can control simulations, and to be a lightweight glue language that leverages the power of existing Java libraries. To be a practical, multi-purpose language, Frink also incorporates text processing, the ability to utilize Internet-based data sources, arbitrary-precision math, object-oriented programming, design by contract, date/time mathematics, and an extensive standard library of data to streamline calculations.

This talk also discusses the difficulties and compromises necessary when creating a language that attempts to preserve standard mathematical notation. The promise and pain of implementing a programming language in Java will also be described. Sample programs in Frink will be demonstrated, from simple, one-line calculations, up to large, high-precision programs to calculate the next moon alignment with M.I.T.'s "Infinite Corridor."


The Problem

People have an intuitive feel for many units of measure. For example, I have an intuitive feel for a foot, a yard, a mile, a gallon, a pound, a minute, a day, a horsepower, and so on.

I remember a few facts and figures about my world, but they're stored in my brain in a wide variety of units.

I know:

Even if your head is hardwired in metric, the world doesn't necessarily get easier for you. The constants of nature aren't multiples of 10. And, despite the best efforts of everyone in the U.S. --(and don't be too surprised here,) there are still lots of facts recorded in a lot of different systems of measurement. And the person you need to communicate with thinks in BTUs and joules, and that's the way they want to see and enter the data.

Computers do our calculations for us, right? Well, not always. The modern programming landscape doesn't help us as much as it could, or should.


A Brief History of Frink

The first precursor to Frink was written about a decade ago at the University of Colorado's Center for Advanced Decision Support for Water and Environmental Systems (CADSWES). At CADSWES, we wrote simulation and modeling software to simulate river basin models.

The software package, which is now called RiverWare, was, used, and is still used, to model many river basins, hydroelectric plants, and habitats for the Colorado River basin, the basins administered by the Tennessee Valley Authority, and more.

The simulation engine modeled the physics of the river basin, but we also needed to model the operating policies and the "laws of the river." These were rules that were primarily written by lawyers and bureaucrats, and controlled how water was to be managed within the river basins and reservoirs. For example, the rules might specify that if a certain reservoir was more than 90% full, and it was a Sunday, that a certain amount of water would be released downstream.

The original language used to implement rules was TCL. The biggest thing TCL had going for it was that it was easily embeddable in a C++ program.

There were some problems with TCL, though. First of all, it was too slow, especially for the U.S. Bureau of Reclamation's huge, century-long, repeated stochastic runs that simulated every reservoir and major diversion structure in the entire Colorado River Basin.

The second problem was that users loved to write rules with nasty side-effects. Users would store "temporary" variables off somewhere, and use them later in the calculation. In Riverware, sometimes timesteps were solved out-of-order, and these "temporary" saved values might be for the wrong timestep.

The third major problem was that users wrote equations that assumed particular units of measure, which were not necessarily the same as Riverware's internal standard units of measure. Many of the rules were only deciphered by reverse-engineering old FORTRAN code, and unexplained, dimensionless numbers were everywhere. Users often wrote rules that expected values to be in their own units, and such errors were very difficult to detect, prevent, or debug.

The least-favorite unit I ever encountered was acre-feet per month. This is a measure of flow, enough to cover an acre of land one foot deep in water over the course of one month. The subtleties in this beautiful unit were many. The foot wasn't the international foot, but the older "survey foot", which is larger by one part in 500,000. (Or is it smaller?) But, by far, my favorite part about the "acre-foot per month" was to try to get someone to pin down what month they meant! Trying to pin anyone down on this usually caused them to yell, "look out! Behind you!" and then run away when you looked around.

In any case, we were tasked with designing a replacement. I was currently enamored of the UNIX "units" program which performed unit conversions and had some modest ability to perform calculations with units. I decided to use dimensioned units as a fundamental feature of our language. (I didn't tell my boss until I had that part written.)

As it turned out, automatic tracking of units through all calculations turned out to be a very popular feature of the software. Rules were easier to read, write, and understand, and the simulation ensured that values came out with the expected dimensions.

It was, in the long run, a rather special-purpose language designed for use primarily in that application.

But the love of writing code that handles units of measure invisibly and effortlessly never left me.


And then the farts came...

Then, 4 or 5 years ago, my lovely friend Heather May Howard sent me one of those endlessly-forwarded e-mails full of dubious but interesting facts, one of which said, "if you fart continuously for 6 years and 9 months, you'll have enough gas to create the equivalent of an atomic bomb."

My first reaction was, "that's gross." My second reaction, being a geek, is "that doesn't sound right. How can I verify it?"

So, I struggled a bit with a newer version of the UNIX "units" program, which didn't let me set variables, or re-use calculations, or take roots, or even parenthesize expressions. It also treated division at a different precedence than multiplication, and didn't expect more than one division in an equation, and was coming up with totally unexpected results. I worked out some of the math. I was in the middle of trying to figure the area of one's--um--to use the medical term, bunghole, when I realized something.

Well, first I realized that I really shouldn't be doing this on company time.

But the next thing I realized was that language like the one I'd written before, that tracked units through all calculations, and had a good library of data, would solve an infinity of similar problems, and make them trivial and pleasant.

Not nearly as poetic as Isaac Newton's seeing an apple fall from the tree, and seeing the moon in the sky at the same time--but my story at least has the virtue of being true. Richard Feynman said he got his Nobel Prize in physics from watching a guy throw a spinning plate in the air. Maybe I should come up with a slightly better story like that.


Let's take a look at a sample calculation, and demonstrate how Frink works in its simplest form.


A Simple Example

(This slide was a placeholder for a hands-on demonstration of Frink. The code samples below can be directly entered into Frink.)

Okay, we're already into the farts. We might as well use that as a sample calculation.

(6 years + 9 months)

The yield of a small atomic bomb is about 12.5 kilotons of TNT. The power is thus given by:

12.5 kilotons TNT / (6 years + 9 months)
245529.24306902837841 m^2 s^-3 kg (power)

The output is given in terms of base SI units, with the additional help that these are units of power. Usually, when formatting your results for output, you'll always want to convert them to a specific unit of measure using the -> operator. This both performs the conversion and ensures that the results have the expected dimensions. For example, to convert to watts:

12.5 kilotons TNT / (6 years + 9 months) -> W
245529.24306902837841

A 245,000 watt fart is pretty powerful. To put this in other terms, you can also convert this to, say, horsepower.

12.5 kilotons TNT / (6 years + 9 months) -> hp
329.26013859711396553

If you can manage a 329-horsepower fart, that's pretty impressive. What kind of food intake would you have to maintain to produce this much power? To see, you can convert this to Calories/day, keeping in mind that U.S. nutrition calculations (like on the back of a food package) assume a 2000 Calorie/day diet. Also note that Frink is case-sensitive. Food Calories are properly written with a capital "C", which are equal to 1000 calories with a small "c". The Calorie with a capital "C" is the unit that a physicist would call a "kilocalorie".

12.5 kilotons TNT / (6 years + 9 months) -> Calories/day
5.0668115508655899242e+6

So, you'd have to eat over 5 million Calories per day and convert that with perfect efficiency into pure fart energy to achieve the stated figures. Needless to say, these fart estimates are way too high. There is further analysis of this in the Frink documentation.

Note that all of these units are known to Frink and distributed with its standard data file. It should also be pointed out that "years" and "months" are not fixed-size units, so they should be treated with caution and only used for approximations.


Design Goals

Before building Frink (okay, I would have built it anyway,) I looked at several design goals that I wanted to be met by either an existing language, or a new language.

Goal: Preserve normal mathematical notation, when it's possible and unambiguous. Overall, standard mathematical notation is largely unambiguous, if you understand the millions of exceptions and can detect subtle changes in typography.

There are plenty of exceptions. Some mathematical operators are difficult or impossible to reproduce in plaintext, for example, exponentiation is represented by making a number a little smaller and higher up on the line.

Standard mathematical notation includes implicit multiplication! That's a biggie, and it forces the way that your language looks in many ways.

In addition, we want to break the rules of standard mathematical notation to make our programs more readable. In normal mathematical notation, xy means the variable x times the variable y. But we'd like to have more than one-letter variables, so x (space) y came to indicate a multiplication, but xy is a single variable name.

It's also understood in standard mathematical notation that "sin x" is a function, and not s times i times n. And you usually gotta guess that "sin x+y" has a particular precedence implied. Instead of trying to capture these unwritten rules, I chose to use brackets for functions.

In addition, it should be a complete language. It should be able to solve any task I throw at it, including text processing (that generally means easy text processing, with first-class regular expressions,) networking, and so on.


Anti-Goals

Don't try to parse English sentences.

English is ambiguous, and even the simplest things can be context-sensitive. For example, when you write:

miles per gallon

You expect it to mean the size of a mile divided by the size of a gallon. But when you write:

days per week

You expect to see 7, right? Well, if you follow the same rule as above, you divide the size of a day by the size of a week, and get 1/7. Even the word "per" is ambiguous in standard English usage, so I don't use it.

And, more generally, don't special-case yourself into a corner. Keep the language extensible, and not a forest of special cases.


More Design Goals

Other design goals include:


Units in existing languages (Java)

So how well do existing languages stack up? Of course, you can write a units library that's computationally equivalent in any language. I didn't want to write a new language before I'd verified that it couldn't be elegantly written in another languages. Do any other languages meet the design goals?

Java has an "expert group" specification, JSR-108. It's not necessarily concise. The above defines a meter and a velocity of 0.01 meter/second. Casting to the desired type is necessary after mathematical operations unless you want to store as an undefined Quantity.

This JSR has gone through the official process and was approved by most of the companies. It was later withdrawn by the specification lead, "because I don't have time to work on it." He says that other team members might try to revive it.

Other implementations in Java, such as Jmeasure, suffer from the same "no overloaded operators" problem that plagues all Java implementations. Mathematical expressions are hard to read, write, and debug.


Units in existing languages (Perl)

Alex Gough has written a clever Perl package called Data::Dimensions which performs some under-the-covers Perl wizardry to perform unit manipulation. It's still somewhat less than transparent, though.


Units in existing languages (Mathematica)

Mathematica has a units package that's not loaded by default, but performs some unit conversions--with interesting orthography. Note how prefixes have to be separated, and use capitalization rules that don't follow the rules set by the SI.

It also doesn't notice any conformance errors (say Foot + Second) until you use the Convert function.

Mathematica is one of the few languages that does implicit multiplication.

Unfortunately, it's too expensive for most people. My target audience includes high school students, elementary school students, hobbyists. And Mathematica's general-purpose facilities for, say, text processing are nonexistent.


Other Issues with existing languages

Number theory is one of my hobbies, and I like languages that can transparently handle numbers of arbitrary size with the same code. In some languages, say, Java, which doesn't allow overloaded operators, writing equations with the arbitrary-size BigInteger class is much, much harder to read than using ints or longs.

On the other hand, that can be slow. So I wanted Frink to use machine-size numbers when possible, and arbitrary-precision numbers when needed.

And, in one of the common issues with lots of programming languages,

1/2 is equal to ... zero?

This is the case in lots of languages like C++, Java, Python, Ruby, many LISP varieties, older Perls (although it's fixed in later Perls,) and it can create lots of subtle and not-so-subtle errors in programs that attempt to transliterate physical formulas into computer representations.

The programmer shouldn't have to manage this. The computer can tell when a number should be promoted from an integer to an rational number and back. And then on to an imprecise floating-point number. The performance hit is often quite acceptable--if it helps you always get the right number.

After looking at the alternatives, I decided that writing a new language would better meet my goals than trying to modify an existing language. So let's take a look at how those design goals affected how the language looks.


Curse you, mathematical notation!

First of all, I wanted implicit multiplication.

There are two implicit multiplications in the expression "3 foot pounds."

However, as you know, in standard mathematical notation, division is done at the same precedence as multiplication.

Thus, you have to parenthesize on the right-hand side of a division.


Parentheses for Grouping

Problem: With implicit multiplication, x(x+1) can be ambiguous. Is that a function call, or does it mean "x times (x plus one)"? It means you essentially can't use parentheses to indicate function calls.

The solution is to use square brackets to indicate function calls. By the way, this is what Mathematica does.

Square brackets are also used to indicate literal arrays.


Running out of Braces

I wanted to use curly braces for code blocks, like C/Java/Perl/etc.

This gives a problem with array dereferencing. You can't use square brackets to dereference items of an array... it would be indistinguishable from a function call.

Finally, I decided on using the @ symbol for array indexing (and dictionary indexing.) I'm still not sure I like it, but it's growing on me.

(Larry Wall has suggested scouring the Unicode character tables for more bracket types for use in Perl 6. I don't know if he's serious.)

By the way, Mathematica uses a[[1]] to indicate array indexes. I can't use that because Frink uses the square brackets to denote literal arrays, and the inner [1] could indicate a literal array. (Mathematica uses curly braces for literal arrays.)

I may be in trouble if I try to do implied multiplication between matrices or vectors.


Unit Manager

As Frink does unit tracking, it needs to recognize units with prefixes, suffixes, singulars, and plurals. The Unit Manager is responsible for most of this.

When Frink encounters an identifier that's not a local variable, it then queries the Unit Manager for a match.

If an exact match isn't found, it checks to see if it matches a known unit with the "s" or "es" suffix removed.

If a match still isn't found, it checks to see if the unit begins with any of its known prefixes. If so, it multiplies by the size of the prefix.

We're lucky that English has semi-regular pluralization rules. You just add s or -es to most words. There are some irregular ones like man->men and foot->feet and century->centuries. A UnitManager designed for other languages with completely irregular pluralization rules (like German) probably wouldn't work. Sometimes pluralization in German involves adding umlauts to vowels in the middle of the word, and so on.

For example, in German, one man is Mann, while two men are Männer.


"Magic" Unit Sources

Some units are time-variant, and may be fetched live from the internet. Some data sources may not even know in advance the exact units that they contain. (As a practical matter, these have a known prefix or suffix.) The Unit Manager may also query these "magic" data sources for additional units of measure.

For example, the following data sources in Frink connect to the internet for their data:


Representing Units

So, how expensive is it to track units through calculations?

A unit is simply a magnitude (which is a dimensionless number. The number may be of any of the numerical types that Frink represents, like arbitrary-size integer, arbitrary-size rational number, arbitrary-precision floating-point, complex number, interval, etc.) and its associated dimensions.

By default, Frink uses the base dimensions defined by the SI, plus two more. These are flexible, and can be set by a user-defined units file. You may also add more base dimensions on the fly in a running program (although it's very rare that you would do this.)

By the way, I don't follow the often-used exception for "kilobyte" silliness. The kilo- prefix is used to denote 1000, and not 1024 (although you can change your default units file to make this exception.)

Frink does have the International Electrotechnical Commission (IEC) prefixes for kibibytes and so on, though.


Dimension Math

Tracking dimensions throughout calculations is quite simple. You simply maintain a list of the exponents for each base dimension, as well as the magnitude of the value as a multiple of the base units.

To multiply, multiply magnitudes and add exponents.

To divide, divide magnitudes and subtract exponents.

To add or subtract, the list of dimensions must have same value for each exponent. Then, simply add or subtract the magnitude.

To perform exponentiation, multiply exponents by the power you're raising to.

In practice, exponents rarely exceed plus or minus 6. Frink doesn't limit the highest exponent allowed, but another implementation could.

This can be reasonably quick, and can even be packed into bit fields and several operations can be performed in parallel. For safety, this generally requires defining "guard bits" between each set of exponents to detect overflow.

Frink has optimizations to keep track of the highest dimension used, which allows this process to be even quicker. As a practical matter, manipulation of units makes up a negligible portion of runtime.


DimensionList interface

To make this more concrete, the DimensionList interface shows the methods that are used to track the dimensions of any unit.

Another class, called DimensionListMath actually does the math on these objects. This allows each DimensionList implementation to be as simple as possible, and not force it to inherit from another class.


Unit Interface

The Unit interface specifies the methods that must be implemented by anything in Frink that wants to be a unit of measure. It's very simple. One method to get the magnitude, the other to get the DimensionList.

Again, another class, called UnitMath, does the math on these objects.

The benefit of this being an interface is that each implementation can choose its own representation. For example, international currency conversions can defer the lookup of their magnitude until it's really needed. They know their dimensions (currency), but the lookup of the magnitude can be deferred until it's actually used.


Special Unit Operators

Special operators were defined to allow writing some English conventions.

Examples:

foot conforms metersConformance operator; returns true if the left-hand-side is a unit that has the same dimensions as the named DimensionList (e.g. length or velocity) on the right-hand-side (the right-hand-side can also be a string.) If the right-hand-side is a unit, this returns true if both sides are units with same dimensions, false otherwise. Hint: use the dimensions[] function to list all known dimension types.
square feetEquals 3 (feet^2) or, more simply, 3 feet^2. square squares the unit on its immediate right-hand side.
sq feetSame as square
cubic feetEquals 3 (feet^3) or, more simply, 3 feet^3. cubic cubes the unit on its immediate right-hand side.
cu feetSame as cubic
3 feet squaredEquals (3 feet)^2, indicating a square 3 feet on a side, or 9 square feet. This squares the multiplicative terms on its left-hand-side. squared has a precedence between multiplication and addition.
3 feet cubedEquals (3 feet)^3, indicating a cube 3 feet on a side, or 27 cubic feet. This cubes the multiplicative terms on its left-hand-side. cubed has a precedence between multiplication and addition.

Getting Help

If you don't know how Frink writes a certain unit of measure, enter part or all of the name preceded by a question mark. This will list all units that contain that word in any case.

Double-question-marks will return both the name of the unit and its value in default units.

If you want the value of the unit in specific units, you can use the arrow operator and the single question mark to display the value in a certain unit.


Objects

In Frink, although everything is not an object, anything can be an object by implementing a simple interface. The interface has three methods.

A FunctionSource is an object that gives you the methods declared on an object.

A ContextFrame is an object that lets you look up the variables contained in an object.

You can write your own object-oriented programs in Frink, too. Frink's classes look very familiar to someone who has written object-oriented programs in C++, Java or Ruby.

For more information, see the classtest.frink and interfacetest.frink samples which document object-oriented programming.


Java Introspection

One of the great features of Java is its introspection capability--the ability to create objects and call methods at runtime. This is a huge win... it allows you to leverage all of the libraries written in Java. The following demonstrates creating a GUI window from within Frink.

f = newJava["java.awt.Frame", "hi"]
f.show[]
f.toFront[]
f.setSize[200,200]
c = staticJava["java.awt.Color", "GREEN"]
f.setBackground[c]

This can be used to connect to databases, do networking operations, or whatever. You can leverage all of the vast number of libraries written in Java.


Embedding Frink into Java

Frink can also be embedded into a Java program. The sample above demonstrates the simplest way of embedding Frink into a Java program. It communicates via Strings, so the Java program needs to know virtually nothing about how Frink works.


Further Embedding

There are more methods for calling Frink from within a Java program. One of the major problems is that converting from Frink types to Java types is almost always a narrowing operation.

For example, if you try to put a Frink value into a Java integer:

As a result, all of these interface methods throw a variety of exceptions.

For more information, see the javadocs about Frink's integration methods.


Java -- The Good

Implementing a programming language in Java may sound crazy (and it is, indeed,) but Java has proven to be good in many ways:


Java -- The Bad

Implementing Frink in a language as restrictive as Java also has obvious liabilities:

In summary, I've implemented similar languages in C++, and I believe that Java has let me get farther in a shorter amount of time, although at a bit of a cost. Memory usage is much higher, programs run slower, and I swear a lot more when very useful language features aren't available. On the other hand, it's quite hard to make mistakes in Java.


What's Next?

Frink is functional and stable, but is far from being "done". New features are being added all the time. The following are some of the most important upcoming features, as I see them:


Implementation Details

During the course of implementing Frink, I tried other parser generators, including ANTLR, Scarab, SableCC, and JavaCC. I finally settled on a combination of the wonderful JFlex and JavaCUP.

The grammar is LALR(1), but it didn't start out that way. My first implementation was as an LL(1) grammar, but by the time I had done the left-factoring necessary just to implement the simplest infix mathematical operations, the parser had gotten so ugly that I decided that it was time for a change to an LALR grammar.

The lexer and parser are very loosely coupled with the language libraries. Loose coupling allows new parsers to be added relatively easily. For example, I may add a "Reverse Polish Notation" (RPN) interface for interactive calculations. In addition, it would sometimes be useful to create a minimal input syntax for handheld devices, especially webphones, which make it very difficult to enter text and punctutation.

Frink will run on any Java 1.1 platform or later. I make sure to only use APIs that are available in Java 1.1. This allows Frink to run on a wide variety of handheld devices, or as an applet in browsers with old Java Virtual Machines.

In addition, I have been using Java Generics features for over 3 years, using prototype compilers released by Sun and the GJ project. The early prototype compilers allowed code using Generics to compile and run on any Java Virtual Machine, without additional classes or changes to the runtime.

Unfortunately, Sun made a technically groundless, closed-door decision late in the process to change their implementation of Generics so that it would require changes to the virtual machine. As a result, current Frink distributions are compiled with an older prototype compiler so that a single distribution can work on any platform. It works fine.


So, why Frink, again?

So, to summarize, why did I create Frink, again?

To put it in other terms, here's a quote from Chris Pine:

"Well, I can tell you what I think the big deal is, anyway. It's easy to do things I would otherwise never do.

"This is something I try to tell one-trick-ponies...er, programmers... when I'm pushing them to learn a language other than C. The issue is not simply that I can write a Ruby program in 1/10th the time I can write that same C program. The issue is that, frequently, I will never write that C program! From a practical standpoint, in the realm of tasks I tend to use Ruby for, Ruby is infinitely more productive.

"What I love about Frink is that it opened up a new realm of tasks that I would never have bothered computing before. How many languages do that?

"I'll admit, the way I use it, it could very well just be an app... but it's pretty cool that it's also a language, and that if I want to push it just a little bit more, ask a little bit more of it, no problem; there's a whole language right there."


Documentation

Complete documentation on Frink can be found at the official Frink homepage.


Sample Calculations

The rest of the presentation involved some hands-on demonstrations of sample Frink calculations. Many of these calculations can be seen in the webcast, or in the Sample Calculations section of the Frink documentation, which contains far more calculations than I had a chance to address in the presentation.

For longer and more varied code samples, see the list of sample Frink programs.

MIThenge Predictions

One of the promised samples was to predict Sun and Moon alignments with MIT's "Infinite Corridor." Since this has become such a large topic in its own right, I have moved discussion of the calculations to my MIThenge pages. The programs to perform the analysis are still available at:


Comments/questions to Alan Eliasen.