Frink

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

About Frink

Frink is a practical calculating tool and programming language designed to make physical calculations simple, to help ensure that answers come out right, and to make a tool that's really useful in the real world. It tracks units of measure (feet, meters, kilograms, watts, etc.) through all calculations, allowing you to mix units of measure transparently, and helps you easily verify that your answers make sense. It also contains a large data file of physical quantities, freeing you from having to look them up, and freeing you to make effortless calculations without getting bogged down in the mechanics.

Perhaps you'll get the best idea of what Frink can do if you skip down to the Sample Calculations further on this document. Come back up to the top when you're done.

Frink was named after one of my personal heroes, and great scientists of our time, the brilliant Professor John Frink. Professor Frink noted, decades ago:

"I predict that within 100 years, computers will be twice as powerful, ten thousand times larger, and so expensive that only the five richest kings of Europe will own them."

Features

For those with a short attention span like me, here are some of the features of Frink.

Get Notified

Frink changes almost every day. If your version of Frink is more than a few days old, you're probably out of date! The latest versions are always available here. Keep an eye on the What's New page to see new features and keep abreast of its rapid developments. If you'd like to be informed of new releases via e-mail, subscribe to the "Frink Language" project at freshmeat.net. (Link opens in new window.)

Donate

You can download and use Frink free of charge. If you find Frink useful, there are lots of ways you can donate to its further development. I'd really appreciate it!

Mailing List

If you'd like to discuss Frink with others, you might want to join the mailing list. (Hosted by Yahoo! Groups. Link opens in new window.)

Subscribe to Frink

Presentations and Papers

You can read (and watch using RealPlayer) my presentation Frink -- A Language for Understanding the Physical World that I gave on Frink at the Lightweight Languages 4 conference at MIT. This discusses some of the design decisions of Frink, how it has evolved, implementation details, and future directions for the language.

Table of Contents

Using Frink

Try as you read

If you want to try the calculations as you're reading, click here to open the web-based interface in a new window. The web-based interface gives hints for new users, which may make it the easiest way to learn how to use Frink.

If you have a frames-enabled browser, and you don't see a Frink sidebar to the left, you can also click here to try Frink in a sidebar as you read this. (The sidebar mode doesn't give as many hints, though.)

Download using Java Web Start

For the incredibly impatient (who have a recent version of Java installed):

Click here to install Frink using Java Web Start.

Read on if you're slightly more patient, or if this doesn't work for you.

This method of installation requires Java Web Start, which is installed with recent versions of Java. Using Java Web Start is a great way to run Frink if you don't need to run programs from the command-line. (But you can still write and run programs from the GUI using Java Web Start!) If you do want to run programs from the command-line, see the Downloading Frink section below. Java Web Start will allow you to automatically get the latest version of Frink and will update Frink automatically when new versions are available.

Installation Steps

  1. If you don't have a recent version of Java, you can get it from Sun. (Link opens in new window.)
  2. (Optional) If you've never installed anything with Java Web Start, please read and understand the FAQ entry about the security warnings you'll see (link opens in new window) and your alternate download options.
  3. Click one of the options below to install Frink with either interface (see the screenshots below):

    You can install both, actually, with no problems.

If you've read those security notes, and understood what the security messages are telling you, and the warnings are still too scary, (and you don't want to send me the $400 it would cost me to remove at least one of them,) and you'd rather download a limited version of Frink that runs in the most restrictive security sandbox (breaking some features), then click here to install a limited version of Frink. Again, please read those security notes to see what features will be unavailable if you choose this option. You can always get the full version of Frink later if you need those features.

If someone wants to send me the $400 necessary to get a VeriSign "Code Signing Cerificate", I'll sign it just for you. It won't work any differently.)

If you have an old version of Java Web Start, Frink will probably show up in the "Downloaded Applications" section of the Java Web Start panel which isn't immediately visible. Use the View menu option to select the Downloaded Applications tab. It will also let you create a Frink shortcut on your desktop or in your start menu. The defaults in Java Web Start before version 1.4.2 are set oddly so that the second time you run Frink, it will ask you if you want to make a shortcut.

If you're using Linux, and Sun's Java release, only Java version 1.5 beta and later will install shortcuts onto your desktop and start menu. Highly recommended.

User Interface Options

Swing User Interface

The Swing version allows mixed fonts and colors. Due to some performance bugs in Sun's Swing implementation (like large paragraphs taking several minutes to paint every time you resize or scroll,) it can be problematic. As of 2008-08-25, the capabilities of the Swing and AWT interfaces are about the same.

Swing GUI Screenshot

AWT User Interface

The AWT user interface has several modes. The two-line conversion mode and programming mode are shown below. Small devices usually can't run Swing, but all Java platforms should be able to run AWT.

AWT Two-line conversion screenshot

AWT Programming Mode screenshot

Frink As An Applet

If your web browser supports Java 1.3.1 or later, try the Java Applet-based interface. It looks and works just like the GUI above, but it requires you to be connected to the internet and must download for each session. Your browser must support Java 1.3.1 or later, or you will need to get download a newer version of Java from Sun. It is extremely highly recommended that you have Java 1.5.0 update 2 or later. This has been tested with Internet Explorer, Netscape 4.x, Netscape 6+, Mozilla (Windows and Linux), and Opera.

If you don't have a recent version of Java, you can get it from Sun. (Link opens in new window.)

(The certificate is just signed by me, so you'll get a warning. Network access is necessary to use the network portions of Frink... like currency calculations, translations, etc. If you deny network access, the non-network parts of Frink will work just fine. If someone wants to send me the $400 necessary to get a VeriSign "Code Signing Cerificate", I'll sign it just for you. It won't work any differently.)

Minimalist Web Interface

If the applet doesn't work for you, try the minimalist web interface. The minimalist interface is intentionally very spare but it should allow you to use the latest version of the Frink engine. It is now powered by Frink Server Pages.

In this web interface, you can enter any Frink expression in the "From:" box. If you also enter a value in the "To:" box, it is treated as the right-hand side of a conversion expression (that is, to the right of the conversion operator -> )

Thus, to convert 10 meters to feet, you can enter 10 meters in the "From" box and feet in the "To" box, or, equivalently, type 10 meters -> feet in the "From" box and leave the "To" box empty. It does exactly the same thing.

Wireless

If you have a wireless Palm VII, download the Frink web clipping application (PQA) to use Frink anywhere, anytime.

Palm VII Screenshot

If you have a webphone that speaks HDML, or WML, point it at
http://futureboy.us/frink/
for the world's most powerful pocket calculator!

HDML webphone screenshot

If the webphone version doesn't work right, it may be because I can't detect that your device is a webphone. If that's the case, please use your webphone to visit:
http://futureboy.us/info/
And then send me an e-mail (from anywhere) telling me about the problem and when you tested, and that should give me enough information to fix the problem.

Browser Shortcuts

If your browser is Mozilla or Netscape 6+, click here to add a Frink sidebar. (You may need to hit the F9 key or go to View | Show/Hide | Sidebar to display the sidebar.)

Search Plugins

If you're using Netscape, Firefox, Mozilla, or another browser that supports "Sherlock" search plugins, click here to add a Frink search plugin.

Frink Browser Plugin Screenshot

Browser Keywords

Another cool way of having Frink readily available is to use your browser's custom keywords feature. [Notes for Mozilla/Netscape 6+] In short, you make a bookmark with a replaceable parameter like below. For Mozilla/Netscape 6+, just right-click on the link below, choose "Bookmark this link" and then follow the steps below to give the link a keyword.

Frink calculation: http://futureboy.us/fsp/frink.fsp?fromVal=%s
Unit Lookup: http://futureboy.us/fsp/frink.fsp?lookup=%s

Now go to "Manage Bookmarks" and give the bookmark a keyword like frink. Then, in your browser's URL bar, you simply type something like

frink 10 yards->meters

and there you are. (Now if you could only sew Frink into your clothes...)

Downloading Frink

If you're just using Frink for interactive calculations, or are happy using the built-in programming mode and you're not writing running programs from the command-line, see the Java Web Start section above.

If you're looking for an installer for handheld devices, see the Small Devices section below.

If you want to write full Frink programs and run them from the commmand-line, you will need to get your own copy of Frink, and have a Java 1.1 or later runtime environment on your machine, 1.4.2+ is recommended as it's less buggy. The date calculations in anything before Java 1.3 are rather bad,) you may download the latest executable jar file. (Note that this changes almost daily as I do more work, so download often.)

Otherwise, here are the steps to downloading Frink:

Experimental Versions

For those who want a standalone, all-in-one Frink download, there is an experimental, unsupported version of Frink compiled for Windows only using an experimental, unreleased version of the GNU compiler for Java (GCJ). This only works in command-line mode, but requires no other downloads and may start up more quickly. It is appropriate for quick calculations and command-line scripts. Not all functions may work. Let me know about parts that do or don't work for you. It is compressed with UPX to reduce the file size (possibly at the cost of some startup time.) This version starts up more quickly than the Sun JVM, but runs programs about 5-6 times slower.

(Unfortunately, it's compiled without optimization, because that pegs the CPU for over 72 minutes, and then blows out my system after trying to use over a gigabyte of virtual memory. Anyone want to donate me a new computer with tons of memory? Or try compiling it with -O3?) For Windows, you can use this experimental gcj 4.3 eclipse-merge-branch version.

Hint: If you install the gcj package linked above, or have a working GCJ (4.3 or later is required) for other platforms, the command line to compile with full optimization will be something like:

gcj -O3 -fomit-frame-pointer --main=frink.parser.Frink -o frinkx.exe frink.jar

Small Devices

Frink can run entirely on handheld devices like the any phone running the Android platform, Sony Ericcson P800, P802, or P900 smartphone, the Nokia 92x0 Communicator (Nokia 9210, 9210i, and 9290), and the Sharp Zaurus.

The installer is built as part of the Frink release process, so these versions will be up-to-date with the latest Frink. (The version numbers in the installers may not change, though.)

Download the installer for the following platforms:

Notes about running Frink on other devices, including notes about why I probably won't provide releases for newer Symbian devices that require their "Symbian Signed" abomination, please see this FAQ entry.

If you have problems running any of these, please contact Alan Eliasen. Since I don't own any of these devices, I rely on others for testing and detailed bug reports. (The emulators don't always work like the real devices!) It's possible for bugs to slip in that work under normal testing, but cause problems on the limited/different JVMs on these devices.

If anyone knows of a Symbian 6.0 device with the "Quartz" user interface that supports PersonalJava, please let me know and I can give you an installer to test.

If you know of a device that supports PersonalJava 1.1 or better, including the java.math package and floating-point math, and you think Frink would run on this device and you would like to help test it, please suggest it to me..

Photograph of Frink running on a real SonyEricsson P800 Smartphone

Screenshot of Frink on a SonyEricsson P800 Smartphone Emulator

Screenshot of Frink on a Nokia 92xx Emulator

Dashboard Widget

Frink is also available as a Dashboard Widget for Macintosh.

[Download Widget].

You should have Java 1.5.0 update 2 or later to run this. It'll work with other versions of Java, but they have known bugs. If you don't have it, download a newer version of Java from Sun. After installation of Java 1.5, you should immediately also go into your Java Control Panel/Java Preferences and make sure that Java is actually configured to use JDK 1.5.0 (also called J2SE 5.0) when launching applets and JNLP (Java Web Start) files! It does not appear to use 1.5 automatically! (You may have to open the Preferences window twice on the Mac just to see the JNLP option selector, which is often empty on the first run!)

Note that when Frink first starts up as a widget, it might appear to hang. This is because Sun's Java installation pops up a dialog box behind other windows that asks you if you want to trust the Frink applet. You may have to hunt for this window. You can choose to "always trust" the application so you don't have this problem repeatedly. This was supposedly fixed in Java 1.5, but it still seems flaky, because Java 1.5 seems not to be used by default. Note the above paragraph which shows how to make sure your installation is actually using Java 1.5 or later.

It should be noted that using the Java Web Start method of installation is in all respects superior to the widget: it updates itself automatically when Frink changes, is easily resizable, can create file associations, and more. But the widget exists for all the kids who are crazy about the widgets.

Google Gadget

If you use Google's customized homepage, you can add a Frink gadget to it by clicking the icon below:

Add to Google

Alternately, you can add the following URL manually:

http://futureboy.us/frinkdocs/FrinkGadget.xml

Programming Mode

If you want a unified environment to write, run, save, and load Frink programs, try the programming mode. You can either start this mode explicitly (see the Running Frink section below) or, from the AWT GUI, choose the menu option Mode | Programming.

This mode is primarily designed to allow programming on small devices, but can run on any platform.

Programming mode running on a Nokia 92xx Emulator

Programming mode running on a SonyEricsson P800 Emulator

The Data menu option allows you to choose between the standard data file and an alternate data file. You'll usually want to use the standard data file, but on small devices, it can take a long time to start your program, and may use a fair amount of memory. The standard data file is big. In that case, you may want to make a pared-down (or even empty) units file and use that when running your programs.

For now, selecting a different data file is not a persistent setting. This setting will only remain in place until you exit Frink.

Running Frink

On many platforms, you can start Frink in the AWT GUI mode by simply double-clicking the frink.jar file.

If you want to run Frink in command-line mode, here are a couple of sample scripts you can use to start Frink. You will need to edit them to match the paths on your system!

In the samples below, you may need to replace java or javaw with the full path to your Java Virtual Machine, whatever that may be. Note that javaw is a Windows-only command that simply starts Java without opening a console window. You'll probably replace this with java on other platforms.

To run the jar file in text mode, use:
java -cp frink.jar frink.parser.Frink [options]

To run the jar file with the AWT GUI which gives access to several modes, including programming mode, use:
javaw -cp frink.jar frink.gui.InteractivePanel [options]

The AWT GUI is the default action for the jar file, so this is the same as saying:
javaw -jar frink.jar [options]

To run the jar file with the Swing GUI, (shown above under Java Web Start,) use:
javaw -cp frink.jar frink.gui.SwingInteractivePanel [options]

To run the jar file and start the GUI in programming mode, use:
javaw -cp frink.jar frink.gui.ProgrammingPanel [filename]

If a single filename is specified in programming mode, this file will be loaded into the interface.

To run the AWT GUI in full-screen size (this is primarily for small devices,) use:
javaw -cp frink.jar frink.gui.FullScreenAWTStarter [options]

Depending on your operating system, I recommend that you write a shell script, batch file, or create a shortcut to let you run this even more easily (see below for samples.) To exit, use Ctrl-C, or send your platform's end-of-file character (usually Ctrl-Z or Ctrl-D), possibly followed by carriage return. Or just close the window.

See the Proxy Configuration below for additional options if you're running behind a HTTP or FTP proxy server.

Sample Start Scripts

In the samples below, adjust the paths to your java executable and your frink.jar file to match your system. All samples will start in command-line mode. See the Running Frink section immediately above to start in different modes.

Bourne shell:
#!/bin/sh
java -cp path/frink.jar frink.parser.Frink "$@"

Windows batch file:
@java -cp path\frink.jar frink.parser.Frink %1 %2 %3 %4 %5 %6 %7 %8 %9

Also see the Performance Tips section below to see how to improve speed.

Command-Line Options

Arguments passed in on the command-line are treated as names of Frink programs to be executed. Other command-line options are listed below.

If you just want to have Frink calculate something and exit, you can pass arguments on the command line using the -e [string] switch. Each command-line argument following the -e will be interpreted as a Frink expression, making it easy to run Frink from other applications:

java -cp frink.jar frink.parser.Frink -e "78 yards -> feet"
234.0

Other command-line options:

SwitchDescription
-f filenameDeprecated. Load and run the specified file. If this option is specified, the program will not receive any following command-line arguments. The -f switch is no longer required or recommended unless you are loading multiple files. Normally, you will just specify the filename to load as the last command-line argument.
-kRemain in interactive mode after loading file or parsing command-line arguments. This is very useful if you want to load definitions from a file and then go into an interactive session.
-u filenameSpecify a different units file than the default. This allows you to change the fundamental dimensions that you like to use, or change my definitions that you don't agree with. You can download my latest data file (normally included in the .jar file) and modify it to suit your needs.
--nounits
-nu
Don't load a units file at all on startup. This will improve startup time, but will break all programs that use any of the standard units. No units of measure will be defined at all.
-I path Appends the specified path to the paths that will be searched when a use statement is encountered in a program. This may be either an absolute or relative file path. You may specify multiple -I arguments on the command-line, and the paths will be searched in the order they are specified.
--encoding str Specify the character encoding of all following Frink program files. This option must precede the filename that it modifies. Frink programs can now be more directly written in any language and encoding system. This switch is only necessary if your system's default encoding (as detected by Java) is different than that of the program file you're loading.

The encoding is a string representing any encoding that your version of Java supports, e.g. "UTF-8", "US-ASCII", "ISO-8859-1", "UTF-16", "UTF-16BE", "UTF-16LE". Your release of Java may support more charsets, but all implementations of Java are required to support the above. Check the release notes for your Java implementation to see if other charsets are supported.

If you specify multiple files having different encodings using multiple -f directives, you can use something like --encoding "" to set the encoding back to your system's default.

This flag does not alter the behavior of files opened using commands like read[] or lines[]. To change their behavior, use the two-argument versions of these commands.

-v
--version
Print out the Frink version and exit. (From inside a program, you can call the function FrinkVersion[] to return the current version.)
--sandboxEnables Frink's internal "sandbox" mode so you can run untrusted code. This is different from Java's sandbox, in that it enables only Frink's notions of what should and shouldn't be allowed. It disallows programs to define functions and many other things, so it's rarely useful to the end-user, and hardly any programs will run this way. It's really more for my testing.
--ignore-errorsIgnores syntax errors when parsing a program and attempts to ignore those lines and recover and run the program. Generally a very bad idea, but this flag was added to preserve old, excessively-permissive behavior.

Any command-line parameters after the name of the program to be executed are passed to the program as an array called ARGS.

GUI Options

You can specify the width or the height of the window for frink.gui.InteractivePanel or frink.gui.FullScreenAWTStarter. You may specify width or height or both. For example:

java -cp frink.jar frink.gui.InteractivePanel -width 500 -height 400

Performance Tips

There are several things you can do to make your Java Virtual Machine (JVM) run Frink more quickly:

Proxy Configuration

If you use a HTTP or FTP proxy server, you need to add some options to your command lines (say, right after the word java) to use the proxy if you want certain functions to work. HTTP and FTP are used for the following:

The following are settings for Sun's distribution of Java 1.4.1. You may need different options depending on your Java distribution. See Sun's Networking Properties documentation for more properties you may need if you're on a network that requires more proxy settings.

HTTP proxy:
-Dhttp.proxyHost=proxyname -Dhttp.proxyPort=portnum

FTP proxy:
-Dftp.proxyHost=proxyname -Dftp.proxyPort=portnum

These settings should not be necessary when using the applet version or the Java Web Start version, as these inherit the proxy settings from your browser or the Java Web Start Application Manager respectively.

How Frink is Different

Frink is, first and foremost, designed to make it easy to figure out things. If there's a unifying principle in Frink, it could be considered to be the normalization of information. I'm trying to simplify and unify the representation of data so that you can perform all sorts of interesting operations on them. Whatever that means.

Frink is optimized for doing quick, off-the-cuff calculations with a minimum of typing, primarily so it can be used with handheld devices which can make text entry difficult (especially symbols). This doesn't mean that Frink is unsuitable for doing large, very high accuracy calculations. It does those well, too, and the complicated calculations look just like the simple ones.

To give an example, Frink represents all numerical quantities as not simply a number, but a number and the units of measurement that quantity represents. So you can enter things such as "3 feet" or "40 acres" or "4 tons", and add, subtract, multiply, etc. these things together. Frink will track the resulting quantities through all calculations, eliminating a large category of errors. You can add feet, meters, or rods all in the same calculation and the details are handled transparently and correctly.

It also knows the ways that these units are interrelated-- a length times a length is an area; length3 is a volume (if you believe in the hypothetical Z axis); mass times distance times acceleration is energy. If you know something in one system of measurement you can convert it to any other system of measurement.

All units are standardized and normalized into combinations a small number of several "Fundamental Dimensions" that cannot be reduced any further. These are completely arbitrary and configurable but are currently:

QuantityFundamental UnitName
length m meter
mass kg kilogram
time s second
current A ampere
luminous_intensity cd candela
substance mol mole
temperature K Kelvin
information bit bit
currency USD U.S. dollar

Look at the data file for these definitions (and my editorializing on the boneheadedness of many these choices.) The data file recursively defines all measurements in terms of the fundamental units.

An exponent can be attached to each dimension. For example, an area is length * length which might be represented as meters^2. Of course, a negative exponent indicates division by that quantity, so meters/second will be displayed as m s^-1, or acceleration (which can be represented as meters per second per second) is represented as m s^-2.

Numeric Types

Numeric values in Frink are represented in one of three ways:

Integer
An arbitrarily large number with no decimal part. Represented as a number with no decimal point, (e.g. 1000000000) or the special "exact exponent" form 1ee9. An integer can also contain underscores for better readability, e.g. 1_000_000_000
Rational
An arbitrarily large number which can be written as integer/integer ( such as 1/3 or 22/7 ). Rational numbers are first reduced to smallest terms; that is, 2/10 is stored as 1/5 and 5/5 is stored as the integer 1
Floating Point
An arbitrary-precision floating-point number. Currently, the number of decimal places calculated or displayed is limited to 20 for efficiency reasons. Any number containing a decimal point is a floating-point number, such as 1. or 1.01132, as well as any approximate exponential such as 2e10 or 6.02e23.
Complex Numbers
Complex numbers are any number with an imaginary part. The imaginary unit is specified by the symbol i. For example, 40 + 3 i. The real and imaginary parts of a complex number can be any of the numerical types listed above.
Intervals
An interval represents a range of values, such as [2, 3] where, depending on your interpretation, the actual number is unknown, but contained within this range, or the number simultaneously takes on all values within the range. See the Interval Arithmetic section of the documentation for more information.

Data Libraries

Frink knows about a wide variety of measurements. You can usually type a unit of measurement in a variety of ways. Plurals are usually understood. Case is important (and somewhat arbitrary until I do some normalization and cleanup of the units file, but usually lowercase is your best choice.) The following are all examples of valid units:

If you're looking for a specific unit, and don't know how it's spelled or capitalized, see the Integrated Help section below.

Or, if you're using the web interface, type part or all of the name in the "Lookup:" field and click "lookup". Selecting the "exact" checkbox will only return exact matches, otherwise you will get all lines containing that substring. Try it for something like "cubit" and you'll see that there are often lots of variations.

Important: You'll learn the most if you look at the voluminous and fascinating data file for more examples of things you can do, and measurements that Frink knows about.

Integrated Help

If you don't know the name of a unit or function, but can guess at it, you can either read the data file for more information, or use the integrated help. Keep in mind that Frink is case-sensitive, so you'll need to use the right capitalization of the names.

Unit or function names can be looked up by preceding part or all of the name with a question mark. This will return a list of all units and function names containing that string, in upper- or lower-case. For example, to find the different types of cubits:

?cubit
[homericcubit, assyriancubit, egyptianshortcubit, greekcubit, shortgreekcubit, romancubit, persianroyalcubit, hebrewcubit, northerncubit, blackcubit, olympiccubit, egyptianroyalcubit, sumeriancubit, irishcubit, biblicalcubit, hashimicubit]

Or, if you want to know the name of the currency used in Iran,

?iran
[Iran_Rial, Iran_currency, Iran]

Simply enter the name of the unit you're interested in to see its value:

biblicalcubit
0.55372 m (length)

If you want to see the results in specific units of measurement, you can use the arrow operator -> as described in the Conversions section below:

biblicalcubit -> inches
21.8

Or, if you want to see the sizes of all the units as a single unit type, and they're all the same, you can use the arrow operator on the list. The following sample shows all the different types of cubits the world has defined and converts them to inches:

?cubit -> inches
[homericcubit = 15.5625,
 assyriancubit = 21.6,
 egyptianshortcubit = 17.682857142857142857,
 greekcubit = 18.675,
 shortgreekcubit = 14.00625,
 romancubit = 2220/127 (approx. 17.480314960629922),
 persianroyalcubit = 25.2,
 hebrewcubit = 17.58,
 northerncubit = 26.6,
 blackcubit = 21.28,
 olympiccubit = 18.225,
 egyptianroyalcubit = 20.63,
 sumeriancubit = 2475/127 (approx. 19.488188976377952),
 irishcubit = 500000000/27777821 (approx. 17.99997199204358),
 biblicalcubit = 21.8,
 hashimicubit = 25.56]

If you don't want to see exact fractions, you can (as always) multiply the right-hand-side by 1.0 or 1. (without a zero after the decimal point) to get approximate numbers:

?cubit -> 1.0 inches

If you use two question marks, the units that match that pattern will be displayed and their values in the current display units:

??moon
[moonlum = 2500 m^-2 cd (illuminance),
moondist = 0.002569555301823481845 au,
moonmass = 73.483E+21 kg (mass),
moonradius = 0.000011617812472864754024 au,
moongravity = 1.62 m s^-2 (acceleration)]

Note: If you use the form with two question marks, you cannot convert them to a specified unit with the -> operator, as they have already been converted to strings.

Note that functions are displayed at the end of the list, and can be distinguished from units by the square brackets following them:

??call
[callistodist = 1.883000000e+9 m (length),
callistoradius = 2.400000e+6 m (length),
callistomass = 1.08e+23 kg (mass),
callJava[arg1,arg2,arg3]]

In addition, the functions[] function will produce a list of all functions.

Editing Frink

If you're writing Frink programs, you can edit Frink files in your favorite text editor. If that happens to be Emacs or XEmacs, you can download the rudimentary Frink mode for Emacs. It's somewhat rough at this moment, but it has syntax highlighting, automatic indenting, ability to run interactive Frink sessions or programs. Screenshot is below.

Screenshot of Frink emacs mode

Conversions

By default, the output is in terms of the "fundamental units". To convert to whatever units you want, simply use the "arrow" operator -> (that's a minus sign followed by a greater-than sign,) with the target units on the right-hand side:

38 feet -> meters
11.5824

Formatting Shortcut: If the right-hand-side of the conversion is in double quotes, the conversion operator will both evaluate the value in quotes as a unit and append the quoted value to the result. So, the above example could be performed as:

38 feet -> "meters"
7239/625 (exactly 11.5824) meters

In this case, because the ratio between feet and meters is an exactly-defined quantity, so the answer comes out as an exact rational number. This is also displayed as a decimal number for your convenience. If you just want the decimal value, you can multiply by an approximate decimal number (any number containing a decimal point) such as 1.0 or 1. without anything after the decimal point:

38. feet -> "meters"
11.5824 meters

Note: If you are using the web-based interface, simply enter everything left of the arrow in the "From:" box and everything to the right of the arrow in the "To:" box. Or you can enter the whole expression including the arrow in the "From:" box and leave the "To:" box empty. It does the exact same thing.

If the units on either side of a conversion are not of the same type, Frink may try to help you by suggesting conversion factors:

55 mph -> yards
 Conformance error -
   Left side is: 15367/625 (exactly 24.5872) m s^-1 (velocity)
  Right side is: 1143/1250 (exactly 0.9144) m (length)
    Suggestion: multiply left side by time
               or divide left side by frequency

 For help, type:
    units[time]
      or
    units[frequency]
    to list known units with these dimensions.

If you get an error like this, you can list all the units that have the specified dimensions by typing units[time] or units[frequency].

Yes, sometimes it gives digits which aren't significant in results. As I improve the symbolic reduction of expressions, this will get better, although I still need to work out ways of specifying and tracking precision (and uncertainty?) throughout all calculations.

Multiple Conversions

If the right-hand-side of the conversion is a comma-separated list in square brackets, the value will be broken down into the constituent units. For example, to find out how long it takes the earth to rotate on its axis:

siderealday -> [hours, minutes, seconds]
23, 56, 4.0899984

or, to maintain symmetry with the quoted-right-hand-side behavior noted above, arguments on the right-hand-side can be quoted:

siderealday -> ["hours", "minutes", "seconds"]
23 hours, 56 minutes, 4.0899984 seconds

This behavior can also be used to break fractions into constituent parts:

13/4 -> [1,1]
3, 1/4 (exactly 0.25)

If the first term is the integer 0 (zero), any leading terms with zero magnitude will be suppressed:

siderealday -> [0, "weeks", "days", "hours", "minutes", "seconds"]
23 hours, 56 minutes, 4.0899984 seconds

If the last term is the integer 0 (zero), any remaining fractional part will be suppressed:

siderealday -> ["hours", "minutes", "seconds", 0]
23 hours, 56 minutes, 4 seconds

Math Operators

Math is very straightforward: the current parser accepts the normal mathematical operators, with normal operator precedence. (Exponentiation first (see notes below,) then multiplication and division, then addition and subtraction. And more tightly parenthesized expressions are performed before anything else.) All expressions can be arbitrarily complex. Parentheses can be used to group expressions.

Important: Whitespace between any two units implies multiplication! This has the same precedence as multiplication or division. If there's one thing you need to keep in mind, it's this. You must parenthesize units on the right-hand-side of a division operation, if you expect them to be multiplied before the division takes place.

The following are all valid expressions. (Note that if you are using the web-based interface you can enter the right-hand side of the arrow operator in the "To:" box.)

ExampleDescription
1+1 addition
1-1 subtraction
3*4 multiplication
3 4 Important: whitespace implies multiplication
3 days multiplication also.
foot meter multiplication also (result is an area)
1/3 division (note this maintains an exact rational number)
week/day division (result is 7)
3^4 exponentiation. Note that chained exponentiations such as 2^3^4 are, following normal mathematical rules of precedence, performed right-to-left, that is, 2^(3^4).
3^200 exponentiation... note that arbitrary precision is supported.
365 % 7 modulus (remainder) defined by x - y * floor[x/y]
365 mod 7 Also modulus
year mod day Also modulus; both sides need to be units having same dimensions (e.g. both length.)
365 div 7 Truncating divide, defined by floor[x/y]
year div day Also truncating divide; both sides need to be units of same type.
6! Factorial: 6 * 5 * 4 * 3 * 2 * 1. Note that factorials have a higher precedence than exponentiation.
foot -> m Conversion operator (for unit conversions, works just like a very low-precedence divide operator but returns a string.)
4^(1/2) square root (note parentheses needed because precedence of exponentiation is higher than that of division. The function sqrt[x] does the same thing.)
1/2 + 1/3 Result is 5/6. Note that Frink maintains rational numbers if it can.
1/2 + 1/3. Result is .083333333 The decimal point indicates an uncertain number.
gallon^(1/3) -> inches Cube root: how big of a cube (or Frinkahedron) is a gallon?
(20 thousand gallons)^(1/3) -> feet How big of a cube is 20000 gallons? Note necessary parentheses because exponentiation is usually done before multiplication or division.
20 thousand gallons water -> pounds How much does that much water weigh? ("water" is a measure of density for now.)
250 grams / sugar -> cupsSample recipe conversion ("sugar" is a density for now.)
1/4 mile / (4.23 seconds) -> miles/hourDragster average speed. Note the parentheses required because space is multiplication which has same precedence as division.
329 mph / (4.23 seconds) -> gravityDragster average acceleration in g's.
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.
3 square feetEquals 3 (feet^2) or, more simply, 3 feet^2. Square squares the unit on its immediate right-hand side.
3 sq feetSame as square
3 cubic feetEquals 3 (feet^3) or, more simply, 3 feet^3. Cubic cubes the unit on its immediate right-hand side.
3 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.

Note: If a number comes out as a fraction, like 20/193209, you can get a decimal result by repeating the calculation with a non-integer number (that is, one with a decimal point in it like 20./193209) or by multiplying by 1.0, or simply 1. (without anything after the decimal point.)

Both sides of a conversion can be arbitrarily complex.

Variables

By default, all variables in Frink can contain any type. Variable names begin with any (Unicode) letter followed by 0 or more letters, digits, or the underscore (_) character.

You do not need to declare variables before using them. The variable will be defined in the smallest containing scope.

To assign a value to a variable, use the = operator:

a = 10 feet (assigns a single value)
b = [30 yards, 3 inches] (assigns an array)

Declaring Variables

Variables may be declared before they are used using the var keyword. For example, to declare a variable called t:

var t

This defines the variable t in the smallest containing scope and sets its initial value to the special value undef. You may also specify an initial value:

var t = 10 seconds

Constraints on Variables

When a variable is declared, you can constrain the type of values that it can contain. The constraints are checked at runtime. If you try to set a value that does not meet the constraints, a runtime error occurs. For example, to make sure that the variable t only contains values with dimensions of time, you can declare it using the is keyword which defines constraints.

var t is time = 10 seconds

In this case, the initial value is necessary to ensure that t contains a value with dimensions of time at all times. (The special value undef is applied if no initial value is supplied.) If a valid initial value is not supplied, this will produce an error at runtime.

Multiple constraints can be specified by placing them in square brackets. All constraints must be met. (If you want to do an "OR" of constraints, see the Constraint Functions section below.)

var t is [time, positive] = 10 seconds

Constraining by Dimensions

Built-in constraint types include all of the dimension types defined in your program. For example, you can list all of the defined dimension types (e.g. length, mass, power, energy) with the dimensions[] function. All of these defined types can be used as constraints.

Constraining to Built-In Types

The following built-in constraints can be used to verify that the value is of one of the built-in types. For example,

var name is string = "Frink"

NameDescription
arrayValue must be an array.
booleanValue must be a boolean value true or false (and not just a type that can be coerced to boolean; see the Truth section.)
dateValue must be a date/time.
dictValue must be a dictionary.
setValue must be a set.
regexpValue must be a regular expression.
substValue must be a substitution (search-and-replace) expression.
stringValue must be a string.
unitValue must be a unit of measure of any type (including dimensionless numbers). You will probably use this rarely; it's more likely that you'll want to constrain based on dimension type.

Constraining by Object Type

A class name can also be a constraint name. If, for example, you've defined a class called Sphere, the following will work.

a is Sphere = new Sphere[]

This constraint check also works with interface names. If the name of the constraint is the name of an interface, this check will ensure that any object assigned to the variable implements that interface. See the interfacetest.frink file for an example.

Constraint Functions

You may define your own functions that will be used as constraints. The function must take one argument and return a true value if the constraint is met. Returning false or another value will cause the constraint to fail. The following defines a function called positive that returns true if a value is a positive dimensionless value.

//define constraining function
positive[x] := x > 0

//declare variable with constraint and set initial value
var x is positive = 1

Testing Variables

You can test to see if a variable is defined using the following functions:

FunctionDefinition
isDefined[x]Returns true if the symbol is defined either as a local variable in the current scope (i.e. with the = operator), or as a unit (i.e. with the := operator.)
isVariableDefined[x]Returns true if the symbol is defined as a local variable in the current scope (i.e. with the = operator).

Both functions can be called either with a raw variable name or with a string. For example:

isVariableDefined[a]
isVariableDefined["a"]

Unicode in Frink

For internationalization, Frink allows Unicode characters anywhere. For maximum portability, and maximum editability with non-Unicode-aware editors, you can use Unicode escapes to embed these characters in program files. Variable names can contain Unicode characters, indicated by \u followed by exactly 4 hexadecimal digits [0-9a-fA-F] indicating the Unicode code-point. This allows Unicode characters to be placed into any ASCII text file, and edited by programs that don't understand Unicode. It also allows any Unicode character to be used in an identifier.

If you do have a nifty editor that handles Unicode, or other character encodings, you can write your Frink program in full Unicode, and load it using the --encoding str command-line switch. Keep in mind that in this case, identifiers can only consist of Unicode letters, digits, and the underscore. You still have to use the \u00a5 Unicode escape trick if your identifier contains other classes of characters.

For example, Unicode defines the character \u201e for Planck's constant. In the data file, we define Planck's constant as the normal character h (which is easier to type) and also as the Unicode character. These definitions look like:

h := 6.62606876e-34 J s // Planck's constant
\u210e := h // Unicode character for Planck's constant

Note: The := notation simply defines a global unit, that is available from all functions.

Setting Display Units

By default, units are displayed with their dimensions given as multiples of the International System of Units (SI) base units. These are often not very intuitive. For example, volts are displayed as:

1 volt
1 m^2 s^-3 kg A^-1 (electric_potential)

Of course, you could convert to volts explicitly using the -> operator, but if you have to do that repeatedly, it's a hassle. Instead, you can define the default output format for a unit type by using the :-> operator:

electric_potential :-> "volts"
10 volt
10 volts

The left-hand side is the dimension list identifier like electric_potential or time or power (you can see what this is named for any given unit by entering an expression of that type--see the first "volt" sample above.)

The right-hand side is any expression that can go on the right-hand-side of a conversion operator -> , including multiple conversions:

time :-> [0, "days", "hours", "minutes", "seconds"]
siderealyear
365 days, 6 hours, 9 minutes, 9.5400288 seconds
siderealday
23 hours, 56 minutes, 4.0899984 seconds

The right-hand-side can even be a function that takes a single argument:

HMS[x] := x -> [0, "hours", "minutes", "seconds"]
time :-> HMS

If you want, you can define a function that displays distances in millimeters if it's small, kilometers if it's bigger, and light-years if it's huge.

Setting Precision

Floating-point calculations are performed to a limited number of digits. You can change the number of digits of working precision by the setPrecision[digits] function:

setPrecision[50]
1 / 3.0
0.33333333333333333333333333333333333333333333333333

Note that this will only affect calculations performed after this flag is set, of course. Currently, not all operations (notably trigonometric functions) can be performed to arbitrary precision.

You can also see the current working precision by calling the getPrecision[] function:

getPrecision[]
50

Setting Number Formats

By default, floating-point numbers are displayed in scientific notation with one digit before the decimal point. This can be changed to "engineering" format where 1 to 3 digits are placed before the decimal point and the exponent is a multiple of 3. This allows you to more easily see it as "milli-", "micro-", "million", etc. The call to enable this is:

setEngineering[true]

Example without engineering mode:

d = 140.5 million meters
1.405000000e+8 m (length)

Now notice the change if you set "engineering mode" to true. The result comes out so you can more easily read it as "140.5 million meters":

setEngineering[true]
d
140.5000000e+6 m (length)

In addition, rational numbers are, by default, displayed with a floating-point approximation to their values:

1/10
1/10 (exactly 0.1)

1/3
1/3 (approx. 0.3333333333333333)

This behavior can be suppressed by calling showApproximations[false].

showApproximations[false]
1/10
1/10

You can tell Frink to always display rational numbers as floating-point approximations by calling rationalAsFloat[true]. The numbers will still continue to be represented internally as rational numbers.

rationalAsFloat[true]
1/3
0.33333333333333

Frink tries to produce a human-readable description of units of measure, such as "power" or "energy" or "temperature":

1 K
1 K (temperature)

This suggestion can be suppressed by calling showDimensionName[false]

showDimensionName[false]
1 K
1 K

Functions

If you repeat a calculation, you may want to define it as a function. Functions in Frink are denoted by the function name followed by arguments in square brackets, separated by commas. A function can be defined like the following:

circlearea[radius] := pi radius^2

Then, to call the function, say, to find the area of my telescope mirror, which has a radius of 2 inches:

circlearea[2 inches]
0.008107319665559965 m^2 (area)

But that comes out in standard units... let's try again, converting to square inches.

circlearea[2 inches] -> in^2
12.566370

Multi-line Functions

Multi-line functions can be built; just put the body in curly braces. It may be more legible to use the return statement in your function. For example, the factorial function could be written as:

factorial[x] :=
{
   if x>1
      return x * factorial[x-1]
   else
      return 1
}

If a function does not explicitly return a value, the value returned is the value of the last expression evaluated. A return statement with no value on the right-hand-side returns a special void type.

Default Values

Function declarations can have default values. Default values are specified by putting "= value" after a parameter name in the function declaration. For example, if your Willard pocket organizer goes out, you can use Frink to calculate the tip on your dinner check, and, to make it easy, you can default the tip rate to 15 percent of the bill. The function declaration with default parameters is:

tip[amount, rate=15 percent] := amount * rate

Now, when you get to the restaurant, you can easily calculate the tip using the default rate:

tip[80.75 dollars]
12.1125 dollar (currency)

Or, if service is outstanding and you want to tip at 20%, you can specify the second argument instead of leaving it at the default:

tip[80.75 dollars, 20 percent]
16.15 dollar (currency)

Multiple Return Values

The previous tip example probably has you thinking, "Well, it would be nice if it calculated the total too!" I'm bad at math, also... that's why I'm developing Frink.

In Frink, values surrounded by square brackets and separated by commas form a list of values. These lists can be returned from a function, assigned to a variable, or whatever. A better version of the above function would be defined to return a list containing the tip and the total as a list:

tipandtotal[amount, rate=15 percent] := [amount * rate, amount * (1+rate)]

Note the square brackets on the right-hand-side of the definition. Then, to calculate the tip, it's as easy as before:

tipandtotal[80.75 dollars]
[12.1125 dollar (currency), 92.8625 dollar (currency)]

I'll let you do the rounding in your head, or you can use the rounding functions below.

Recursive Functions

Yes indeedy-o, functions can be recursive. The classic example is the factorial:

factorial[x] := x>1 ? x factorial[x-1] : 1

This uses the conditional expression condition ? trueClause : falseClause. The condition is first evaluated (it should evaluate to a boolean value,) and if it's true, the true clause is evaluated and returned, the false clause otherwise. Let's try a big number, just big enough that it would overflow my old solar calculator:

factorial[70]
119785716699698917960727837216890987364589381425464258 57555362864628009582789845319680000000000000000

You can still blow out the stack if you go too deep, or forget to put in a condition such that the function terminates. Don't come crying to me.

Constraining Function Arguments

Like other variables, formal arguments to functions can have constraints. The syntax for constraining is just the same as setting Constraints on variables. For example, if you want to make sure that a function that calculates the volume of a sphere is passed a radius, the declaration looks like:

sphereVolume[radius is length] := 4/3 pi radius^3

The constraint(s) are checked at runtime, and if all constraints are not met, the function call produces an error.

At some point in the future, I'd like to have this choose an appropriate function based on the constraints, if more than one is possible. My underlying function dispatching is designed to allow this, but functions with constraints may be slower to resolve.

If/Then/Else

You can control program flow with the if/then/else construct. If the condition is true, it will execute the first clause, otherwise, if there is an (optional) else clause, it will execute the else clause.

if a<10
{
   println["Less than ten"]
} else
{
   println["Greater than ten."]
}

Note: Note that putting the brackets and statements on separate lines is currently important. Also, please note that the else keyword goes on the same line as the closing bracket of the then clause.

If either the then or else clause is a single line, the curly braces for that clause can be eliminated. The following is the same as the code above:

if a<10
   println["Less than ten"]
else
   println["Greater than ten."]

The condition must be able to be turned into a boolean value. When testing for equality, be sure to use the double equals sign, (a single equals indicates assignment) e.g.:

if a==b
   println["Equal."]

If, for some reason, you need to jam everything into one line, you need to add the then keyword:

if a==b then println["Equal."] else println["Not equal."]

Truth

The condition in an if/then/else statement or a loop needs to be a boolean (true/false) value. This can either be represented by the special values true and false, or the following types can be used in places where a boolean value is required:

TrueFalse
truefalse
Any non-empty stringThe empty string ""
Any list (even a zero-element list)
The special undefined value undef

Any other value will cause a runtime error. See the Boolean Operators section below for operators that return boolean values.

Loops

While Loop

The while loop is a loop with a condition and a body. The body is executed repeatedly while the condition is true.

i=0
while i<1000000
{
   i = i+1
}

If the body is a single line, the braces can be omitted:

i=0
while i<1000000
   i = i+1

You can use the next statement to prematurely jump to the next iteration of a while loop.

You can use the break statement to exit the smallest containing loop. You can also use labeled break statements to break out to a higher loop:

i=0

OUTERLOOP:
while i<1000000
{
   i = i+1
   j = i
   while j<1000000
   {
      j = j+1
      if i+j > 1000000
         break OUTERLOOP // Breaks out of both loops
   }
}

The label must precede the loop on a separate line and be followed by a colon.

Do...While Loop

The do...while loop is much like the while loop, the only difference being that with the do loop, the body of the loop is always executed at least once, and then the condition is checked. The body of the loop then repeats as long as the condition is true.

i=0
do
{
   i = i+1
} while i<1000

If the body is a single line, the braces can be omitted, but each part of the loop has to be on a different line:

i=0
do
   i = i+1
while i<1000

You can use the next statement to prematurely jump to the next iteration of a do loop. Using the next statement currently does not check the condition.

You can use the break statement to exit the smallest containing loop. You can also use labeled break statements to break out to a higher loop. See the while loop section of the documentation for an example.

For Loop

The above while loop can also be written as:

for i = 1 to 1000000
{
   body
}

If the body is a single line, the curly braces can be omitted. The above sample can be written as:

for i = 1 to 1000000
   body

The range and step size can be specified using the step keyword:

for i = 1 to 1000 step 3

This also works with a date range, but the step must be specified and it must have dimensions of time:

for time = #2001-01-01# to #2002-01-01# step 1 day

The for loop is also used to iterate over the contents of an enumerating expression or array. (You can think of it as a "for each" loop, which is really what it is.)

If the enumerating expression produces a list, and you want to break apart that list into named variables in the for loop, write it as:

for [var1, var2, ...] enumerating_expression
{
   body
}

Again, if the body is a single line, the curly braces may be omitted:

for [var1, var2] enum
   body

See the Input and Output section below for a sample of its use.

You can use the next statement to prematurely jump to the next iteration of a for loop.

You can use the break statement to exit the smallest containing loop. You can also use labeled break statements to break out to a higher loop. See the while loop section of the documentation for an example.

(Note to programmers: The special keyword to creates an enumerating expression that successively takes on all values from the beginning to the end, inclusive, with the default step being 1. (The step size can be changed as shown below.) You can use this to notation anywhere to create an enumerating expression that takes on successive values. You can even make it into an array using the array function:)

a = array[1 to 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Self-Evaluation

Frink can evaluate a string as a Frink expression. If that means something to you, good. It's cool. You can make programs that write and run their own programs. Frink became self-aware on December 7, 2001 at 9:26 PM MST. This is 1561.926 days after Skynet became self-aware. History will be the judge if this December 7th is another date that will live in infamy.

eval["2 + 2"]
4

This behavior can also be used to convert a string into a number. It allows users to enter information as any Frink expression such as "6 billion tons" or 2+2 and have it handled correctly. See the Input section below for examples of its use.

eval[] can also be used to perform another layer of evaluation on a value that is not a string.

If eval[] is passed an array, all elements of the array will be individually evaluated and the result will be returned in an array.

There is also a two-argument version, eval[expression, rethrows] where the rethrows argument is a boolean flag indicating if we want evaluation errors to be thrown or just suppressed and undef returned. If it is true, errors will be rethrown as Java exceptions, otherwise an error returns undef.

Security Restrictions on eval[]

The eval[] function restricts some insecure operations from being performed (e.g. you can't read files from the local filesystem.) If you need all functions to be available from your evaluation, use the intentionally frighteningly-named unsafeEval[str]

Arrays

Arbitrarily-dimensional, non-rectangular, heterogeneous arrays are possible. (If you're playing "buzzword bingo," you just won.) Array indices are zero-based. Arrays are indicated by square brackets.

c = [1, 2, 3]

You can break arrays into multiple lines by inserting newlines after the commas:

b = [1, 2, 3,
     4, 5, 6,
     7, 8, 9]

Think of multidimensional arrays as being a list of lists. For example, to create a 2-dimensional array:

a = [[1, 2, 3],
     [4, 5, 6],
     [7, 8, 9]]

To get elements out, use the lovely @ operator (yes, I'm running out of bracket types... square brackets would be indistinguishable from function calls):

a@0
[1, 2, 3]
a@0@2
3

Arrays can be modified in place and automatically extended:

a@3= "Monkey"
[[1, 2, 3], [4, 5, 6], [7, 8, 9], Monkey]
a@0@2 = 42
[[1, 2, 42], [4, 5, 6], [7, 8, 9], Monkey]

To get the length of an array, use the length function:
length[a]
4

With the advent of array manipulation, I've proven to myself that Frink is capable of simulating a Turing machine, and thus, as of December 12, 2001, at 10:16 PM MST, Frink is theoretically capable of calculating anything calculable by any other programming language.

To create a new empty array, use the notation:

a = new array

Array Methods

It is very important to note that arrays are normally passed by reference. This means that if you assign an array to another variable, and modify the second variable, then you are modifying the original!

a = [3,2,1]
b = a
sort[b]
println[a] // a is also sorted!

[1,2,3]

To avoid this behavior, use the method array.shallowCopy[]. This makes a shallow copy of the object.

a = [3,2,1]
b = a.shallowCopy[]
sort[b]
println[a] // a is now not sorted.

[1,2,3]

Variables that contain an array can be automatically extended by using the methods a.push[x] and a.pop[] to append an item to the end of an array, or remove an item from the end of the array:

array = [1, 2]
array.push[3]

[1, 2, 3]

array = [1, 2, 3]
c = array.pop[]
array
now contains [1, 2]
c now contains 3

Items can also be inserted or popped from the front of an array by using the methods a.pushFirst[x] and a.popFirst[] methods.

Items can be inserted into an array using the method a.insert[index, value]. This inserts the specified value before the item at the specified index. If the index is greater than or equal to the size of the array, the array is extended to fit the new elements, setting any unspecified values to the undefined value undef.

array = [0, 1, 2]
array.insert[0, "first"]
array
now contains [first, 0, 1, 2]

Items can be removed from an array using the method a.remove[index]. This removes the item with the specified index and returns it, so you can do something with the value if desired. If the specified index does not exist, this generates an error.

array = ["a", "b", "c"]
n = array.remove[1]
array
now contains ["a", "c"]

Items with a specified value can be removed from an array using the method a.removeValue[value]. This removes the first item having the specified value from the array. If a matching item is found, this returns true, otherwise returns false.

array = ["one", "two", "three"]
array.removeValue["two"]
array
now contains ["one", "three"]

A random items can be removed from an array using the method a.removeRandom[]. This removes a random item and returns its value.

array = ["a", "b", "c"]
n = array.removeRandom[]

You can obtain all of the permutations of the array by using the permute method. This returns an enumerating expression that lazily generates the permutations. Note that the permutations are currently in reflected Gray code order, but this may change.

array = [1, 2, 3]
array.permute[]
[[1, 2, 3], [1, 3, 2], [3, 1, 2], [3, 2, 1], [2, 3, 1], [2, 1, 3]]

If you need the results in lexicographical order, with duplicates removed, you can obtain all of the permutations of the array by using the lexicographicPermute method. This returns an enumerating expression that lazily generates the permutations. Note that all of the elements of the array must be comparable to each other, a constraint which is not necessary in the permute method.

array = [1, 2, 3]
array.lexicographicPermute[]
[[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]

Input

You can request input from the user with the input[prompt] or input[promptdefaultValue] function. The result always comes back as a string, but you can parse it into a unit, a date, or whatever, using the eval[str] function:

radius = input["Enter the radius of a sphere: "]
volume = 4/3 pi eval[radius]^3

This allows your users to enter things like "3 inches" or "1 mile" or any units that Frink knows about (like "earthradius",) and everything will Just Work. (That "Self-Evaluation" section above seemed irrelevant at the time, but it turns out it's quite useful.)

Output

To print, use the print or println functions, each of which take one argument. The only difference is that println sends a linefeed afterwards.

println["The volume of the sphere is " + (volume -> ft^3) + " cubic feet."]

Boolean Operators

I'm just going to list a forest of cryptic boolean expressions here without explanation. You pick out the ones you like. They all work, and there are usually multiple equivalents for the same thing, taken from different languages. I've tried to keep precedence the same as Java. There is no difference between the different versions of, say, and, AND, and && .

true TRUE false FALSE == != <> < <= > >= && and AND || or OR ! NOT not nand NAND nor NOR xor XOR implies IMPLIES

Dictionaries

A dictionary is an associative data structure that lets you map arbitrary keys to values (currently, keys can be strings or units, that is, numbers. Please note that it makes very little sense to hash on a floating-point value! Don't do it!) The syntax may change in the future, but right now it looks like the syntax for array element manipulation. You simply create an empty dictionary using new dict. Here's a sample as it works now:

a = new dict // Construct a dictionary
a@"one" = 1
a@"two" = 2
a@"three" = 3
a@"one"
1

b = new dict // Construct a dictionary
b@1 = "one"
b@2 = "two"
b@3 = "three"
b@1
one

You can get an enumeration of the keys in a dictionary by using the keys function. This function does not return the keys in any defined order, but you can sort them with the sorting functions below.

for key = keys[a]
   println[ "$key = " + a@key]

two = 2
one = 1
three = 3

You can also enumerate over [key, value] pairs directly in a dictionary.

for [key,value] a
   println["$key = $value"]

two = 2
one = 1
three = 3

A dictionary can be cleared by using the clear method:

a.clear[]
a
[]

Sets

A set is a data structure that contains items with no duplicates. A set must contain values that are strings or units, that is, numbers. You simply create an empty set using new set. Note that sets do not preserve any order of the items contained in them. There are a variety of methods for modifying sets:

a = new set // Construct a set

Items are inserted into a set using the put method:

a.put[1]
a.put[2]
[1,2]

Items are removed from a set using the remove method:

a.remove[2]
a
[1]

A set can be tested to see if it contains a value by using the contains method:

a.contains[1]
true

A set can be cleared by using the clear method:

a.clear[]
a
[]

You can also enumerate over values contained in a set:

for value = a
   println[value]

Common Functions

The most common trigonometric functions are built in. They, as everything else in Frink, are best used when you explicitly specify the units. For the following functions, input should be an angle, and output will come out dimensionless. (If no unit is specified for input, it should act like radians, because radians are dimensionless units and really indistinguishable from pure numbers.)

sin[90 degrees]
cos[2 pi radians]
tan[30 arcsec]
sec[45 degrees]
csc[pi/2 radians]
cot[30 arcsec]
sinh[90 degrees]
cosh[2 pi radians]
tanh[30 arcsec]

For inverse operations, the input must be dimensionless, and the output will come out in angular units. (Radians, by default.) This is easily converted to whatever angular units you want, as above. You don't see that the output is in radians because radians are essentially dimensionless numbers. You just gotta be a bit careful with angles.

arcsin[.1] -> degrees
arccos[1/2] -> radians
arcsin[.1] -> degrees
arccsc[3/2] -> radians
arcsec[3/2] -> radians
arccot[1/2] -> radians
arctan[3 inches/(1 foot)] -> arcminutes
(Returns a value in the range [-π/2, π/2])
arctan[3 inches, 1 foot] -> degrees
(Calculates arctan[x/y] corrected for the proper quadrant. Returns a value in the range [-π, π])

Rounding Functions

FunctionDefinition
floor[x]Returns largest integer <= x
ceil[x]Returns smallest integer >= x
round[x]Rounds to nearest integer
round[x, y]Rounds x to nearest multiple of y
int[x]Truncates decimal places to produce integer
trunc[x]Truncates decimal places to produce integer
numerator[x]Returns the numerator of a rational, dimensionless number. If the number is not rational and dimensionless, simply returns the number.
denominator[x]Returns the denominator of a rational number. If the number is not rational and dimensionless, simply returns 1.

Number Theory

Some functions for number theory and factorization are available.

Note: If you're doing number-theoretical work with very large integers, please see the Performance Tips section of the documentation for ways to greatly improve integer performance.

FunctionDescription
bitLength[x]Returns the number of bits in the minimal two's-complement representation of an integer, excluding a sign bit.
getBit[numbit]Returns an integer, either 0 or 1, indicating the value of the specified bit in an integer. Bit 0 is the least-significant bit. The number is treated as as a two's-complement representation with infinite length. That is, the high bits on a negative number will always be 1, and the high bits on a positive number or zero will always be 0.
gcd[xy]Returns the greatest common divisor of the integers x and y.
isPrime[x]Returns false if the integer x is composite, true if the number is prime (or probably prime.) This test uses trial division and then Rabin-Miller strong pseudoprime tests to determine primality. The bases used in the Rabin-Miller test are known to always return correct answers for numbers smaller than 341,550,071,728,321, but for larger numbers this function may erroneously declare a composite number to be prime. (If it returns false, the number is definitely composite.)

If the number is larger than this, the test is performed against all 78 prime bases less than or equal to 400. This gives a very small probability of about 1 in 1047 that the function may return true for a composite number. (And that's a worst-case; for randomly-chosen large numbers, the probability of error is actually far, far lower for most numbers, especially big ones.) It is more likely that any of the following will happen:

  • Your hardware will fail and return the wrong answer (perhaps due to a cosmic ray hitting it.)
  • You'll flip a coin 156 times in a row and it will land on "heads" every time.
  • You'll play roulette and your number will get picked 29 times in a row.
  • You'll test a trillion numbers every second for the known lifetime of the universe and you'll still only have a miniscule 1 in 1017 chance of getting a single wrong answer.

As of the 2005-11-13 release, isPrime[x] was extended to automatically use a faster Lucas-Lehmer test if x is of the form 2n-1 (i.e. Mersenne numbers). Note that this the Lucas-Lehmer test is sufficient to prove primality for numbers of this form, regardless of their size.

factor[x]Returns the prime factors of an integer x as a two-dimensional list. This uses trial division, then the Pollard p-1 method, and then the Pollard Rho method to find factors. The factor list consists of a list of pairs of prime factors and the exponent of each factor:

factor[1000]
[[2, 3], [5, 3]]

This indicates that the prime factors of 1000 are 23 * 53.

JacobiSymbol[a,n]Returns the Jacobi symbol (often written as (a/n) ) of the integers a and n. n must be a positive, odd integer. This function is used in factorization and primality testing. The Jacobi symbol is a generalization of the Legendre symbol, so it can be used to calculate the Legendre symbol of two numbers. (The Legendre symbol is only defined if n is prime.)

Warning: In releases of Frink prior to the 2005-03-11 release, this function could return the wrong value if a was negative and within the range [-2147483648, -1].

isStrongPseudoprime[num, base] Returns true if num is a strong pseudoprime to base base. If this returns false, the number is definitely composite. If it returns true, the number may be prime. This does not prove that a number is actually prime, as numbers can fail this test for up to 1/4 of bases. This can be used as a component of a prime sieving algorithm.
nextPrime[n]Returns the next prime number greater than n. The value of n may be any real number. This method uses a wheel factoring method to completely avoid testing composite numbers with small factors.
partitionCount[n]Returns the number of ways that the integer n can be partitioned. This uses Euler's pentagonal number algorithm to find the partition count somewhat efficiently, and caches the results so subsequent calls to this function will be efficient.
binomial[m,n]Returns the binomial coefficient. This is of the number of ways m things can be chosen n at a time, with order being unimportant. This is sometimes called "m choose n" or "m C n". This is equivalent to m!/(n!*(m-n)!) although calculating that way often leads to way-too-big numbers. For example, binomial[10000, 9998] is equal to 49995000, but if you calculated it naively, you'd have to calculate 10000! which is a 35660-digit number, and divide it by another huge number, which could be inefficient and slow.
eulerPhi[n]Returns Euler's Totient (also known as the phi function or Euler's Phi) of the given integer. This is the number of positive integers less than n that share no factors with n.
DFT[array, divFactor=-1, direction = 1]

InverseDFT[array, divFactor=-1, direction = 1]

Performs a Discrete Fourier Transform of the given array.

Since different fields of mathematics and engineering use different conventions for the Fourier transform, these functions allow you to (optionally) specify the scaling factor and sign convention.

The (optional) second argument divFactor sets the scaling factor for the results:

FFTInverseDFT
divFactor = -1 (default)1/n1
divFactor = 01/sqrt[n]1/sqrt[n]
divFactor = 111/n

The (optional) third argument direction sets the sign used in the exponent.

FFTInverseDFT
direction = 1 (default)e2 pi i j k / ne-2 pi i j k / n
direction = -1e-2 pi i j k / ne2 pi i j k / n

The InverseDFT function produces the inverse of the DFT given by the DFT function. In fact, it just calls the DFT function with appropriately-reversed parameters.

If you specified the optional second or third arguments for the DFT function, you will need to pass in the same arguments to the InverseDFT function to get the inverse operation. This function takes care of reversing them appropriately.

Other number-theoretical functions, such as those for calculating the values of the Riemann Zeta function may be available in the Sample Programs library.

Other Functions

FunctionDefinition
inv[x]Reciprocal 1/x
recip[x]Reciprocal 1/x
sqrt[x]Square root
log[x]common log, base 10
ln[x]natural log, base e
exp[x]ex
Re[x]The real part of x
Im[x]The imaginary part of x as a real number.
sinh[x]Hyperbolic sine of x, or (ez - e-z)/2
arcsinh[x], asinh[x]Inverse hyperbolic sine of x, or ln[z + sqrt[1 + z^2]]
arccosh[x], acosh[x]Inverse hyperbolic cosine of x, or ln[x + sqrt[x-1] sqrt[x+1]]
arccsch[x], acsch[x]Inverse hyperbolic cosecant of x, or ln[sqrt[1 + 1/z^2] + 1/z]
cosh[x]Hyperbolic cosine of x, or (ez + e-z)/2
tanh[x]Hyperbolic tangent of x, or sinh[x]/cosh[x]
abs[x]absolute value; returns complex number abs[x + iy] = sqrt[x^2 + y^2] for complex arguments. For interval arguments, returns an interval (which may contain zero if the interval contains zero.) Note that the result will also have the same dimensions as the argument!
signum[x]Returns the sign of the argument. For real-valued arguments, this returns (-1 if x<0), (0 if x==0), (1 if x>0). For complex arguments, returns x/abs[x]. For intervals, returns an interval containing the signum of each endpoint (which may be collapsed to a single value if both endpoints have the same sign.) Note that the result will also have the same dimensions as the argument!
format[num, divideBy, decPlaces]Divides num by divideBy and returns a string with no more than decPlaces decimal places. e.g.
format[len, 1 feet, 2].

If divideBy is a string, this evaluates the string, expecting a unit to be returned, and both divides by this unit and prints the string after the result.

random[x]Pick a random integer between 0 (inclusive) and x (exclusive.)
random[min, max]Pick a random integer between min (inclusive) and max (inclusive.)
randomFloat[lower, upper]Pick a uniformly-distributed random floating-point value in the specifed range.
randomGaussian[mean, sd]Pick a normally-distributed (i.e. "bell curve") random floating-point value with the specified mean and standard deviation.
randomBits[numBits]Generate a random positive integer containing numBits evenly-distributed binary bits.
randomSeed[seed]To obtain repeatable results with a program that generates random numbers, sometimes it is desirable to use the same random sequence. This function seeds the random number generator with a known value. The seed must a number from -263 to 263-1 (both inclusive.)
bitLength[int]Returns the number of bits in the minimal two's-complement representation of an integer, excluding a sign bit.
modPow[base,exponent, modulus]Perform the integer modular exponentiation baseexponent mod modulus
modDiv[n,m,modulus]Performs the integer modular division n/m mod modulus and returns the integer result if one exists, otherwise returns undef.
modInverse[n,modulus] Finds the integer modular inverse of n to the base modulus and returns the integer result if it is invertible, otherwise returns undef.
min[arg1arg2]Returns the smaller of the two arguments, or the first argument if they're equal.
min[array]Returns the smallest item in the array.
max[arg1arg2]Returns the larger of the two arguments, or the first argument if they're equal.
max[array]Returns the largest item in the array.
isInteger[expr]Returns true if the argument is a dimensionless integer, false otherwise.
isNegative[expr]Returns true if the argument is a dimensionless negative number, false otherwise.
isPositive[expr]Returns true if the argument is a dimensionless positive number, false otherwise.
isUnit[expr]Returns true if the number is a unit of any type, including dimensionless numbers.
sum[x]Returns the sum of the elements of x, which can currently be an array or an enumerating expression. If x is of any other type, this simply returns x.
sleep[time]Sleeps for the specified amount of time. The argument time must have units of time, such as 1 s or 4.9 minutes or 1/30 s.

Cryptographic Functions

The function messageDigest[str, algorithm] can calculate a variety of cryptographic hashes of various strings. The algorithm parameter is a string containing one of any hashing algorithms your Java platform supports, which probably includes: "MD2", "MD5", "SHA-1", "SHA-256", "SHA-384", "SHA-512". The values are returned as a string containing a hexadecimal representation of the hash.

The following calculates the MD5 hash of the string "abc":

messageDigest["abc", "MD5"]
900150983cd24fb0d6963f7d28e17f72

All Your Base Conversions...

Integer values can be converted to and from other bases (from 2 to 36 inclusive) in several ways. The following functions can be used to convert to or from other arbitrary bases.

FunctionDescription
base[x, b]Returns a string representing the integer x in base b
base2[x] ... base36[x]Returns a string representing the integer x in the specified base.
parseInt[str]Parses a string, containing digits 0-9 only, to an integer in base 10. This should contain no spaces or other text.
parseInt[strbase]Parses a string, treating it as if it's a number in the specified base. The string should contain no spaces or other text.

The following named base conversion functions can also be used. In the cases where several names are commonly used, all options are listed. All functions return strings.

BaseFunction Name(s)
2binary
3ternary, trinary
4quaternary
5quinary
6senary, sexenary
7septenary
8octal, oct, octonary
9nonary
10decimal, denary
11undenary
12duodecimal, duodenary
13tridecimal
14quattuordecimal
15quindecimal
16hexadecimal, sexadecimal, hex
17septendecimal
18octodecimal
19nonadecimal
20vigesimal

The conversions can be performed by calling the named function, or by using the conversion operator ( -> ). The following are all equivalent, and all convert the number specified by the variable number to a string in base 8.

base[number, 8]
base8[number]
number -> base8
octal[number]
number -> octal
oct[number]
number -> oct

Use whichever is most convenient for you. (Note: the function version will be slighly faster.)

As noted above in the Data Libraries section, you may input numbers in a specified base by following the number with two backslashes and the specified base:

Strings

Text surrounded by double quotes is a string.

"My hovercraft is full of eels."
My hovercraft is full of eels.

If you need to put a literal double-quote inside a string, precede it with a backslash:

"If you believe in the hypothetical \"Z-Axis\""
If you believe in the hypothetical "Z-Axis"

Backslashes have a special meaning within double-quoted and triple-quoted strings. They may precede a special character, as follows:

StringDescription
\\Places a single backslash into the string.
\tPlaces a tab character into the string.
\nPlaces a newline character(s) into the string. The exact characters insert follow your platforms's Java-defined settings for the newline character, which may be one character or two.
\rPlaces a return character into the string.
\"Places a double-quote character into the string.
\uXXXXPlaces a Unicode character into the string, where XXXX is a 4-digit hex value for the Unicode codepoint. For more information, see the Unicode in Strings section of the documentation.

A single backslash preceding any other character simply inserts the following character into the string (and removes the backslash.)

Strings can be concatenated using the + operator. If either side of a + operator is a string, the values will be converted to strings before concatenation.

Unicode in Strings

For internationalization, Frink allows Unicode characters anywhere. Strings can contain Unicode characters, indicated by \u followed by exactly 4 hexadecimal digits [0-9a-fA-F] indicating the Unicode code-point. This allows Unicode characters to be placed into any ASCII text file, and edited by programs that don't understand Unicode.

"The symbol for micro is \u00b5"
The symbol for micro is µ

Unicode Character Codes

You can convert a character to its Unicode character code by using the char[x] function.

If passed an integer, it returns the character with that Unicode character code:

char[00b5\\16]// Base 16, micro character as above.
µ

If passed a single-character string, it returns the Unicode character code:

char["A"]
65

If passed a multiple-character string, it returns the Unicode character code for each character in an array:

char["Frink"]
[70, 114, 105, 110, 107]

If you always need an array of character codes, use the chars[x] function which turns a string into an array of character codes, even when passed a string containing only one character.

chars["F"]
[70]

If passed an array of integers, the char[x] function returns a string:

char[ [70, 114, 105, 110, 107] ]
Frink

The charList[str] returns a list of the characters in a string:

charList["Frink"]
[F, r, i, n, k]

Upper/Lower Case

The functions uppercase[str] or uc[str] and lowercase[str] or lc[str] convert a string to upper- or lowercase. These functions use Unicode single- and multiple-character mapping tables and thus try to do the right thing with Unicode, possibly making the string longer in some cases:

uc["Imbiß"] // Last char is \u00df
IMBISS

As the Unicode standard for casing states, "it is important to note that no casing operations on strings are reversible:"

lc[ uc["Imbiß"] ]
imbiss

Substrings

Substrings can be taken with the functions:

FunctionDescription
substr[stringstartPosendBefore]
substring[stringstartPosendBefore]
Takes the substring of string str beginning with startPos and ending before the character position endBefore. The length of the substring will be endBefore-startPos.
substrLen[stringstartPoslen]
substringLen[stringstartPoslen]
Takes the substring of string str beginning with startPos and containing len characters.
left[stringlen]
right[stringlen]
Returns a string containing the leftmost or rightmost characters of the given string with the specified length.
indexOf[stringsubstr] Returns the index (zero-based) of the first occurrence of a substring in a string. This returns -1 if the substring is not found in the string.

Other String Functions

Additional functions for manipulating strings are listed below.

FunctionDescription
trim[str]Returns a string with whitespace trimmed from the left and right ends.
length[str]Returns the length of a string in characters.
parseInt[str]Parses a string, containing digits 0-9 only, to an integer in base 10. This is much less powerful and less forgiving than using eval[str], but faster.
parseInt[strbase]Parses a string, treating it as if it's a number in the specified base.
reverse[str]Reverses the characters in a string.
editDistance[str1str2]Returns the edit distance or Levenshtein distance between two strings. This is the minimum number of operations needed to transform one string into the other, where an operation is an insertion, deletion, or substitution. It can be used to aid in spell checking, fuzzy spelling, plagiarism detection, and determining similarity of two strings. The algorithm used runs in O(n*m) time, where n and m are the lengths of each string. It uses O(m) space when processing. The comparison is case-sensitive. The following are examples of edit distances between strings:
String 1String 2Edit DistanceDescription
Frink Frank 1 Substitution
Frink Frinky 1 Insertion
Frink Fink 1 Deletion
Frink Franks 2 1 Substitution + 1 Insertion

Multi-Line Strings

Text surrounded by three sets of double-quotes is a multi-line string (like in Python.) Newlines are allowed and retained in the string. Hopefully, this is less burdensome and error-prone than Perl's "here-document" syntax. For example:

lyrics = """Oh, Danny Boy,
The pipes, the pipes are calling
From glen to glen and down the mountainside"""

String Interpolation

If a double-quoted string or multi-line string contains a dollar sign ($) followed by a variable name (which must begin with a letter), the value of that variable is replaced in the string at evaluation time. This is (probably) faster than string concatenation which could be used to get the same effect. There are certain optimizations to make sure that this isn't significantly more inefficient if the string doesn't need replacement. (The string is checked for dollar signs at compile time.)

first = "Inigo"
last = "Montoya"
"My name is $first $last."

My name is Inigo Montoya.

If you need to explicitly mark where the variable name begins and ends, you can put it in curly braces as below:

last="Frink"
"You can call me the ${last}meister."

You can call me the Frinkmeister.

Since a variable name must begin with a letter, it's fine to put a quantity like $2.00 into the string, and no substitution will be attempted, and there will be no runtime performance penalty. To put a literal dollar sign into the string immediately preceding an letter character, use two dollar signs:

"I want my $$USD 2.00. Plus tip."
I want my $USD 2.00. Plus tip.

For best performance, don't use double dollar signs like this unless they directly precede an letter character.

You can always use this technique to coerce a numeric value, or a unit, or a date, etc., to a string representation by enclosing it in quotes:

n = 2^13367-1
stringRep = "$n"

After the above code, the variable stringRep contains the result of the calculation as a string which you can use to grab certain characters, truncate, etc.

Text Translation

Obligatory Disclaimer: This feature requires connection to the internet. If you are using Frink on a handheld device, you may incur connection charges. Also, since I cannot guarantee the availability of any internet sites, this feature is intended only as a bonus that may not work reliably if at all. You may also require some proxy configuration if you use an HTTP proxy server to access the web.

Text can be translated into other languages:

"My hovercraft is full of eels." -> German
Mein Luftkissenfahrzeug ist von den Aalen voll.

"I will not buy this record; it is scratched." -> Spanish
No compraré este expediente; se rasguña.

Nice translation. All of these are equivalent to calling the same-named function:

German["My hovercraft is full of eels."]
Mein Luftkissenfahrzeug ist von den Aalen voll.

Or, to translate from another language, use the FromLanguage conversion, or the appropriate keyword:

"Yo quiero un burrito." -> FromSpanish
or
"Yo quiero un burrito." -> Ingles
I love a young donkey.

(Thanks to Brian C. White discovering the above gem of translation, which is literally correct.) So it's not perfect, and it sure helps if your operating system is set up to display Unicode characters correctly. Or, you can do round-trips:

"The spirit is willing but the flesh is weak." -> Spanish -> Ingles
The alcohol is arranged but the meat is weak.

You can also define a function to do the same as the above:

corrupt[x] := x -> Spanish -> Ingles
or
corrupt[x] := Ingles[Spanish[x]]

You can also build up more complex strings:

"The German word for \"dog\" is \"" + German["dog"] + ".\""
The German word for "dog" is "Hund."

And you can use Frink to not just translate the words, but the words and the units:

"My farm is " + (220000 acres -> "hectares") + ", but it's not arable land." -> German
Mein Bauernhof ist 89031,19741723355 Hektars, aber es ist nicht urbares Land.

"Gasoline costs " + (round[1.37 USD/gallon / (EUR/liter), .01]) + " Euro/liter in the United States." -> German
Benzin kostet 0,33 Euro/liter in den Vereinigten Staaten.

Ooh, that's cool. If I had this when I lived in Germany, I might have seemed semi-literate.

Translation Pairs

The following table summarizes the language pairs that can be translated and their keywords. The default translations use your operating system's language setting, which should detect your default language and Do The Right Thing most of the time. When you're translating from another language, you need to indicate what the foreign language is. Keywords like Inglese (the Italian word for English) imply that you're translating from Italian to English.

FromToKeywords
DefaultEnglishEnglish, en
DefaultGermanGerman, Deutsch, de
DefaultSpanishSpanish, Espanol, Español, es
DefaultFrenchFrench, Francais, Français, fr
DefaultItalianItalian, Italiano, it
DefaultPortuguesePortuguese, pt
DefaultKoreanKorean, ko
DefaultSimplified Chinese SimplifiedChinese, Chinese, zh
DefaultTraditional Chinese TraditionalChinese, zt
DefaultRussianRussian, ru
DefaultJapaneseJapanese, jp
DefaultDutchDutch, Nederlands, nl
DefaultSwedishSwedish, Svenska, sv
DefaultArabicArabic, ar
EnglishDefaultFromEnglish, from_en
GermanDefaultFromGerman, from_de
SpanishDefaultFromSpanish, from_es
FrenchDefaultFromFrench, from_fr
ItalianDefaultFromItalian, from_it
PortugueseDefaultFromPortuguese, from_pt
JapaneseDefaultFromJapanese, from_ja
KoreanDefaultFromKorean, from_ko
RussianDefaultFromRussian, from_ru
Simplified ChineseDefaultFromSimplifiedChinese, FromChinese, from_zh
Traditional ChineseDefault FromTraditionalChinese, from_zt
DutchDefaultFromDutch, from_nl
SwedishDefaultFromSwedish, from_sv
ArabicDefaultFromArabic, from_ar
EnglishGermanEnglishToGerman, en_de
EnglishSpanishEnglishToSpanish, en_es
EnglishFrenchEnglishToFrench, en_fr
EnglishItalianEnglishToItalian, en_it
EnglishPortugueseEnglishToPortuguese, en_pt
EnglishKoreanEnglishToKorean, en_ko
EnglishJapaneseEnglishToJapanese, en_ja
EnglishRussianEnglishToRussian, en_ru
EnglishSimplified Chinese EnglishToSimplifiedChinese, EnglishtToChinese, en_zh
EnglishTraditional Chinese EnglishToTraditionalChinese, en_zt
EnglishDutchEnglishToDutch, en_nl
EnglishSwedishEnglishToSwedish, en_sv
EnglishArabicEnglishToArabic, en_ar
GermanEnglishGermanToEnglish, Englisch, de_en
GermanFrenchGermanToFrench, franzoesisch, Franzoesisch, französisch, Französisch, de_fr
SpanishEnglishSpanishToEnglish, Inglés, Ingles, es_en
SpanishFrenchSpanishToFrench, frances, Frances, francés, Francés, es_fr
FrenchEnglishFrenchToEnglish, Anglais, fr_en
FrenchGermanFrenchToGerman, Allemand, allemand, fr_de
FrenchSpanishFrenchToSpanish, Espagnol, espagnol, fr_es
FrenchPortugueseFrenchToPortuguese, Portugais, portugais, fr_pt
FrenchItalianFrenchToItalian, Italien, italien, fr_it
ItalianEnglishItalianToEnglish, Inglese, it_en
ItalianFrenchItalianToFrench, Francese, francese, it_fr
PortugueseEnglishPortugueseToEnglish, Inglês, pt_en
PortugueseFrenchPortugueseToFrench, francês, Francês, pt_fr
JapaneseEnglishJapaneseToEnglish, ja_en
KoreanEnglishKoreanToEnglish, ko_en
RussianEnglishRussianToEnglish, ru_en
Simplified ChineseEnglish SimplifiedChineseToEnglish, ChineseToEnglish, zh_en
Traditional ChineseEnglish TraditionalChineseToEnglish, zt_en
DutchEnglishDutchToEnglish, Engels, nl_en
SwedishEnglishSwedishToEnglish, Engelska, engelska, sv_en
ArabicEnglishArabicToEnglish, ar_en

Translator Program

Here's a small program that can be used to make a mini-translator for a specific language. It allows you to enter phrases to be translated without entering the quotes and other bits. It will continue to translate phrases until you click the "OK" button without entering a phrase:

while phrase=input["Enter phrase in Portuguese: "]
   println[phrase -> FromPortuguese]

Date/Time Handling

Frink has the ability to define specific points in time and add time intervals to them, to convert between timezones, or to subtract dates from each other. Date literals are surrounded by pound signs (#) and can be entered in a wide variety of formats, but I prefer a format like:

# yyyy-MM-dd HH:mm:ss #

Note that I've chosen to have most-significant digits first, as is only logical (the world will realize this someday.) You can have whitespace preceding or following the # signs for readability. All of the predefined formats are defined in /data/dateformats.txt. Check that file first to see the formats that are already defined. If the format you want is not there, see below for ways to define your own formats.

You can also parse a string into a date using the parseDate[string] function. This returns the string parsed into a date/time datatype, or returns undef if the string cannot be parsed as a date using any of the defined date formats.

Specifying Timezones

All date/time formats also allow a timezone specifier at the end. This timezone specifier can be a 3-letter code like "UTC" or "MST", but you can also use the name of a country or U.S. state (if the country or state has a single time zone) or a selected city (of course, not all cities are available.) This means that you don't have to know when daylight savings time starts and ends. The following are all valid inputs:

# 2002-01-03 10:00 AM New York #
# 2002-01-03 10:00 AM Colorado #
# 2002-01-03 10:00 AM Eastern #
# 2002-01-03 10:00 AM America/New_York #
# 2002-01-03 10:00 AM France #
# 2002-01-03 10:00 AM Paris #
# 2002-01-03 10:00 AM Europe/Paris #

This is similar to calling the parseDate function:

parseDate["2002-01-03 10:00 AM Europe/Paris"]

Listing Timezones

The function timezones[] will return an enumeration of all known time zone names.

Please note that your Java implementation may not have all of the timezones named in these examples. Notably, Java 1.1 distributions tended to use only a small number of three-letter timezones, like JST.

The function timezone[] with no arguments will return the name of the default timezone.

Sloppy Time Specifications

If you don't specify an exact date, the date will be treated roughly as "today." This is useful for getting a quick-and-dirty timezone conversion, but if you want to get the day right, you should specify the date as above.

Conversion to local timezone, assuming today:
# 6:00 PM Bosnia #
AD 2002-01-06 10:00:00.000 AM (Sun) Mountain Standard Time

Conversion of local time to another timezone:
# 6:00 PM # -> Japan
AD 2002-01-07 10:00:00.000 AM (Mon) Japan Standard Time

Conversion between arbitrary timezones:
# 6:00 PM Bosnia # -> "New York"
AD 2002-01-06 12:00:00.000 PM (Sun) Eastern Standard Time

Note that running the above simplified conversions at different times of the year will give you different results because of differences in the Daylight Savings Time rules for each country.

Also, please note that your Java implementation may not have all of the timezones named in these examples. Notably, Java 1.1 distributions tended to use only a small number of three-letter timezones, like JST. Use the timezones[] function to list the timezones defined on your system.

Current Time

The function now[] will return the current date/time.

In an interactive session, you can use the string ## as shorthand for "now." This should evaluate to the time at which the value was parsed, which is why it won't usually do what you intend in a program... the value would be the time the program was originally run and parsed.

Time is only as accurate as your computer's clock setting. (I use AtomTime on Windows machines to keep the clock synchronized to the atomic clock.)

Timezone Conversions

By default, times are displayed in the local timezone and using your locale information (but are internally stored correctly in Julian Day relative to Universal Time.) You can also convert to another time format by specifying it after the arrow operator. "JD" for Julian Day, "JDE" for Julian Day (Ephemeris) referenced to Dynamical Time, "MJD" for Modified Julian Day are supported, as well as finding the times in selected timezones, cities and countries:

Example: Sky & Telescope predicted that the peak of this year's Perseid meteor shower might be around August 12, 2001 at 0400 UTC:

Converting to local time (the default behavior:)
# 2001-08-12 04:00 UTC #
AD 2001-08-11 10:00:00.000 PM (Sat) Mountain Daylight Time

Converting to another time zone:
# 2001-08-12 04:00 UTC # -> Japan
AD 2001-08-12 01:00:00.000 PM (Sun) Japan Standard Time

Current time in Germany:
now[] -> Germany
AD 2001-12-28 07:20:01.508 AM (Fri) Central European Time

Current Julian Day:
now[] -> JD
JD 2452824.5679731136

Current Julian Day Ephemeris:
now[] -> JDE
JD 2452824.578919213

Convert Julian Day Ephemeris to a date:
JDE[2451545.0]
AD 2000-01-01 04:58:56.170 AM (Sat) Mountain Standard Time

Actually, since 2003-06-12, Frink uses full precision (usually rational numbers) when storing dates, so the Julian day may come out as a rational number like:

JD 211924042672877/86400000 (approx. 2452824.5679731136)

If you don't desire this much precision, you can use the JD[date] or MJD[date] function and divide by 1.0 days to get the Julian date as a floating-point number, not a string or a rational number:

JD[now[]] / (1.0 days)
2452824.5679731136

Date/Time Arithmetic

You can subtract one date from another, or add/subtract a time interval to a date. So, if I wanted to find out when I was 1 billion seconds old, I add it to my birthdate:

#1969-08-19 16:54 Mountain# + 1 billion seconds
AD 2001-04-27 06:40:40.000 PM (Fri) Mountain Daylight Time

That was pretty recently. You not only forgot my birthday but you forgot my 1-billion-second-anniversary. Bastard. I have a wish list at Amazon.com if you still want to buy me something.

Note: You may notice that adding units of months or years (or commonyear, etc.) may not give you the results you expect--that is because months and years are not fixed-size units, and even having a unit called "month" or "year" is inherently ill-advised. Perhaps later there will be "increment/decrement" of these individual fields on Date objects, but it's better to make sure that you understand these are also inherently troublesome and often meaningless operations. (e.g. what does it mean to increment the month on a date representing Jan. 31?) Use fixed-size units if you can.

You can subtract one date from another, and receive an answer as a Unit with dimensions of time (that you can convert to any scale you want... you're used to this by now.) For example, if you want to know how many days until Christmas:

#2002-12-25# - now[] -> days
105.70975056709

(Try inverting the above calculation to see exactly when I wrote this!)

Notes on Dates

Note: The following notes apply to Frink release 2008-08-02 and later. Previous releases may have handled the transistion between Julian and Gregorian dates and large BC years inconsistently.

Defining New Date/Time Formats

If you want to enter date/time values in a specific format, you can enter new date formats on the fly. Date formats are enclosed between sets of 3 pound signs: ###pattern###. After defining a new pattern, dates between pound signs should be recognized. For readability, you may have leading or trailing space in your formats or dates. For example:

### yyyy-MM-dd ###
#2001-09-10#
Sep 10, 2001 12:00:00 AM

You can also define the default output format with 4 pound signs. Without a definition in the dateformats.txt file, you get the Java default (which should theoretically get it from your system's settings, or mine if you're using the web interface,) but for lots of info, try something like:

#### G yyyy-MM-dd hh:mm:ss.SSS a (E) zzzz ####

Date format expressions can be assigned to variables and/or used on the right-hand side of a conversion operator ( -> ). To output a date in a specified format, use something like:

fmt = ### HH:mm ###
now[] -> fmt

23:55

If you want to format a date with both a specific format and a timezone, make the right-hand-side of the conversion operator into a 2-argument bracketed list with the first argument indicating the date format and the second a string indicating the timezone name:

fmt = ### yyyy-MM-dd hh:mm a (E) zzzz ###
# 2003-06-12 02:25 PM Mountain # -> [fmt, "Japan"]

2003-06-13 05:25 AM (Fri) Japan Standard Time

In these patterns, all ASCII letters are reserved as pattern letters, which are defined as the following:

SymbolMeaning Presentation Example
Gera designator Text AD
yyear Number 1996
Mmonth in year Text & Number July & 07
dday in month Number 10
hhour in am/pm (1-12) Number 12
Hhour in day (0-23)Number 0
mminute in hour Number 30
ssecond in minute Number 55
Smillisecond Number 978
Eday in week Text Tuesday
Dday in year Number 189
Fday of week in month Number 2 (2nd Wed in July)
wweek in year Number 27
Wweek in month Number 2
aam/pm marker Text PM
khour in day (1-24)Number 24
Khour in am/pm (0-11)Number 0
'escape for text Delimiter
''single quote Literal '
For use in output specifications only:
zGeneral timezone String Pacific Standard Time, PST, GMT-08:00
ZRFC 822 timezone String -0800

The count of pattern letters determine the format.

(Text): 4 or more pattern letters--use full form, < 4--use short or abbreviated form if one exists.

(Number): the minimum number of digits. Shorter numbers are zero-padded to this amount. Year is handled specially; that is, if the count of 'y' is 2, the Year will be truncated to 2 digits.

(Text & Number): 3 or over, use text, otherwise use number.

(Timezone): If there are 3 or more "z" characters in a row, use the full name of the timezone, otherwise use the (usually 3-character) abbreviation. Timezone specifiers are not necessary and should not be used when specifying an input format string. Timezones will always be allowed at the end of any date literal.

See the documentation for java.text.SimpleDateFormat for more information.

Other Date Formats

Internally, all dates are represented as the Julian Day (which is essentially just a numeric value indicating the number of days and fractions of days since Julian Day 0, which began at noon UTC on January 1, 4713 B.C. as reckoned on the Julian Calendar.) Yes, noon is the beginning of the day in the Julian Day system. So noon UTC on September 10, 2001 is JD 2452163.0.

Julian Day (JD) and Modified Julian Day (MJD) can also be parsed. MJD is defined as Julian Day - 2400000.5. Note that midnight is the start of a Modified Julian Day. Many astronomical calculations use Julian Day (Ephemeris) which is Julian Day with reference to Dynamical Time, not Universal Coordinated Time. This can be parsed using the prefix JDE:

# JD 2452163.0 #
# JDE 2452163.0 #
# MJD 52162.5 #

You can also use the JDE function to convert a number to the corresponding Julian Day Ephemeris value:

JDE[2451545.0]
AD 2000-01-01 04:58:56.170 AM (Sat) Mountain Standard Time

Note: Do not confuse the Julian Day, (which is a single continuous numbering system often used by astronomers, who like the day number to change at noon when they're not working,) with a date in the Julian Calendar, which is almost identical to the Gregorian Calendar we use today, except without the centuries-divisible-by-400 leap-year rules. (The differences are deeper, but that's the big one.)

Dynamical Time

The offset ΔT between UTC and Dynamical Time (that is, the time that must be added to UTC to get Dynamical Time) can be obtained by the function:

deltaT[date]

You usually won't use this directly, but instead parse dates in Dynamical Time by appending the TD or Dynamical Time timezone specifiers when parsing a date. The JDE (Julian Date Ephemeris) format is also referenced to Dynamical Time. For example, the time represented by the Dynamical Time at the year 2000 epoch is:

epoch = # 2000-01-01 00:00 TD #

Internally, all times are represented as the Julian Day referenced to UTC, but are automatically converted to Dynamical Time if you specify Dynamical Time as the timezone on the right-hand-side of the conversion operator:

# 2003-10-10 11:26 PM Mountain # -> TD
AD 2003-10-11 05:27:04.184 AM (Sat) GMT+00:01

International Atomic Time (TAI)

International Atomic Time (TAI) is a system of time based on the "proper time" on earth's geoid. While dates and times in Frink are internally represented as a Julian day referenced to UTC, you can convert between UTC and TAI for any given date using the function TAIMinusUTC[date] which returns the value TAI-UTC. This is the cumulative number of leap seconds that have been introduced into the calendar.

TAIMinusUTC[# 2008-12-01 00:00 UTC#]
33 s (time)

This facility allows you to adjust for leap seconds, as TAI does not use leap seconds, but UTC does. By default, Frink's date/time math does not correct for leap seconds, but using the above function can help correct for leap seconds. For example, to find the exact time between two dates nominally a year apart:

d1 = # 2008-12-01 00:00 UTC #
d2 = # 2009-12-01 00:00 UTC #
diff = d2 - d1 + (TAIMinusUTC[d2] - TAIMinusUTC[d1])
diff -> ["days", "s"]
365 days, 1 s

Note that the duration is slightly longer due to the leap second being introduced at the end of 2008.

Modern leap seconds were first introduced on 1972 January 1, where the value of TAI-UTC became exactly 10 seconds, and leap seconds have been introduced at irregular intervals since then. Leap seconds may be added just before January 1 or July 1 of each year.

It should be noted that from 1961 January 1 to 1972 January 1, instead of introducing discrete leap seconds, you had to do linear interpolation to convert between TAI and UTC. Be warned that Frink follows this interpolation process between these dates, and the value of UTC-TAI will not be an integer during this period! Before 1961 Jan 1, this function returns 0 seconds. For dates after the last known leap second is introduced, this function will return the value of TAI-UTC for the last leap second (e.g. 34 seconds after 2009 Jan 1.)

For more information on the interpolation, see the US Naval Observatory's tabulation of leap seconds. This is the file that Frink uses to perform these conversions.

Other Time Systems

Additional conversions between time systems can be performed using the following relations:

Regular Expressions

Regular expressions allow you to match complex patterns in strings. Frink matches most all of the regular expressions matched by Perl. I'm not listing the full syntax of all possible regular expressions which are a language to themselves (that's the subject of an entire book but you can get an idea from the Perl documentation here. The matching syntax is similar to Perl or Ruby, with the exception that the pattern is denoted by %r/pattern/options as below:

line = "New Zealand"
if ( line =~ %r/Alan/i ) // Case-insensitive match
   println["Matched"]

In the form listed above, no variable interpolation is done in the string. If you need to build up a variable regular expression from a string, use the regex[string] or regex[string, options] functions. The sample below is identical to the sample above:

line = "New Zealand"
re = regex["Alan", "i"]
if (line =~ re)
   println["Matched"]

Any part of the pattern surrounded by parentheses are saved off and returned as an array (even if only one item is returned.) There aren't shortcut $1... variables like in Perl, and probably never will be, as it's too easy to break code without knowing it.

line = "My name is Inigo Montoya."

results = line =~ %r/my name is (\w+) (\w+)/i
if (results)
{
   println["First name is: " + results@0]
   println["Last name is: " + results@1]
}

Alternately, you can assign to the elements of a list. The variable named by the parameter at the corresponding point in the left-hand list is set as a variable. Thus, the above example could be better rewritten as:

line = "My name is Inigo Montoya."

if [first, last] = line =~ %r/my name is (\w+) (\w+)/i
{
   println["First name is: $first"]
   println["Last name is: $last"]
}

Iterating Matches

Repeating regular expression matches (those with the /g modifier) can be used in an enumerating context (e.g. in a for loop,) in which case each pass through the loop will return a match. The following returns:

for [email] read["http://futureboy.us/"] =~ %r/(\w+@(\w|\.)+\.\w+)/g
   println[email]
eliasen@mindspring.com
eliasen@mindspring.com

(The above URL contains the e-mail address twice.) Note that pattern matches always return a list of values, (even if only one item is returned,) so to get only the first match, the variable email must be placed in square brackets.

If used in a non-enumerating context (such as simple assignment,) the match will return a list-of-lists:

list = read["http://futureboy.us/"] =~ %r/(\w+@(\w|\.)+\.\w+)/g
println[list]
[[eliasen@mindspring.com], [eliasen@mindspring.com]]

This list can be flattened with the flatten[list] function:

println[flatten[list]]
[eliasen@mindspring.com, eliasen@mindspring.com]

Search and Replace

A Perl-like search-and-replace operator also exists. The syntax is:

line =~ %s/pattern/replacement/opts

If the expression on the left-hand-side of =~ is a simple variable name, it will modify it in-place. The following fixes a spelling mistake:

line =~ %s/Frank/Frink/g

In the form listed above, no variable interpolation is done in the from string. If you need to build up a variable replacement expression from a string, use the subst[fromStr, toStr] or subst[fromStr, toStr, options] functions. Note that these do not actually perform the substitution, but create an object that can be used later to perform the substitution. The sample below is identical to the sample above:

fromStr = "Frank"
rep = subst[fromStr, "Frink", "g"]
line =~ rep

You can even use the Perl 5 behavior of replacing parenthesized parts of an expression. The first parenthesized pattern on the left-hand side can be denoted by $1 in the replacement, the second by $2 and so on. For example, to change a file with names like "Frink, John" to "John Frink":

line =~ %s/(\w+), (\w+)/$2 $1/

You can't use $1 and $2 outside of the pattern match, like you can in Perl, though. Another difference from Perl is that the return value of a search-and-replace is the replaced string, and not the number of times replaced. This may change.

Substitution with Expressions

If the /e modifier exists on a search-and-replace operation, the right-hand-side of the substitution is treated as an expression. The values on the left-hand side in parentheses are put into the variables $1 , $2 , etc.

The following sample increments every integer it finds in the line.

"There are 3 lights. My ship is the NCC-1701-D." =~ %s/(\d+)/eval[$1]+1/eg
There are 4 lights. My ship is the NCC-1702-D.

Joining and Splitting Arrays

A string can be split into an array using the split[regex, str] function which splits the string into parts. The first argument to the split[patstr] function can be either a regular expression or a string containing the delimiter. Conversely, the elements of an array can be joined into a string with a fixed delimiter, using join[separator, array]

Splitting a string into an array, splitting on whitespace (the pattern \s+ matches 1 or more whitespace characters:)

array = split[ %r/\s+/, "1 2 3 4 5"]
[1, 2, 3, 4, 5]

Or, to split a tab-delimited line into elements:

array = split["\t", line]
which is the same as
array = split[%r/\t/, line]

And the reverse, joining multiple array elements into one string:

join[":", array]
1:2:3:4:5

Input and Output

Reading Lines

Frink has a useful file/URL input function called lines[URL] which reads lines one at a time from the specified URL. The URL can be an HTTP, file, or FTP URL. It is best used with the for loop. For example, to fetch and display the contents of a web page:

for line = lines["http://futureboy.us/"]
   println[line]

By default, the lines[URL] function returns an enumerating expression which returns each line as requested, and forgets about previous lines. If you want to store each line in an array for later use, use the array[] function:

a = array[ lines["file:data/units.txt"] ]
println["The data file contains " + length[a] + " lines."]

Reading Entire Files

If you're carefree and have lots of memory, you can load the entire contents of a URL (again, file, HTTP, or FTP URL) into a single big honkin' string using the read[URL] function:

bigstring = read["http://futureboy.us/"]

Needless to say, this may use up large amounts of memory, so only use it if you need to. Or want to. Do what you like. Run with scissors.

Specifying Alternate Encodings

The lines[URL] and read[URL] functions will attempt to set the character encoding correctly based on the Content-Type HTTP header. If you are not requesting an HTTP URL, or the encoding is not properly specified, or not set in the HTTP headers, these functions will use your system's default character encoding. If the default charset is not appropriate for a file or URL, you can explicitly specify the character encoding of the file using the two-argument versions of the above functions:

lines[URLencoding]
read[URLencoding]

The encoding is a string representing any character encoding that your version of Java supports, e.g. "UTF-8", "US-ASCII", "ISO-8859-1", "UTF-16", "UTF-16BE", "UTF-16LE". Your release of Java may support more charsets, but all implementations of Java are required to support the above. Check the release notes for your Java implementation to see if other charsets are supported.

E-mail Harvesting

The following sample uses regular expression matching to harvest things that look like e-mail addresses from any URL. The combination of the for loop and the /g modifier allows multiple matches to be found in a single line.

url = input["Enter a URL: "]

for line = lines[url]
   for [result] line =~ %r/(\w+@(\w|\.)+\.\w+)/g
      println[result]

Pretty easy, eh? Now you see why you get so much spam e-mail. It's easy to grab e-mail addresses from files or Web pages. Using this capability, Frink can be made to grab any kind of data from other web pages easily.

Note that pattern matches always return a list of values, (even if only one item is returned,) so to get only the first match, the variable result must be placed in square brackets.

Stripping HTML

The following (ridiculously simple) function fetches the contents of any URL and (somewhat naïvely) strips out the HTML markup.

stripHTML[url] := read[url] =~ %s/<[^>]*>//gs

URL Manipulation

Frink provides a few functions which are useful for manipulating URLs and producing web-spiders:

url[base, relative] returns a new string URL made up of the given base and relative parts of a URL. This is useful in resolving relative URLs in an HTML document:

url["http://futureboy.us/frinkdocs/index.html", "whatsnew.html"]
http://futureboy.us/frinkdocs/whatsnew.html

urlHost[url] returns a string indicating the hostname of a specifed URL string (e.g. futureboy.us, an empty string if no host is specified.

URLEncode[string, encoding] encodes a string for use as part of a URL. The encoding should probably be the string "UTF8" for most applications.

urlProtocol[url] returns a string indicating the protocol (e.g., http of a given URL string.)

The following program resolves all of the relative URLs in an HTML document and prints their values.

url = input["Enter a URL: "]
println[join["\n", findURLs[url]]]

findURLs[u] :=
{
   results = []
   for [rel] read[u] =~ %r/<\s*A\s+[^>]*HREF\s*=\s*"([^ "]+)"/gsi
      results.push[url[u, rel]]

   return results
}

Procedure Blocks

In Frink, you can define blocks of executable code which can be assigned to variables, passed to and from functions, and executed as functions. These work just like functions with no name. In fact, that's exactly what they are. The syntax is:

{ |arglist| body }

The arguments in arglist are a (possibly empty) comma-separated list of variable names which are treated just like the formal parameters to a function. The body is one or more statements or expressions to be executed.

How is this useful? Well, for example the select[list, proc] function allows you to select the items from a list for which proc returns true. For example, to return the even items from a list:

array = [0,1,2,3,4,5]
select[ array, { |x| x mod 2 == 0 } ]
[0,2,4]

This successively assigns each element of array to a new local variable x, and returns the list of values for which x mod 2 equals zero.

Note: The second argument to select[list, regex] can also be a regular expression. This expects the list to contain all strings and returns all of the strings that match the regular expression.

A procedure block can be called as a function. The current syntax looks just like a function call:

isEven = { |x| x mod 2 == 0 }
isEven[4]
true

Sorting

sort[list]will sort lists in which the elements have the same type. When sorting units, the units should be conformal (that is, all should have the same dimensions.) It is important to note that the list will be sorted in-place, that is, the original list will be modified! To get around this, the array should first be copied with the array.shallowCopy[] method.

a = [5,2,3,1,4]
sort[a]
[1,2,3,4,5]

The two-argument version sort[list, proc] allows you to specify a user-defined comparison routine. The second argument is a procedure block which contains a user-defined comparison routine. The comparison routine must take 2 arguments (say |a,b|) and return -1 if a is less than b, 0 if a==b, and 1 if a is greater than b. The following samples are equivalent:

a = [5,2,3,1,4]
cmp = { |a,b| a <=> b }
sort[a, cmp]
[1,2,3,4,5]

The default sort is much faster than if you define a user-defined comparison function (about 30 times faster in my tests!) Now I see why Perl has so many anomalous and special-cased optimizations around user-defined sorting.

If you want the elements in reverse order, you can reverse the sorted list by calling the reverse[list] function on the sorted list.

To sort all of the units with dimensions of time (or by extension, any dimension list) by their magnitude, you can use the following. (Keep in mind that the units function returns the names of the units as strings. The function unit[string] returns the unit with the specified name.:

sort[units[time], { |a,b| unit[a] <=> unit[b] }]

Including Other Files

For ease of maintenance, you can separate your program code into multiple files and include them in other files. This is accomplished by the use statement. This includes the contents of the named file at compile-time, at the point where the use statement is encountered. For example, to include a file called sun.frink in the current directory:

use sun.frink

The use statement searches for the named files in the following places:

  1. Relative to the root of the current classpath or jar file.
  2. If the statement specifies a fully-qualified URL, the URL is loaded. This includes file:, ftp:, and http: URLs.
  3. Relative to the current file or URL being parsed.
  4. Relative to the current working directory.
  5. Relative to all paths specified using the -I path command-line option.
  6. From the internal "standard library" shipped within the jar file's /lib directory. This location may change.

The use statement has protection against including a file multiple times.

Tip: If you're including a file that's not relative to any of the above, you'll need to specify it using an absolute file: URL, such as:

use file:///c:/prog/frink/samples/sun.frink
or
use file:///prog/frink/samples/sun.frink

This is necessary because a file or relative URL can legitimately contain colons on some operating systems. For example, on a UNIX-like system, you could have a subdirectory called c: and that would be just fine. Frink doesn't try to duplicate all the quirks of all operating systems and their wacky filename rules.

Object-Oriented Programming

Frink allows you to write your programs in object-oriented fashion, allowing complex data structures that are still easy to use. It's not fully completed yet, (inheritance isn't implemented,) but works fine for programs that don't require inheritance.

Classes are defined using the class keyword and a syntax that won't particularly surprise anyone who has worked with Java, C++, Ruby, Python, or other object-oriented languages.

The format of a class and how to use it is demonstrated in the classtest.frink file.

Interfaces are defined using the interface keyword, and is similar to Java's implementation.

The format of an interface and how to use it is demonstrated in the interfacetest.frink file.

Methods on an object can be listed using the methods[obj] function. If obj is an instance of an object, this lists the instance-level and class-level methods of that object. If called with a classname (that gives you the "Metaclass Object" for that class,) it displays just the class-level methods.

Graphics

Frink has a powerful system for drawing graphics in a simple way. Here are some of the features:

Introduction to Graphics

Graphics are drawn and displayed in three steps:

  1. Create an object of type graphics:

    g = new graphics

  2. Draw your shapes into the graphics object:

    g.line[1,100,100,1]
    g.line[1,1,100,100]

  3. Show (or print, or write your image to a file):

    g.show[]

    Graphics Introduction

That's it. Frink takes care of the scaling and centering by default. The coordinates that you choose can be whatever is most convenient and natural for you. You can also create as many graphics objects as you want. By default, when you call graphics.show[], each graphics object is displayed in its own resizable window.

Coordinates

Coordinates in Frink's graphics are very flexible. There are a few things to note:

For example, the following short program prints graph paper with a 1 mm grating:

g= new graphics
g.color[.7,.9,.7] //light greenish-gray

for x=0 mm to 8.5 in step 1 mm
   g.line[x, 0 in, x, 11 in]

for y=0mm to 11 in step 1 mm
   g.line[0 in, y, 8.5 in, y]

g.print[]

Shapes and Colors

All of Frink's graphics are built out of a small number of basic shapes. These are drawn into a graphics object using the methods outlined below. For example, you use them like the following:

g = new graphics
g.color[0,0,0] // black
g.fillEllipseCenter[0,0,10,10]
g.color[1,1,0] // yellow
g.fillEllipseCenter[0,0,9,9]
g.show[]

Sample Circle

MethodDescription
color[r, g, b]Sets the current drawing color. All following drawings will be made using this color. The color is specified with its red, green, and blue components which are floating-point values which must range from 0.0 to 1.0, with 0.0 being completely dark for that component, and 1.0 being the brightest value for that component. In a new graphics object, the default drawing color is black.
color[r, g, b, alpha]Also specifies a color, but with transparency. The alpha component specifies the opacity of the color, and takes values from 0.0 to 1.0, with 1.0 being fully opaque and 0.0 being fully transparent. (Note: transparency requires Java 1.2 or later.)
backgroundColor[r, g, b]Sets the background color of the graphics window or image file. The color components are specified as above. There should be only one backgroundColor method call in a graphics object, and it should be the first method called when drawing. If more than one call to backgroundColor is made, this will raise a warning. (It will also replace any existing background color, but that behavior should not be relied on.) By default, the background color is opaque white (or transparent when writing formats like SVG.)
stroke[width]Sets the stroke width used to draw lines, polygon outlines, ellipses, and polylines. If the width is a dimensionless number, (e.g. 10) the stroke will be scaled along with the drawing. If the stroke has units of length, (e.g. 2 mm) the lines will be rendered at that constant width regardless of how the image is scaled. (Note that the stroke width can not be changed in Java 1.1.)
line[x1, y1, x2, y2]Draws a straight line segment between the points (x1, y1) and (x2, y2) using the current color.
fillRectSize[x, y, width, height]

drawRectSize[x, y, width, height]

Draws a rectangle (filled or outlined, depending on the method called) with top left coordinate (x,y) and the specified width and height. If the width or height are negative, this draws the rectangle to the left or to the top of that point.
fillRectSides[x1, y1, x2, y2]

drawRectSides[x1, y1, x2, y2]

Draws a rectangle (filled or outlined, depending on the method called) defined by its four sides. The sides do not have to be in any particular order.
fillRectCenter[cx, cy, width, height]

drawRectCenter[cx, cy, width, height]

Draws a rectangle (filled or outlined, depending on the method called) defined by its centerpoint (cx, cy) and its width and height.
fillEllipseSize[x, y, width, height]

drawEllipseSize[x, y, width, height]

Draws a filled or unfilled ellipse (or circle if width==height) with top left coordinate (x,y) and the specified width and height. If the width or height are negative, this draws the ellipse to the left or to the top of that point.
fillEllipseSides[x1, y1, x2, y2]

drawEllipseSides[x1, y1, x2, y2]

Draws a filled or unfilled ellipse (or circle if width==height) defined by its four sides. The sides do not have to be in any particular order.
fillEllipseCenter[cx, cy, width, height]

drawEllipseCenter[cx, cy, width, height]

Draws a filled or unfilled ellipse or circle defined by its centerpoint (cx, cy) and its width and height.

Advanced Shapes

Polygons and Polylines

Drawing a curve or a polygon out of line primitives might not give good results, as the lines don't know that they're supposed to be connected to each other. To solve this problem, Frink has polyline, polygon and filledPolygon objects which produce high-quality, connected lines with properly-joined corners. Drawing a polygon or polyline to a graphics object consists of a few steps:

  1. Create the graphics object (if you haven't done so already.)

    g = new graphics

  2. Create a polygon, filled polygon, or polyline. (The sample below shows all three. Pick one.)

    p = new polygon
    or
    p = new filledPolygon
    or
    p = new polyline

  3. Add an arbitrary number of points to the line or polygon using its addPoint method. Each point represents a vertex in the polyline or polygon.

    p.addPoint[x,y]
    p.addPoint[x,y]
    p.addPoint[x,y]

    Note that a polygon or filledPolygon is automatically closed. You should not manually connect the last point back to the first by repeating it at the end of the list. You should only have as many addPoint calls as there are vertices in your polygon.

  4. Add the polygon to the graphics object. A polygon can be added to multiple graphics objects. The drawing color and stroke width used in rendering the GeneralPath are the ones which are active at the time of the .add call. Note that no points should be added to the polygon after it is added to the graphics object!

    g.add[p]

  5. Show the graphics object (or print, or save it to a file, or keep drawing into it and show it later...)

    g.show[]

GeneralPath

A GeneralPath allows you to create complex shapes consisting of straight lines and quadratic and cubic Bézier curves. These paths can be filled or outlines, and can have multiple sub-paths that represent the "inside" and "outside" of an object. For example, rendering a filled letter P in which one can see through the "hole" in the P can be obtained with a GeneralPath, and is not possible with a polygon.

Note: The GeneralPath functionality is only available under Java 1.2 and later. Attempting to draw with a GeneralPath in earlier releases will produce a warning and the GeneralPath will not be drawn.

Using a GeneralPath object consists of a few steps:

  1. Create the graphics object (if you haven't done so already.)

    g = new graphics

  2. Create a GeneralPath or a filledGeneralPath. (The sample below shows both. Pick one.)

    p = new GeneralPath
    or
    p = new filledGeneralPath

  3. Add an arbitrary number of segments using the following methods on the GeneralPath class:
    MethodDescription
    moveTo[x,y]Moves to the specified point without drawing. This creates a new subpath. This should be the first call, otherwise the initial point is unspecified.
    lineTo[x, y]
    addPoint[x,y]
    Draws a straight line segment from the current point to the specified point. The addPoint syntax is retained to make it easy to change code from a polygon or polyline representation to use GeneralPath.
    quadratic[cx, cy, px, py]Draws a quadratic Bézier curve from the current point to the point specified by px,py using the coordinates specified by cx,cy as the "control point". The curves at each endpoint will be tangent to a line connecting that point and the control point. Quadratic Curve
    (Control points drawn for clarity.)
    cubicCurve[c1x, c1y, c2x, c2y, px, py]Draws a cubic Bézier curve from the current point to the point specified by px,py using the coordinates specified by (c1x,c1y) and (c2x,c2y) as the "control points". The curves at each endpoint will be tangent to a line connecting that point and its corresponding control point. Cubic Curve
    (Control points drawn for clarity.)
    ellipseSides[x1, y1, x2, y2]Creates an ellipse with the specified coordinates indicating its sides, that is, a rectangle that will contain it. Note that an ellipse is considered to be disconnected from previous line segments, so you should use a moveTo[x,y] after this call to create a new path.
    ellipseSize[x1, y1, width, height]Creates an ellipse with the (x1,y1) coordinates indicating the top left corner of its bounding box, and the specified width and height relative to that point. Note that an ellipse is considered to be disconnected from previous line segments, so you should use a moveTo[x,y] after this call to create a new path.
    ellipseCenter[cx, cy, width, height]Creates an ellipse with the (cx, cy) coordinates indicating the center of the ellipse, and the specified width and height. Note that an ellipse is considered to be disconnected from previous line segments, so you should use a moveTo[x,y] after this call to create a new path.
    circularArc[cx, cy, angle]Creates a circular arc from the current point. The (cx, cy) coordinates indicate the center of the circle, and angle specifies the angle to go around the circle in the counterclockwise direction. Note that the angle parameter should have units of an angle, e.g. 90 degrees or 1.2 radians or even 1.2 (implying radians) if the standard data file is used which treats radians as a dimensionless number.
    close[]Closes the current subpath by drawing a straight line to the initial point of the subpath. It is strongly recommended to use this method to close curves, as it properly joins corners, and informs the curve that it is logically closed. This creates a new subpath, so you should use a moveTo[x,y] after this call to create a new path.
  4. Add the GeneralPath to the graphics object. A GeneralPath can be added to multiple graphics objects. The drawing color and stroke width used in rendering the GeneralPath are the ones which are active at the time of the .add call. Note that no points should be added to the GeneralPath after it is added to the graphics object!

    g.add[p]

  5. Show the graphics object (or print, or save it to a file, or keep drawing into it and show it later...)

    g.show[]

For a sample of using the GeneralPath class, see GeneralPathTest.frink which demonstrates drawing a filled letter "P" with a properly-transparent hole.

Graphics with Text

High-quality text with transparent anti-aliased edges can be added to any graphics object using the following methods:

FunctionDescription
font[fontName, height]

font[fontName, style, height]

Sets the current font that will be used to render text. The arguments are:
  • fontName: A string indicating the name of the font family. For portability to all platforms and image types (including SVG files,) this should be one of "Serif", "SansSerif", "Monospaced". However, any font name available on your system may be used if you don't care about portability.
  • style: An string containing information about the font style. This should contain one of "plain", "bold", "italic", "bold+italic". (It's actually a case-insensitive substring search that just looks for "bold" and "italic" so this can be written in a lot of ways.)
  • height: The height of the font in the current coordinate system. Height is taken as the distance from standard baseline to the next standard baseline, not necessarily as the height of the tallest characters. This fits the usual definition of font size used in most systems.

    If the height is a dimensionless number, (e.g. 10) the font will be scaled along with the drawing. If the height has units of length, (e.g. 10 points or 1 cm) the font will be rendered at that constant height, regardless of how the image is scaled. Note that using a height with dimensions of length might force the text out of the viewable area if the drawing is scaled too small to accommodate it.

If you don't specify a font before drawing text, your system will use its default font, which may give different results when the same program is run on different systems or rendered to different devices. This behavior may change to specify a fixed default font in the future.

text[text, x, y]Draws the specified text centered (vertically and horizontally) at the coordinates (x,y). Since it's hard to predict how wide (or tall) text will be until it's rendered, centering is often the most useful option. Note that text is currently treated as a single line, with no text wrapping nor processing of newlines.
text[text, x, y, horizontalAlign, verticalAlign]Draws the specified text with one point specified by the (x,y) coordinates and the rest of the text aligned relative to that as specified by the horizontalAlign and verticalAlign parameters.

The parameter horizontalAlign is a string containing one of: "left", "right", "center" indicating if the x coordinate indicates the left, right, or center horizontal position of the text. For example, if the horizontal alignment is given as "right", the specified x coordinate will be the right side of the text. Note that text is currently treated as a single line, with no text wrapping nor processing of newlines.

The parameter verticalAlign is a string containing one of: "top", "bottom", "center", "baseline" indicating if the y coordinate indicates the top, bottom, center, or baseline vertical position of the text. For example, if the vertical alignment is given as "top", the specified y coordinate will be the top of the text. The "baseline" parameter indicates the bottom of most characters, but characters with descenders like "j", "p" and "q" may hang below the baseline.

Example: The following program draws a 5x5 grid of random characters.

g=new graphics
g.font["SansSerif", "bold", 10]   

for x=1 to 5
   for y=1 to 5
      g.text[char[random[char["A"], char["Z"]]], x*10, y*10]

g.show[]

Letter grid example

Showing Graphics

Once a graphics object has been constructed, it can be shown on-screen, printed, or written to a file using the following methods:

MethodDescription
show[]Displays the graphic object using the default method. On most platforms, this opens a new resizable window. Note that this method returns an object that can be used to repaint the graphics. See the Animation section of the documentation for more.
print[]Prints the graphics object to a single page on a printer. This will produce a print dialog that allows you to select the printer, and the orientation and margins for the page.
printTiled[pagesWide, pagesHigh]Prints the graphics object tiled across multiple pages on a printer. This allows very large graphics to be printed. The arguments indicate how many printer pages wide and high the graphic should be drawn.
write[filename, width, height]

writeTransparent[filename, width, height]

write[filename, width, height, insets]

writeTransparent[filename, width, height, insets]

Writes the image to a file with the specified width and height (usually in pixels). The format of the file is guessed from the filename's extension. If the writeTransparent method is called, and if the image format supports it, the image will be rendered with a transparent background, allowing you to stack and create composite images with full anti-aliasing and background support. The file formats supported by your version of Java may vary, but the following should be supported:
  • JPEG: Does not support transparency. Requires Java 1.4 or later.
  • PNG: (Portable Network Graphics) Supports transparency and full anti-aliasing of transparent graphics. Requires Java 1.4 or later.
  • SVG: (Scalable Vector Graphics) A vector format that is infinitely resizable. Supports transparency. By default, all backgrounds in SVG graphics are transparent. Works in all versions of Java. Warning: Due to a deficiency in the SVG standard, drawings with dimensions of length (e.g. "1 inch") may not work properly for some shapes, notably polygons and polylines.

In the versions of these functions where insets is specified, the value of that argument indicates how wide a border should be left when drawing. When specifying 1 (indicating 100%) to the insets argument, the drawn image occupies 100% of the width and height, with no border. By default, only 95% (0.95) of the width or height is used, leaving a small 5% border around the drawn graphics. Specifying the insets is important when rendering image files out at their original size. See the Images section of the documentation for more.

writeFormat[filename, format, width, height]

writeFormatTransparent[filename, format, width, height]

Writes to a file, explicitly specifying the format. The format should be a string containing one of "svg", "SVG", "jpeg", "JPEG", "jpg", "JPG", "png", "PNG", or possibly another format that your Java platform understands.

The following draws a partially-transparent circle, and then successively displays it on-screen, prints it, and renders it to various image files:

g = new graphics
g.color[0, 0, 1, 0.5]   // Blue, half-transparent
g.fillEllipseCenter[0,0,10,10]

g.show[]
g.print[]
g.write["circle.jpg", 200, 200]
g.write["circle.png", 200, 200]
g.writeTransparent["circleTrans.png", 200, 200]
g.write["circle.svg", 200, 200]

Images

Bitmap images can be loaded and drawn to a graphics object, displayed in their own window, resized, printed (including tiled across several pages,) drawn over, saved out to files, etc. You can also load an image or create it in memory, and read or write the values of individual pixels, allowing image processing or analysis.

Note that Java 1.1 and earlier did not have a portable, public way to read and write individial pixels of images, so many of these methods require Java 1.2 or later.

To load an image, call new image[URL], passing it a URL. The URL can be of any type your Java platform understands, including a file: URL indicating a file on your local system:

img1 = new image["http://futureboy.us/images/futureboydomethumb4.gif"]
img2 = new image["file:yourfile.gif"]

To create a new (blank) image, specify the width and height in pixels:

img3 = new image[640, 480]

The image can be then shown in its own window by calling the .show[] method:

img1.show[]

The following table summarizes the methods available on an image object:

MethodDescription
getHeight[]Returns the height of the image in pixels.
getWidth[]Returns the width of the image in pixels.
getPixel[]Returns the pixel's color as an array of [red, green, blue, alpha] components between, each between 0 and 1 inclusive.
setPixel[x, y, red, green, blue, alpha]Sets the specified pixel's color to the color specified by the red, green, blue, alpha components, each between 0 and 1 (inclusive).
setPixel[x, y, red, green, blue]Sets the specified pixel's color to the color specified by the red, green, blue, components, each between 0 and 1 (inclusive). The pixel will be fully opaque.
averagePixels[left, top, right, bottom]Returns the average color of the pixels within the region specified by the given coordinates. Each coordinate may be a floating-point value, and if an incomplete pixel is sampled, it is weighted accordingly. The return value is an array of [red, green, blue, alpha] components, each between 0 and 1 inclusive. The values for the coordinates can range from 0 to (im.getWidth[] or im.getHeight[],) inclusive, indicating, for example, the left and right side of the pixel (or subpixel) to be sampled. For example, in a 2x2 pixel image, you'd want to sample [0,0,2,2] to average the whole image.
makeARGB[]Forces a loaded image to have an ARGB color model, that is, to have an 24-bit true color model with an 8-bit alpha channel that supports transparency. By default, when loading an image, the color model is preserved and may not support true color nor transparency.
write[filename] - Write the image to the specified filename. The format of the file is guessed from the filename's extension. The file formats supported by your version of Java may vary, but the following should be supported:
  • JPEG: Does not support transparency. Requires Java 1.4 or later.
  • PNG: (Portable Network Graphics) Supports transparency and full anti-aliasing of transparent graphics. Requires Java 1.4 or later.
show[]Displays the image (by default, in its own window.)
print[]Prints the image to a printer.
printTiled[pagesWide, pagesHigh]Prints the image to a printer, tiled across several pages to make a very large image.

Images can be drawn onto a graphics object with the following methods on the graphics object:

MethodDescription
draw[img, left, top, width, height]Draws the specified image onto the graphics object with the specified top left coordinates and the specified width and height. Note that this method does not specifically preserve the image's aspect ratio and thus may distort the image if the width and the height are not in the same ratio as in the original image.
fitCenter[img, cx, cy, width, height]Draws an image onto the graphics object with the specified center coordinates (cx,cy), making it fill the specified width and height as much as possible without modifying the aspect ratio. This will thus preserve the proportions of the image.

See the rewriteImage.frink sample program for an example of loading an image, writing a semi-transparent watermark over it, and then saving the image out to another file at its original size.

Animation

As of the 2009-08-12 release, Frink supports generalized animation. Animation is performed by calling the replaceGraphics[g] method on a graphics window (obtained by graphics.show[]) which replaces the graphics object with a new graphics object and repaints the window.

A short sample of animation is available in the animate.frink sample program.

Starting with the 2009-02-03 release, an on-screen graphics window can be repainted as items are added to its graphics object. Repainting is not done automatically, but is under the programmer's control. This allows the screen to be repainted only when desired.

The graphics.show[] method returns an object with a repaint[] or replaceGraphics[g] method that instructs the graphics window to be painted. Tip: don't save this object if you're not planning on doing incremental animation, or set the variable to some value such as undef when animation is complete. This will allow the window's resources to be garbage-collected as soon as possible.

g = new graphics
window = g.show[]

for x = 1 to 10
{
// Do something that takes a long time...
   g.fillRectangleCenter[x,0,1,1]
   window.repaint[]
}

Sample Graphics Programs

Below is a small list of simple but interesting and powerful programs that demonstrate Frink's graphics.

FilenameDescription
animate.frinkDemonstrates simple animation.
graphpaper.frink Prints graph paper with 1 cm and 1 mm grids. Demonstrates exactly-sized printing.
rewriteImage.frink Loads a bitmap image and writes a semi-transparent watermark on it and then saves it back out to a file. Demonstrates image loading, drawing over images, and saving image files at their original size.
SolarCooker2.frink Draws a parabola and focal point for a small solar cooker that you can cut out and use to make a precisely-shaped mirror for cooking hot dogs and such.
simplegraph3.frink A very powerful but simple program to graph just about any equation, no matter how complicated or ill-behaved, using Frink's Interval Arithmetic capabilities.
spiral.frink Draws simple, colorful spirals. Fiddle with the numbers to make different, interesting patterns.
drawSolarSystem.frink Draws the current position of planets in the solar system. (With exaggerated scale, otherwise they're invisible.) Requires the planets.frink and sun.frink libraries.

Temperature Scales

Temperature scales that have their zero point (kelvin, Rankine) at absolute zero can be multiplied and converted normally.

45 Rankine -> K
25

Temperature scales like Fahrenheit, Celsius, and Reaumur cannot be represented as normal multiplicative unit definitions because their zero point is not at absolute zero. Thus, to avoid ambiguous "do what I mean" interpretation, you must use the functions Fahrenheit[x] or the shorter F[x], Celsius[x] or the shorter C[x], and Reaumur[x] to convert to/from these temperature scales:

To represent a Fahrenheit temperature:
Fahrenheit[451]
or
F[451]
505.9277777777778 K (temperature)

To convert another temperature scale to Fahrenheit:
Fahrenheit[30 K]
or
F[30 K]
-405.67

To represent a Celsius temperature:
Celsius[0]
or
C[0]
273.15 K (temperature)

To convert another temperature scale to Celsius:
Celsius[30 K]
or
C[30 K]
-243.15

To convert between scales (short version):
Fahrenheit[98.6] -> Celsius
or
F[98.6] -> C
37.0

This is equivalent to saying:
Celsius[ Fahrenheit[98.6] ]
or
C[ F[98.6] ]
Except this way doesn't turn the result into a string like the -> operator does.

Note: The units degC and degF only indicate the difference in the size of a degree in these various scales. They should only be used when you're indicating the difference between two temperatures, (say, how much energy to raise the temperature of a gram of water by 5 degrees Celsius,) not for absolute temperatures. Conversely, the conversion functions above should not be used when the difference between temperatures in two scales should be compared.

Other Data Sources

One of the main design goals of Frink was to allow new sources of data to be added in very easily. These special sources are not necessarily defined in the data file. The three data sources listed below retrieve data on demand from up-to-the minute data on the Internet (and thus require connection to the Internet.)

Historical U.S. Price Data

Obligatory Disclaimer: This feature requires connection to the internet. If you are using Frink on a handheld device, you may incur connection charges. Also, since I cannot guarantee the availability of any internet sites, this feature is intended only as a bonus that may not work reliably if at all. You may also require some proxy configuration if you use an FTP proxy server to access the web.

The units "dollar" or "USD" indicate the value of a current U.S. dollar (which is arbitrarily chosen as the standard unit of currency.) Historical price data is available to allow comparisons between the historical "buying power" of U.S. currency. This allows you to adjust historical prices for inflation. These are represented by specially-named units containing both the currency and the year, separated by an underscore, for example:

Data after 1913 is fetched live from the U.S. Department of Labor Bureau of Labor Statistics Consumer Price Index data, specifially by retrieving and parsing this file. If that file is unavailable, the data will be fetched from a static file distributed with Frink, which is only as recent as your version of Frink.

Data from 1700 to 1912 is based on some general economists' guesses and should be taken with a grain of salt. U.S. data before 1700 is not available, and probably wouldn't be meaningful unless you could convert between the value of pelts, tinder, and tallow.

Warning: Fetching this file uses Java's FTP capability which doesn't work if you have more than one network card, or a network card and a dialup connection. (See Java Bug 4107059) It'll just lock up when you request historical dollar data. That's nice. In addition, the BLS web and FTP servers seem to have frequent outages, and historical data will not be available if the servers are down, or if you are not connected to the Internet.

From 1913-present, you can even use monthly resolution by indicating the month after the year. Months are 2 digits and padded with zeros:

Historical currency values can be converted to the current value. For example to find today's cost of Mark Twain's passage to Europe and the Holy Land on the steamship Quaker City at a cost of $1250 in 1867 (detailed in The Innocents Abroad, the conversion of which was one of my first web projects):

1250 dollar_1867 -> dollar
14982.240769251547535000

And, you can add the 5 dollars/day in gold that they were encouraged to bring along to cover expenses for the 6-month trip:

1250 dollar_1867 + 5 dollars_1867/day 6 months -> dollar
26043.38587544437

You can translate from one year and month to another, if you have a DeLorean, and want to watch a Reagan movie:

50 cents_1955_11 -> dollars_1985_10
2.020446096654275

Historical British Price Data

Obligatory Disclaimer: This feature requires connection to the internet. If you are using Frink on a handheld device, you may incur connection charges. Also, since I cannot guarantee the availability of any internet sites, this feature is intended only as a bonus that may not work reliably if at all. You may also require some proxy configuration if you use an HTTP proxy server to access the web.

The units Britain or Britain_Pound or Britain_currency or Great_Britain or Great_Britain_Pound or United_Kingdom_Pound, or England or England_currency or GBP (the ISO-4217 code for the U.K. Pound) indicate the current pound (but don't use pound by itself--that's a measure of mass.) The exchange rate between the pound and all other world currencies, see below, is fetched live from the Internet.

Historical price data is available to allow comparisons between the historical "buying power" of British currency, both pre- and post-decimalization. Data goes back to the year 1600. I don't know if data before this would be very meaningful.

Historical currency values are represented by specially-named units containing both the currency and the year, separated by an underscore. All can be used in the plural, (e.g. pound_1960 or pounds_1960 or GBP_1960 are all valid). The following are examples of the plethora of values up to and including 1970 (from 1971 on, it just became pounds and pence):

ExampleDescription
guinea_1865 A pound plus a shilling (21/20 pounds)
pound_1865 Fundamental unit
GBP_1865 Fundamental unit
sovereign_1865 A pound coin
merk_1865 13/6d (that is 13 shillings and 6 pence) or 27/40 of a pound
mark_1865 2/3 pound
noble_1865 80 pence or 1/3 pound
crown_1865 1/4 pound or 5 shillings
florin_1865 2 shillings or 1/10 pound
shilling_1865 1/20 pound or 12 pence
groat_1865 4 pence or 1/60 pound
penny_1865 or pence_18651/12 shilling or 1/240 pound
farthing_1865 1/4 penny or 1/960 pound

No wonder they went to decimalization. It was either that or go to base-960 math.

To form combinations you can add them (using parentheses when necessary). For example, to convert a historical rate per day to current dollars/year:

(4 pounds_1860 + 3 shilling_1860 + 5 pence_1860) / day -> dollars/year
101853.3826649

I acknowledge that's a bit cumbersome.

So, you can find out what a great amount of money was involved when the British Parliament announced a 20,000 pound prize in 1714 for solving the Longitude Problem:

20000 pound_1714 -> dollars
2807866.8

That's a lot of lettuce. For more about the fascinating history of this problem, I highly recommend Dava Sobel's Longitude: The True Story of a Lone Genius Who Solved the Greatest Scientific Problem of His Time. By God, Harrison, I will see you righted.

Thanks to Dan Weiler who loaned me the above book which I never returned (and passed along to my Grandpa.) Sorry, Dan, I'll buy you a book of your choice.

International Exchange Rates

Obligatory Disclaimer: This feature requires connection to the internet. If you are using Frink on a handheld device, you may incur connection charges. Also, since I cannot guarantee the availability of any internet sites, this feature is intended only as a bonus that may not work reliably if at all. You may also require some proxy configuration if you use an HTTP proxy server to access the web.

Current exchange rate between almost all of the world's currencies is available. Exchange rates are fetched live from an allegedly zero-delay source on the Internet. The currency can either be specified by the name of the country, by the 3-letter ISO-4217 code for the currency, or by one of the combinations shown below. The following examples all work:

To list all of the currencies, you can use:

units[currency]

So, I'm watching "The Amazing Race" and seeing a team pay 600 Baht in Thailand for a hotel room. How much is that in a currency I'm familiar with?

600 baht -> USD
13.73724

I could have also used Thailand_Baht or Thailand in the above example.

You can also get the current trade rates of various precious metals (normalized from the obscure troy weights that these values are measured in.) These are referenced using the capitalized name (lower case brings up element properties for now... this will all be addressed when I add object-oriented behavior to Frink) or the 3-letter ISO code (which is an X followed by the chemical symbol):

ElementISO Code
GoldXAU
PlatinumXPT
SilverXAG
PalladiumXPD

Gold
8765.9010519364188896 kg^-1 USD (price_per_mass)

Note that this is in units of currency/mass (the international exchange rates for these are specified in dollars/troyounce, (but try to find that written somewhere)), but you can use any units of mass you want:

1 ton Gold
7952291.666666666667 USD (currency)

Or find out how much it would be worth to melt down that necklace:

3 gram 18 karat Gold
19.723277366856942501600 USD (currency)

Note: If you want to set a different base currency in your units file, and if you want currency conversions to still work, you should now) define the base currency as its 3-letter ISO-4217 currency code (say, "EUR" or "JPY"). This will allow the currency converter to unambiguously figure out which currency you mean. The following special cases work as well:

SymbolDescription
dollarU.S. dollar
EuroEuro
euroeuro
Euro symbol (Unicode \u20ac)
¥ Japanese Yen symbol (Unicode \u00a5)
£ U.K. Pound symbol (Unicode \u0163)

Interval Arithmetic

Frink has the magical ability to perform rigorous interval arithmetic throughout calculations. So what is interval arithmetic? Well, you can think of it as a "new kind of number" that represents a fuzzy range of values. For example, you may know that a value lies between 1 and 2, but you're not quite sure where the value lies in that interval. Depending on your philosophy, you can think of an interval as specifying a fuzzy error bound, or you can think of an interval as simultaneously taking on all values within its bounds.

Frink can take this uncertain interval and propagate the uncertainty through its calculations, giving you the ability to see how the initial uncertainties in your values affect your final calculations.

Currently, the way to indicate that something is an interval is to use the new interval syntax (although something more concise will likely be added later, and the output format may change.)

a = new interval[2,3]
b = new interval[5,7]

The intervals can then be manipulated in mathematical expressions, either with ordinary scalar variables or other intervals:

a * 3
[6, 9]

a + b
[7, 10]

a * b
[10, 21]

Intervals may also have a "middle" or "main" value which indicates the best-known value. Note that values should be specified in increasing order.

d = new interval[2, 2.5, 3]
e = new interval[7, 8.2, 9.4]
d * e
[14, 20.5, 28.2]

Of course, all intervals used in a calculation must have "main" values or the main value will be dropped, creating an interval with only upper and lower bounds.

Note: The boundaries and "main" values for intervals must be real numbers. (These numbers can also have dimensions like feet, meters, etc.) Although there is a theory of complex intervals, it's much harder and may not get implemented any time soon. (If you want to speed the process, the most expensive book in the world is on my Amazon.com wishlist, if you want to buy it for me.)

Lest you think that intervals are simpler than they are, I find that people better understand them when they consider the following case:

x = new interval[-2,2]

Now, let's square x. Note that the values at each endpoint are equal to 4. However, over the range [-2,2], the value of x2 ranges from 4, down to 0 (at x=0), and back up to 4. Frink does the right thing for the values over this whole range:

x^2
[0, 4]

Yeah, that is cool. Frink tracks appropriate boundaries for intervals throughout all of your calculations.

Frink's interval arithmetic is also rigorous in its treatment of error bounds. It painstakingly controls the rounding direction of arithmetic operations so that the boundaries are guaranteed to include the next-largest or next-smallest representable floating-point number that contains the interval. (See notes below on implementation status.) This is subtle, but I have spent a lot of work ensuring that the boundaries came out trustable, and no bigger than need be. For example:

m = new interval[3,6]
1.0/m

[0.16666666666666666666, 0.33333333333333333334]

Note that the bottom bound is rounded down, and the top bound is rounded up.

Currently, almost all functions have been made interval-aware. Implementing all functions to arbitrary precision will take quite a bit of effort. Of course, that would be much slower, too. Error bounds are unfortunately not "sharp" for all operations (meaning as tight as they could possibly be with limited precision.) I've noted them as such in the Interval Arithmetic Status section below.

Not all operators such as < > = make unambiguous sense when applied to intervals, so Frink has introduced new operators to disambiguate these cases, and will implement other operators to work with intervals. See the Interval Comparison Operators section below for more details.

By default, degenerate intervals which have the same upper and lower bounds are "collapsed" into a single real number. If you want to maintain them as intervals, call the function collapseIntervals[false] before constructing or performing mathematics on those intervals.

As everyone uses Interval Arithmetic for the first time, they come upon two characteristic problems in the field: the dependence problem and the overestimation problem. These are common to all interval analysis, and not just to Frink, and are covered in extensive detail in the Interval Arithmetic section of the Frink FAQ.

For more information about the field of Interval Arithmetic, please visit the Interval Computations website. (Link opens in new window.)

Interval Arithmetic Example

Interval arithmetic is an incredibly powerful feature that allows programs that weren't necessarily written with intervals in mind to track error bounds throughout your calculations, and can be magically applied to programs that are already written. For example, let's take some calculations to find the volume and density of a sphere:

circumference = eval[input["Enter circumference of a sphere: "]]
mass = eval[input["Enter the mass of the sphere: "]]
diameter = circumference / pi
radius = diameter / 2
volume = 4/3 pi radius^3
density = mass / volume
println["The density is: " + (density -> "g/cm^3")]

Now, you can run the program and enter something like "9.1 inches" for the circumference and "5.1 ounces" for the mass and find out the density of your baseball. No surprises there. But when you read the rules of Major League Baseball, you'll find that section 1.09 states:

"The ball shall be a sphere formed by yarn wound around a small core of cork, rubber or similar material, covered with two stripes of white horsehide or cowhide, tightly stitched together. It shall weigh not less than five nor more than 5 1/4 ounces avoirdupois and measure not less than nine nor more than 9 1/4 inches in circumference."

So, using your exact same program above, and a little interval input, Frink can calculate the effects of these allowed variations and show you the allowed range of densities of any legal baseball:

Enter circumference of a sphere: new interval[9, 9+1/4] inches
Enter the mass of the sphere: new interval[5, 5+1/4] ounces
The density is: [0.64720283343427980773, 0.73778085086685322066] g/cm^3

The output indicates the range of uncertainties. Note that two different intervals were used to perform this calculation, and the effects of their uncertainties was automatically tracked throughout all calculations. All this in a program that wasn't even written with intervals in mind. Unscrupulous teams may also not that the official definition allows for a large variation in allowable densities of baseballs, which could be manipulated to your advantage.

Also note that I put the units of measure (e.g. inches, ounces) outside the brackets. You could put them inside the brackets, but you'd just have to write them twice in this case. Intervals can, of course, contain units of measure.

By the way, I'm working on a more concise notation for specifying intervals. Join the mailing list if you're interested in the discussion.

Interval Comparison Operators

The relational operators (e.g. < == >, etc) work with intervals, but there are many ambiguous cases. These operators try to Do The Right Thing when applied to intervals. If you compare intervals that do not overlap, they return the appropriate result. If, however, the intervals do overlap, they terminate the program with an error similar to the following:

Comparison expression: Using operator > to compare intervals [1, 3] and [2, 4]
This operator is only defined if there is no overlap between intervals.
Please modify your program to use interval-aware comparison operators.

To handle the overlapping cases, Frink defines operators like "certainly less than" (CLT) and "possibly less than" (PLT). These operators can directly replace the normal relational operators. These new operators also work with normal real numbers, so you can still write programs that run using either intervals or real numbers as input.

OperatorDescription
CEQCertainly equals
CNECertainly not equals
CLTCertainly less than
CLECertainly less-than-or-equal-to
CGTCertainly greater than
CGECertainly greater-than-or-equal-to
PEQPossibly equals
PNEPossibly not equals
PLTPossibly less than
PLEPossibly less-than-or-equal-to
PGTPossibly greater than
PGEPossibly greater-than-or-equal-to

Example:

a = new interval[1,3]
b = new interval[2,4]
a PLT b
true
a CLT b
false

Interval Arithmetic Status

As noted above, not all functions are implemented for intervals. The following table notes the status of the implementation of various operators and functions. If a function does not appear on this list, it may still return values for interval arguments, but you shouldn't trust it because I haven't evaluated it for discontinuities or non-monotonicity yet.

Function / OperatorArbitrary Precision?Rigorous Error Bounds?Notes
+YY
-YY
*YY
/YY
modYY
^NN Performed to hardware precision only.
floor[x]YY
ln[x]NY Performed to hardware precision only.
log[x]NN Performed to hardware precision only.
exp[x]NY Performed to hardware precision only.
sin[x]NN Performed to hardware precision only.
cos[x]NN Performed to hardware precision only.
tan[x]NN Performed to hardware precision only.
sec[x]NN Performed to hardware precision only.
csc[x]NN Performed to hardware precision only.
cot[x]NN Performed to hardware precision only.
arccos[x]NY Performed to hardware precision only.
arcsin[x]NY Performed to hardware precision only.
arctan[x]NY Performed to hardware precision only.
arctan[x, y]NN Performed to hardware precision only. Returns arctan[x/y] corrected for quadrant. Arguments can be real or intervals. Has some corrections to range of function to eliminate branch discontinuity across x=0 when y<0.
sqrt[x]NN Performed to hardware precision for floating-point numbers, exact values for integers that produce exact integer values.
infimum[x]YY Returns the infimum (lower bound) of an interval. If called with a number that is not an interval, just returns the number.
supremum[x]YY Returns the supremum (upper bound) of an interval. If called with a number that is not an interval, just returns the number.
magnitude[x]YY Returns the absolute value of the endpoint furthest from zero. If called with a real number, just returns the number. If called with a complex number, returns the absolute value (the magnitude) of the complex number.
mignitude[x]YY Returns the absolute value of the endpoint closest to zero. If called with a real number, just returns the number. If called with a complex number, since "mignitude" of a complex number is generally not defined, this also returns the absolute value (the magnitude) of the complex number.
mainValue[x]Y Y Returns the main (middle) value of an interval. If the interval does not have a middle value, returns undef. If called with a number that is not an interval, just returns the number.

Date/Time Intervals

An interval can also be composed of date/times. The syntax is very similar:

a = new interval[now[], now[] + 3 days]

b = new interval[#1969-08-19#, #2005-06-11#]

You can then perform Date/Time arithmetic on the values.

Java Introspection

Missing a function that you need? Frink can directly call Java code to let you take advantage of any Java library that's in your classpath. Thus, you can use your favorite graphing package, connect to a database, perform lower-level networking, and more, directly from within Frink.

The Java null will be converted to/from the Frink type undef.

Creating Java Objects

New Java objects can be created with the newJava[classname] and newJava[classname, argList] functions. These call Java constructors with the specified arguments. If the constructor takes a single argument, argList can be a single value, otherwise it should be an array of values. The following creates a new Frame and calls some methods on the Frame to display it. Note that the method calls require square brackets.

f = newJava["java.awt.Frame", "Frink Rules!"]
f.setSize[200,200]
f.show[]
f.toFront[]

Arrays of Java objects, including primitives, can be constructed with the newJavaArray[classname, length] method. The classname should be a string containing either be a fully-qualified classname (e.g. "java.util.Hashtable") or a primitive type name, (e.g. "int" or "double").

The methods on a Java object can be listed using the methods[obj] function:

f = newJava["java.awt.Frame", [] ]
sort[methods[f]]

Calling Static Java Methods

If you don't have an instance of the class, you can call static methods in Java classes using the callJava[classname, methodname, argList] function. The following uses the java.lang.Math class to generate a random number.

n = callJava["java.lang.Math", "random", [] ]
0.38102192379837

Lame example, huh? Especially when Frink can already generate random numbers. The same syntax can be used to get a database driver, or something more interesting.

Accessing Static Java Fields

You can access static variables in a class without having an instance of the class by calling the staticJava[classname, fieldname] function.

green = staticJava["java.awt.Color", "GREEN"]
JavaObject:java.awt.Color

If you've constructed a Frame as in the Creating Java Objects section above, you can set its background color by:

f.setBackground[green]

Now go wire all your Java code into Frink. At the moment, not all Frink types are mapped to Java types. If there's a mapping that you need, please let me know.

Embedding Frink

Not only can you call Java from Frink, but you can call Frink from Java. It's quite easy to embed a Frink parser into any Java program and give those programs all of the power of Frink. It can take just a few lines of Java:

String results;
Frink interp = new Frink();
// Enable security here? Currently commented-out.
// interp.setRestrictiveSecurity(true);

try
{
   results = interp.parseString("2+2");
}
catch (frink.errors.FrinkEvaluationException fee)
{
   // Do whatever you want with the exception
}

Warning: Frink is a Turing-complete programming language, and parseString() evaluates a string as a complete program. A Frink interpreter normally has the ability to read your filesystem, call arbitrary Java code, execute infinite loops, allocate infinite amounts of memory, write large amounts of output, and do other things which may compromise your security. Thus, if you're taking input from untrusted users, it's critical to call:

interp.setRestrictiveSecurity(true);

before parsing any user input. This will enable the highest level of security, prohibiting all untrusted actions.

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, especially the Frink class.

If you're interested in integrating Frink into your company's products, please contact Alan Eliasen.

Sample Calculations

The following sections demonstrates some of the real-world calculations I've made with Frink.

Mass and Volume

Let's say you wanted to fill your bedroom up with water. How much water would it take? Let's say your room measures 10 feet by 12 feet wide by 8 feet high.

10 feet 12 feet 8 feet -> gallons
552960/77 (approx. 7181.298701298701)

It would take approximately 7181 gallons to fill it. Note that you get both an exact fraction and an approximation. (If you don't want to see the fraction, put a decimal point in any of the numbers, like 10. or 10.0.) How much would that weigh, if you filled it with water? Frink has the unit "water" which stands for the density of water.

10. feet 12 feet 8 feet water -> pounds
59930.84215309883

So it would weigh almost 60,000 pounds. What if you knew that your floor could only support 2 tons? How deep could you fill the room with water?

2. tons / (10 feet 12 feet water) -> feet
0.5339487791320047

So you could only fill it about .53 feet deep. It'll be a pretty sad pool party.

Liquor

You can set variables on the fly, by using the assignment = operator. Let's say you want to define a new unit representing the amount of alcohol in a can of (quality) 3.2 beer. Keep in mind that 3.2 beer is measured by alcohol/weight, while almost all other liquors (and many beers) are usually measured in alcohol/volume. The density ratio between water and alcohol is given by:

water/alcohol
1.267

Water is thus 1.267 times denser than alcohol. 3.2 beer (measured by weight) is thus actually 4.0 percent alcohol as measured by volume. Now let's set that variable in terms of a beer's density of alcohol per volume so we can compare:

beer = 12 floz 3.2 percent water/alcohol

Then, you wanted to find out how many beers a big bottle of champagne is equal to:

magnum 13.5 percent -> beer
14.07

You probably don't want to drink that whole bottle. Now let's say you're mixing Jungle Juice (using a 1.75 liter bottle of Everclear (190 proof!)) and Kool-Aid to fill a 5-gallon bucket (any resemblance to my college parties is completely intentional.) What percent alcohol is that stuff?

junglejuice = 1.75 liter 190 proof / (5 gallon)
junglejuice -> "percent"

8.78372074090843481138500000 percent

It's really not that strong. About 8.8%. But if you drink 5 cups of that, at 12 fluid ounces each, how many beers have you had?

5 12 floz junglejuice -> "beer"
10.832 beer

Maybe that's why people were getting punched in the head. QED.

More Liquor

Some more useful calculations, most thanks to the lovely Steve Clymer:

How many cases in a keg? (A keg is a normal-sized keg, what those in the beer industry would call a "half barrel," or 1/2 beerbarrel in Frink notation. I don't think they sell full barrels. I've never seen one. It would weigh 258 pounds. A "pony keg" is a "quarter barrel" or, in Frink notation, ponykeg or 1/4 beerbarrel)

keg -> case
62/9 (approx. 6.888888888888889)

How many 12 fluid ounce drinks (i.e. cans o' beer) in a keg?

keg -> 12 floz
496/3 (approx. 165.33333333333334)

What is the price in dollars per fluid ounce of alcohol when buying a keg of 3.2 beer? (Remember that 3.2 beer is measured in alcohol/weight, so we correct by the density ratio of water/alcohol to get alcohol by volume:)

(60 dollars)/(keg 3.2 percent water/alcohol) -> "dollars/floz"
0.74593 dollars/floz

A bottle of cheap wine? (A "winebottle" is the standard 750 ml size.)

(6.99 dollars)/(winebottle 13 percent) -> "dollars/floz"
2.12 dollars/floz

A big plastic bottle of really bad vodka?

(13.99 dollars)/(1750 ml 80 proof) -> "dollars/floz"
0.59104811225625 dollars/floz

Movie Magic

In the movie Independence Day, the alien mother ship is said to be 500 km in diameter and have a mass 1/4 that of earth's moon. If the mother ship were a sphere, what would its density be? (The volume of a sphere is 4/3 pi radius3)

1/4 moonmass / (4/3 pi (500/2 km)^3) -> water
280.68

This makes the ship 280 times denser than water. This is 36 times denser than iron and more than 12 times denser than any known element! As the ship is actually more a thin disc than a sphere, it would actually be even denser. Since it contains lots of empty space, parts of it would have to be much, much denser.

If the object is this dense and has such a large mass, what is its surface gravity? Surface gravity is given by G mass / radius2, where G is the gravitational constant (which Frink knows about):

G 1/4 moonmass / (500/2 km)^2 -> gravity
2.000079

The surface gravity of the spaceship is thus at least twice earth's gravity--and that's on the rim where gravity is weakest. It would actually be much higher since it's much, much flatter than a sphere. I hope you're not the alien that has to go outside and paint it.

Fiscal Calculations

You can calculate the day that your company will run out of cash, based on their financial statements. The following is an example for a real company, based on SEC filings, which read as the following:

Cash and Cash Equivalents (in thousands)
December 31, 2000June 30, 2001
$86,481$41,601

To make this more readable, you can define variables to hold values:

burnrate = (#2001-06-30# - #2000-12-31#) / ((86481 - 41601) thousand dollars)

burnrate -> dollars/day
248012.89431247435

You can calculate the number of days until the money runs out at this rate:

41601 thousand dollars / burnrate -> "days"
167.7372 days

Using date/time math, starting from the last report date (June 30, 2001) you can find out the exact date this corresponds to:

#2001-06-30# + 41601 thousand dollars / burnrate
AD 2001-12-14 04:41:38.101 PM (Fri) Mountain Standard Time

Just in time to see the cinema release of the first Lord of the Rings movie with your last six bucks. Will they know it's Christmas Time at all?

Ouch!

At the moment, I'm watching CNN which is discussing some land-mines used in Afghanistan. They showed a very small mine (about the size of a bran muffin) containing "51 grams of TNT" and they asked how much destructive force that carries. Frink's data file includes how much energy is in a mass of TNT, specified by the unit "TNT". How many feet in the air could 51 grams of TNT throw me, assuming perfect efficiency, and knowing energy = mass * gravity * height?

51 grams TNT -> 185 pounds gravity feet
937.7628167428616

Yikes. 937 feet. But the only difference between explosives and other combustible fuels is the rapidity of combustion, not in the quantity of energy. How much gasoline contains the same amount of energy?

51 grams TNT -> "teaspoons gasoline"
1.2903255 teaspoons gasoline

1.29 teaspoons? That's not much at all. You're buying a huge amount of energy when you fill up your car.

Sniping eBay Auctions

I need a monocle, but I don't want to pay a lot for it. The eBay monocle auction ends in 7 hours and 44 minutes... what time do I need to set the alarm clock for to remind me?

now[] + 7 hours + 44 min
AD 2001-11-17 02:13:51.934 PM (Sat) Mountain Standard Time

Epilogue 2001: I didn't get the damned monocle.

Junkyard Wars

I can't watch Junkyard Wars (or lots of other television shows) without having Frink at my side. This week the team has to float a submerged half-ton Cooper Mini... how many oil barrels will they need to use as floats?

half ton -> barrels water
2.8530101742118243

They're trying to hand-pump air down to the barrels, submerged "2 fathoms" below the water. If the guy can sustain 40 watts of pumping power, how many minutes will it take to fill the barrel?

2 fathoms water gravity barrel -> 40 watts minutes
2.376123072093987

And how many food Calories (a food Calorie (with a capital 'C') equals 1000 calories with a small 'c') will he burn to fill a barrel?

2 fathoms water gravity barrel -> Calories
1.3620653895637644

Better eat a Tic-Tac first.

Body Heat

I've seen lots of figures about how much heat the human body produces. You can easily calculate the upper limit based on how much food you eat a day. Say, you eat 2000 Calories a day (again, food Calories with a capital "C" are equal to 1000 calories with a little "c".)

2000 Calories/day -> watts
96.91666666666667

So, your average power and/or heat output is slightly less than a 100-watt bulb. (Note that your heat is radiated over a much larger area so the temperature is much lower.) Many days I could be replaced entirely with a 100-watt bulb and have no discernible effect on the universe.

Microwave Cookery

I'm heating up yummy mustard greens in my microwave, but I don't want to overheat them. I just want to warm them up. If I run my 1100 watt microwave for 30 seconds, how much will their temperature increase? I have a big 27 ounce (mass) can, and I'll assume that their specific heat is about the same as that of water (1 calorie/gram/degC):

1100 W 30 sec / (27 oz 1 calorie/gram/degC) -> degF
18.5350

30 seconds should raise the temperature by no more than 18 degrees Fahrenheit, assuming perfect transfer of microwave energy to heat.

Knowing this, I could see how efficiently my microwave actually heats food. I could heat a quantity of water and measure the temperature change in the water. I'll do that sometime if I can find my good thermometer.

Why is Superman so Lazy?

Superman is always rescuing school buses that are falling off of cliffs, flying to the moon, lifting cars over his head, and generally showing off. So why does he still allow so many accidents to happen? Shouldn't he be able to rescue everybody who has a Volkswagen parked on their chest?

While searching for answers, I found out three interesting things about Superman:

  1. He's 6 feet 3 inches tall.
  2. He weighs 225 pounds.
  3. He gets his strength from being charged up with solar energy.

This is enough information to find some answers. Frink has units called sunpower (the total power radiated by the sun) and sundist (the distance between the earth and the sun.) Thus, we can find the sun's power that strikes an area at the distance of the earth (knowing the surface area of a sphere is 4 pi radius2):

earthpower = sunpower / (4 pi sundist^2)

This is about 1372 watts per square meter. Superman is a pretty big guy--let's say the surface area he can present to the sun is 12 square feet. (This is probably a bit high--it makes him an average of 23 inches wide over his entire height.) This allows Superman to charge up at a power of:

chargerate = earthpower 12 ft^2
chargerate -> watts
1530.1602

Superman thus charges up at the rate of 1530 joules/sec or 1530 watts. At this rate, how long does he have to charge up before he can lift a 2 ton truck over his head? (Knowing energy = mass * height * gravity)

2 ton 7 feet gravity / chargerate -> sec
24.80975

So, charging up for 25 seconds allows him to save one dumb kid who is acting as a speed bump. So his power is huge but not infinite. He couldn't sustain a higher rate (unless he showed off less by lifting the car only a foot or two.) Lifting a truck every 30 seconds or so isn't bad, though. He could be saving a lot more people. So why doesn't he?

Well, we've all seen the movie. He's using his super-powers to pick up chicks. Literally. Superman decides to take a break from saving lives and takes Lois Lane up in the sky for a joyride. So how long does he have to charge up with solar energy to fly himself and Lois Lane (let's say she weighs 135 pounds) up to 15,000 feet?

(225 + 135) pounds 15000 feet gravity / chargerate -> minutes
59.809235

So, Superman has to charge up with solar energy for an hour to cart Lois's fat ass up there. With the same energy, he could have saved over 120 trapped kids. Keep in mind that if Lois didn't weigh so much, he'd have more energy left over to save people. If she would manage to stay off the ham and lose just two pounds, Superman would have enough energy to save another kid's life.

Sure, he's a great guy, and, sure, he's the Defender of Truth, Justice, and the American Way, but can't he find a better use for his super-powers than schlepping some shiksa into the stratosphere? Shovel my walk, he could, in 3 seconds--and me with the sciatica.

Fart Jokes

I received one of those endlessly-forwarded e-mails of dubious but "interesting facts" 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." Hee hee. Cute. (Thanks to Heather May Howard... being unable to easily calculate the veracity of this statement was one of the primary influences that showed how existing programs were too limited and inspired the creation of Frink.) But I didn't believe it and wanted to check it. The Hiroshima bomb had a yield of 12.5 kilotons of TNT, which is a very small bomb by today's standards. How many horsepower would that be?

12.5 kilotons TNT / (6 years + 9 months) -> horsepower
329.26013859711395

Can you produce a 329-horsepower blowtorch of a fart? I doubt it. That's the power produced by a Corvette engine running just at its melting point. A one-second fart with that much power could blow me 1000 feet straight up. To produce that kind of energy, how much food would you have to eat a day?

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

Ummm... can you eat over 5 million Calories a day? (Again, note that these are food Calories with a capital 'c' which are equal to 1000 calories with a small 'c'.) If you were a perfect fart factory, converting food energy into farts with 100% efficiency, and ate a normal 2000 Calories/day, how many years would it really take?

12.5 kilotons TNT / (2000 Calories/day) -> years
17100.488984171367

17,000 years is still a huge underestimate; I don't know how much of your energy actually goes into fart production. Oh well. To continue the calculations, let's guess your butthole has a diameter of 1 inch (no, you go measure it.) Let's also guess that the gas you actually produce in a fart is only 1/10 as combustible as pure natural gas. What would be the velocity of the gas coming out?

12.5 kilotons TNT / natural_gas / (6 years + 9 months) / (pi (.5 in)^2) 10 -> mph
280.1590446203110

Nobody likes sitting next to a 280-mile-per-hour fart-machine. Lesson: Even the smallest atomic bombs are really unbelievably powerful and whoever originally calculated this isn't any fun to be around if they really fart that much.

Fart jokes. Sheesh. If Frink isn't a huge success, it's not because I didn't pander to the Lowest Common Denominator.

Advanced Farting

The above order-of-magnitude estimate shows how far off the mark that the fart e-mail was. Not content with that, I found some medical studies that allowed me to do a more detailed analysis of the average person's available fart energy.

What do you think are the most flammable gases in a fart? Most people think it's methane, but I found some medical studies that disprove this. Most people hardly have any methane in their intestines. For example, one study stated that only 4 out of 11 people had any detectable methane in their intestines! So what's the rest of the gas?

GasPercent by Volume
Nitrogen64%
Carbon Dioxide14%
Hydrogen19%
Methane3.2%
Oxygen0.7%

These studies also note that the average person has 100 milliliters of gas is present in their intestinal tract at any given time. The average person expels 400-2000 ml of gas daily (and I'm not talking about through the mouth and nose.)

Okay, that's almost enough information to figure out available fart energy. Now all we need to know is the energy of combustion of the flammable gases. Of the above, only hydrogen and methane are readily combustible. Looking up their energies of combustion:

GasEnergy of Combustion in kJ/mol
Hydrogen (H2)285.8
Methane (CH4)890.8

Okay, that's plenty enough information to find out how much energy is released in a day of farting! Say you're on the farty end of the scale, and you produce the 2000 ml of gas each day.

Note that the energies above are given in kJ/mol, but we have volumes in milliliters. As you may have learned in chemistry class, a mole of any gas at standard temperature and pressure takes up the same volume. Frink knows this as molarvolume.

The total energy in the hydrogen (keeping in mind that hydrogen makes up 19% of the 2000 ml volume) is given by:

h2energy = 2000 ml / molarvolume mol * 19 percent * 285.8 kJ/mol
4845.3656205695224816 m^2 s^-2 kg (energy)

The combustible hydrogen thus produces 4800 joules (per day.) Now, for the methane, which makes a smaller percentage, but releases more energy per mole:

methaneenergy = 2000 ml / molarvolume mol * 3.2 percent * 890.8 kJ/mol
2543.5537223989278488 m^2 s^-2 kg (energy)

The energy in the combustible methane is thus about 2500 joules (per day), about half the energy produced from the hydrogen. Thus, the grand total of energy produced by combustible farts by a farty person in a day, in food Calories (with a capital C, remember--these are what a physicist would call a kilocalorie) is:

methaneenergy + h2energy -> Calories

Which gives a result of about 1.76 Calories/day of energy available from burning your farts. (About 1.16 Calories from hydrogen, and about 0.60 Calories from methane.) This is out of the 2000 Calories that an average person eats a day. Or, one part in about 1133 of the energy in the food you eat is available in fart energy, (again, for a gassy person.)

Thus, a good estimate to the problem stated above is that a real (gassy) human would need to save their farts for:

12.5 kilotons TNT / ((methaneenergy + h2energy) / day) -> years
1.9379377133697419931e+7

or about 19 million years to make the equivalent of the energy in a (small) atomic bomb! So the estimate given in that e-mail is off by a factor of at least 2.8 million!

Now, you know the true facts about farts. Frink is now complete, and I couldn't be prouder. Umm... thanks, Heather May.

More Incorrect Facts

That e-mail has a higher density of incorrect facts than just about anything I've seen. Below are several more examples.

QE2

"The cruise liner, Queen Elizabeth II, moves only six inches for each gallon of diesel that it burns."

From a page of facts about the QE2, we find that the ship consumes 18 tons of fuel per hour at a service speed of 28 knots. By legislation in many areas, diesel fuel must have a density no higher than .85 kg/liter (if it were watered down, it would be higher.)

18 tons/hour / (28 knot) / (.85 kg/liter) -> feet/gallon
Warning: reciprocal conversion
33.52338503156235

They're very, very wrong. It actually travels about 33.5 feet per gallon, or 157 gallons/mile. They're only off by a factor of 67. Still not great gas mileage, though.

Hamburgers and Cars

The same e-mail states "pound for pound, hamburgers cost more than new cars."

Let's see... let's try with a medium-expensive, light car. A 2001 Corvette Z06 weighs 3,115 pounds and costs $48,055.

(48055 dollars) / (3115 lb) -> dollars/lb
1373/89 (approx. 15.426966292134832)

I know I don't pay $15/lb for hamburger.

Let's try with a light, very cheap car. A quick lookup showed that a 2001 Hyundai Accent costs $10,184 and weighs 2255 pounds. That's still $4.51 a pound. Do you pay that much for hamburger? Maybe a finished hamburger in a good restaurant, certainly not for hamburger. This is deceptive if not outright wrong.

"Get The Provisions..."

By the way, did you ever notice that in the movie Stand By Me that Gordie really gets ripped off for hamburger? Supposedly set in 1960, Gordie buys "a buck and a half of hamburger" which is slapped down in a tiny wrapper that couldn't contain more than 3/4 of a pound. Probably a half pound--it looks like all wrapper. (You estimate it.) Converting to modern prices:

1.50 dollars_1960 / (.75 lb) -> dollars/lb
26.96

Gordie paid a modern equivalent of $27/lb for that hamburger. Perhaps a smarter shopper could have gotten more for Vern's 6 cents.

Typing

The same e-mail says "the longest word that can be typed using only the left hand is 'stewardesses'." Well, Frink is good for doing word stuff too. Using the single word list from the Moby wordlist, the following program finds lots of 12-letter alternatives, and several longer:

infile = "path to words file"

// Pattern which matches words containing only the
// characters under the left hand on a QWERTY keyboard
leftPattern = %r/^[qwertasdfgzxcvb]+$/i

// Pick out words that match the pattern
matches = select[lines[infile], leftPattern]

// Length sort
sort[matches, { |a,b| length[a] <=> length[b] } ]

for [line] matches
   println[length[line] + ": $line"]

(Actually, my original program was only 2 lines, but this is easier to read. The program could be written lots of ways.) Some of the results are:

12: stewardesses
12: desegregates
12: terracewards
12: watercresses
12: extravasated
12: decerebrated
12: gazetteerage
12: desegregated
12: extravagated
12: tessaradecad
12: resegregated
12: reaggregated
12: reverberated
12: reverberates
12: reasseverate
12: aftereffects
13: tesseradecade
13: aftercataract
13: devertebrated
17: redrawerredrawers

I have no idea what that last word means.

Biblical References

So you want to build an ark, do you? And not an Ark of the Covenant, but the boat. How bad was that flood?

The bible is also quite precise in its measurement of the flood. Genesis 7:19-20 states that "And the waters prevailed exceedingly upon the earth; and all the mountains, that were under the whole heaven, were covered. Fifteen cubits upward did the waters prevail; and the mountains were covered."

Okay, so the highest mountains of the earth were covered, plus an extra 15 cubits (approx 27 feet) for good measure. The current measurements for highest mountain is Mt. Everest at 29030.8 feet (according to the highly dubious and utterly non-trustable 2002 Guinness Book of World Records.) I know that Everest is growing slowly, (best estimates are 2.4 inches/year) so we'll discount for that.

depth = 29030 feet + 15 biblicalcubits - (2.4 inches/year 4000 years)

About 28257 feet of water. This was deposited over 40 days. The rainfall was thus:

rainfall = depth / (40 days)

Or about 353 inches/hour, or 29 feet/hour. A good rain around here is about an inch an hour. The very rainiest places on earth like Cherrapunji get about this much rain in a year. (I'm campaigning Colorado farmers to sin a bit more...)

I have much longer analysis of the predicted effects of what such a flood would produce; e-mail me if you want a copy.

E=mc2

Everyone knows Einstein's E=mc2 equation, but to apply it is often very difficult because the units come out so strange. Let's see, I have mass in pounds, and the speed of light is 186,282 miles/second... ummm... what does that come out to? In Frink the calculation becomes transparently simple.

If you took the matter in a teaspoon of water, and converted that to energy, how many gallons of gasoline would that equal?

teaspoon water c^2 -> "gallons gasoline"
3164209.862836101 gallons gasoline

Unbelievable. The energy in a teaspoon of water, if we could extract it, is equal to burning more than 3 million gallons of gasoline.

Days Old

The November 2001 edition of Sky & Telescope magazine has a charming article called "Stellar Guides for Your Birthday" by Jeff A. Farinacci (p. 63), which provides a list of "nearby" stars and their distances in light-years or light-days. This allows you to look at the light coming from a star that was emitted the day you were born. It includes a 28-line BASIC program to calculate how many days old you are on a certain date. As you know by now, the essential calculation can be done in one line of Frink. For example, the bright star Pollux is about 33.7 light-years away (12314 days, based on the Hipparcos satellite's parallax measurement of 96.74 milliarcseconds) and the light it emitted on the day I was born will finally reach earth on the date:

#1969-08-19# + 12314 days
AD 2003-05-07 12:00:00.000 AM (Wed) Mountain Daylight Time

or, to calculate the date directly from the parallax, we can use the following, where au is an astronomical unit (the average distance between the earth and the sun,) and c is the speed of light, the values of which are known to Frink:

#1969-08-19# + au / (96.74 milliarcsec) / c

This gives the same date as the calculation above (May 7, 2003.) I was amazed to find that the universe has conspired to produce a beautiful conjunction on this date. Pollux will form a straight line with the moon and Jupiter in the western sky on that night:

View of the sky on May 7, 2003
(Screenshot courtesy of the wonderful Sky View Café applet)

(Note: The three objects aren't as close together as it may look in this picture. The sky is big.) These screenshots show how it will look at 10 PM Mountain Daylight Time.

Below is a 45-degree chunk of sky looking due west. This will give you a better idea of how the sky will look as you face west. You'll probably see Pollux and Castor quite clearly. Castor is the bright star directly to the right of Pollux. Castor and Pollux will appear to make a horizontal line at this time.

45-degree view of the sky looking West during conjunction

It shouldn't be hard to find. Look west. The moon will be the brightest object in the sky, and Jupiter will be the second-brightest. Follow the line from Jupiter to the moon. Pollux is by far the brightest star along that line (it has a dimmer twin Castor, which will be on the right.) The moon is in the center and Jupiter and Pollux are equal distances on either side of the moon. Follow the wise men. Bring gold. I already have lots of frankincense and myrrh.

Alan's Editorializing: This article also underlines one of the things I am growing to miss in most physical equations and all programs written in other languages... the loss of units. Everything is an unexplained number, and inscrutable conversion factors are strewn liberally throughout. This is exactly the type of thing that Frink was designed to address. For example, a line in the article indicates:

The parallax-to-distance formula is simple: d=1/p, where p is the parallax angle in arcseconds and d is the distance in parsecs (3.26 light-years).

This description is unfortunate. 1 divided by an angle (which is dimensionless) is still dimensionless, not a distance. This is better specified by saying that the formula is distance=(orbital radius)/(parallax angle). Since the parallax angles are specified with respect to Earth's orbital radius, you can write the equation as d = au/p. "au" is an astronomical unit, the average distance between the Earth and the Sun, which is included in Frink's standard data file. Then, p can be specified in any angular units and distance can be automatically converted to light-years or light-days instead of parsecs (or into feet, if you want). As always, Frink makes the units of measurement transparent, and helps to ensure that your calculations make sense. So, using the Hipparcos satellite's measurements for the parallax of the closest star, Proxima Centauri (okay, second-closest, smartypants):

au / (.7723 arcsec) -> lightyears
4.223182420960891

This way, we learn something about the nature of the physical calculation which we can generalize, rather than having an equation that only works with one weird system of measurement. (Although professional astronomers like to use parsecs, I think it's a horrible, intentionally exclusive, geocentric measurement and they're just being difficult. There was even an article in Sky & Telescope a while back which intimated that some astronomers would sneer and giggle at you if you used light-years in a professional publication or speech.) Using our deeper knowledge, we can see how much more accurate the Hipparcos satellite would be if it were put into Jupiter's orbit, or how accurate its instruments need to be to achieve a certain accuracy in distance measurements. We've learned something more general.

Below is the same program as in Sky & Telescope, but more flexible. You can enter the exact second of your birth using any of the date formats that Frink recognizes. You enter the desired age as "12314 days" or "1 billion seconds" or any other duration.

str = input["Enter your birthdate: "]
birthdate = eval["# $str #"] // Parse as a date
str = input["Enter desired age: "]
age = eval[str]
println["You will be $str old on " + (birthdate + age)]

Enter your birthdate: 1969-08-19 04:54 PM Mountain
Enter desired age: 1 billion seconds
You will be 1 billion seconds old on AD 2001-04-27 06:40:40.000 PM (Fri) Mountain Daylight Time

Finding Your Own Star

Now you want your own star, don't you? You might take a look at a list of the brightest stars in the sky at Cosmobrain and look the stars' distances in light-years. This will give you an idea of the bright stars, and give you their Hipparcos catalog numbers. Using the Hipparcos catalog number given in that table, you can look up that star from this Hipparcos search form, find its Geometrical Parallax (field H11, which is probably given in milliarcseconds) and plug that number into the equation shown above. At some point, I may make a Frink Server Page that automates this. But you might learn more just by doing the calculations yourself.

Whether you have a kid who is 8.6 years old (for Sirius which is the brightest star in the sky and 3141 light-days away,) someone turning 11.4 (Procyon), turning 16.8 (Altair) or someone turning 65.1 (Aldebaran,) a star is a great gift and might just start a love of astronomy. I can't promise that the moon and the planets will line up for them, though.

Note that there is some uncertainty in measuring parallaxes, often several percent, and thus the dates are somewhat uncertain, so it's a gift you can give any time around the date. For example, the standard error for the parallax of Pollux is .87 milliarcseconds (parallax error is field H16 in the Hipparcos catalog, specified in milliarcseconds) which leads to an actual date that can vary from around January 17, 2003 to August 27, 2003 -- a range of over 7 months. Just have fun and celebrate your stars when you want!

Model Solar System

This one is fun. I didn't have a grasp of the size difference between the Earth and the Moon so I wanted to make a little scale model in my home. It would be best to use spheres of the appropriate sizes, but I don't have that many balls. Instead, I decided to cut circles out of paper. My deciding dimension was the size of the piece of paper I used to cut out the pieces. I could only get a 7-inch diameter circle for the Earth (3.5-inch radius), so this defined my scale, which I saved in a variable for use in later calculations:

scale = earthradius / (3.5 inches)
7.166851856017998E7

The standard data file contains information about the dimensions of the planets, earthradius being one of those. Now how big should the Moon circle be?

moonradius / scale -> inches
0.9547455176283174

Okay, using my rolling ruler, I cut out a circle with radius 0.95 inches (diameter 1.9 inches). There's the Moon. It's interesting to see the difference in size between the Earth and Moon:

Earth-Moon Sizes

Now, to place them properly... how far away should they be at that scale?

moondist / scale -> feet
17.59705489913335

Okay, stick the Earth to one wall, and then measure a distance 17.5 feet away, and stick the Moon to that. Installed!

From each vantage point, you can see how big the other actually looks from that distance. Standing by the Earth, you can see how big the Moon looks (you've seen the Moon, but it's a smaller angle than you might guess... the Moon really doesn't take up much sky, only about half a degree in diameter.) You can verify this by holding a fingernail out at arm's length, comparing it to the size of your Moon model, and then going outside and doing the same to the Moon, if you can see it.

Now walk over to the Moon and look at the Earth. It would be pretty big! Earth would appear about 3.66 times wider in diameter (in Frink notation, earthradius/moonradius, or 13.4 times larger in area ( pi earthradius^2 / (pi moonradius^2) ).

Just to verify, I wanted to make sure that the visible angles in my model match real life. The visual angle of an object which does not subtend a large angle can be expressed as angle = width / distance. The angle normally comes out in radians if width and distance are in the same units, but this is Frink. You can get the answer out in degrees, or arcminutes if that's where your heart lies:

1.9 inches / (17.5 feet) -> arcminutes
31.10342316424469

Yep, that's just the right number of arcminutes. From Earth, the Sun and the Moon both appear just over half a degree in diameter, or about 32 arcminutes (an arcminute is 1/60 of a degree). It all works out. Note that in the standard data file, radians are dimensionless units (a radian is defined as "1".) This is because radians are dimensionless units, but you can convert values in radians to other angular units.

By the way, a more accurate angular formula that is valid for large and small angles subtended by a sphere with a given radius at a specified distance is:

2 arcsin[radius/(radius + distance)] -> degrees

Note that inverse trigonometric functions (arcsin, arccos, arctan) have their output in radians. This is easily converted to whatever angular units you want, as above. You don't see that the output is in radians (if you use the standard data file) because radians are dimensionless numbers. You just gotta be a bit careful here, or make the minor change in your units file to make radians a fundamental dimension. (Read the documentation in the units file... the units file is otherwise radians-correct.)

Nostalgic Digression: I remember a cool black-and-white movie we saw in my Kindergarten class about making a scale model of the solar system. It involved a huge vertical circular sign representing the Sun that somebody had built for the film and a car driving a measured distance away to look at it. (A bit confusing, though. I remember them saying "here we are, 93 million miles away!" and they were still in the same park.) Good stuff, and I'm glad I finally made my own model. It helps me understand how cool the Apollo missions were. But my enthusiasm is tempered by the fact that I'm just now figuring out stuff they tried to teach me in Kindergarten.

Now go make your own model. Pick your own scale to fit your surroundings and materials. It's fun. In my model, Jupiter would have to be a sphere 6.59 feet in diameter, placed about 5.5 miles away right now. I'll have to get a bigger place.

Now that you know how to calculate the size in Frink yourself, I've gone ahead and built a Frink Server Page that lets you design your own!

Homework: In your model, figure and/or plot the following:

Saving Hundreds of Millions of Dollars

"The MCO [Mars Climate Orbiter] MIB [Mishap Investigation Board] has determined that the root cause for the loss of the MCO spacecraft was the failure to use metric units in the coding of a ground software file, "Small Forces," used in trajectory models. Specifically, thruster performance data in English units instead of metric units was used in the software application code titled SM_FORCES (small forces)."
--Mars Climate Orbiter Mishap Investigation Board, Phase I Report

This is not to take away from the designers of a wonderfully complex spacecraft that can travel to Mars; that's an incredibly difficult problem, and I couldn't do it. However, this is just the type of error that Frink was designed to help avoid, and because I make these type of errors a lot, I've designed this tool to help me. Frink tracks units through all calculations and makes conversions between them transparent. This is why I'm working toward making Frink a feasible solution for calculations of this type.

Update: I received the following from Peter Norvig:

"I ran across Frink, and as a member of the MCO review board, I appreciate your efforts. Note, however that more than just language support is necessary. First, you'd have to have conventions on data I/O -- the misinterpreted data was from a file, not from another function in the program. Also, there was an issue of software reuse -- the errant portion of the system had been used before on a previous mission, and in that case it was used in a non-critical, non-navigational way. It was not properly reviewed because the team did not realize that in MCO it became critical."

The points above are well-taken. Proper parsing of units can be easily achieved in Frink with a simple, appropriate comment in the data file. unittable.frink shows how Frink can parse a file containing units of measure. All that is needed is to add a single comment to the data file that contains the units of measure of each data column. Frink then reads each column with the appropriate units of measure and scale, using any units of measure that Frink knows about as input, and Frink parses them and works with them properly. See its sample data file.

Frink and its eval[x] function could always trivially handle the case where each number in a file has its units of measure specified with the number, e.g. "3.5 km/s". That's literally zero effort to parse with Frink, and is slightly less compact, but that's a very small price to pay, compared to mission failure.

Of course, there's no simple solution for someone completely not reading the specification documents, but the fact that a file completely omits any units of measure would be a good warning flag to double-check your sources, I'm growing to fear and avoid any system that treats every physical measurement as an unexplained dimensionless number, as most programming languages have for the past few decades. We can do better.

Changing Syntax

Keep in mind that the syntax represented in this document will change as Frink evolves. The current parser just makes it easier for me to test certain features, and I intentionally refuse to spend a lot of time on it at this point. The internals of the language should be the first concern, and the external representation is free to change. Frink should be equally usable whether you want to load and save your data in the current mathematical notation, in a (LISP-like) prefix notation, in a (HP calculator-like) Reverse-Polish postfix notation, from a visually-based GUI, in MathML, TeX, XML, or whatever other flavor-of-the-month format the kids are crazy about these days. The internals of the language are intentionally agnostic on this point, as they should be in a flexible design.

On the other hand, I always want to keep Frink easy to use and transparent for the quick calculations as it is now. Turning it into a language that forces an encumbering programming paradigm is out of the question.

Alan's Unsolicited Advice: Anytime you're involved with a project where you hear people saying "we want to use 'X' technology" before they've even thought about representing the problem they're trying to solve, look up for the cloud of doom, which floats nigh. It's like hiring a carpenter, who, before he knows what you want to make, insists on using mortice-and-tenon joints.

Acknowledgements

"This product includes software developed by the Apache Software Foundation (http://www.apache.org/)."

They made me say that. The included part is the nifty ORO regular expression library. I hacked it significantly so that it would compile and run on a Java 1.1 platform (so that Frink can be used on small devices running PersonalJava 1.1 and run in the JVM in just about any browser out there.)

Frink's parser is built using the wonderful JFlex and JavaCUP.

Donate to Frink

If you've gotten this far, hopefully you've seen something you liked. If you find Frink useful, I'd appreciate if you took a look at some of the ways you can donate to Frink's development. Thanks!

Please send comments or questions to Alan Eliasen.

Back to Alan's Home Server