Frink

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

About Frink

Frink is a practical calculating tool and programming language designed to help us all to better understand the world around us, to help us get calculations right without getting bogged down in the mechanics, 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 make physical calculations easily, to mix units of measure transparently, and ensures that the answers come out right.

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

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 write command-line Frink programs. (If you do want to write command-line programs, 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. 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 is not recommended due to a horrible bug in Sun's Swing implementation (Bug 4425177, which they have astonishingly refused to fix!) which make it impossible to display output paragraphs longer than 32000 characters. This is despite many people (including me) filing bug reports including a very simple and obvious fix that uses no more memory (and would even allow memory reductions if they wanted.) If you have a smarter Swing implementation than Sun's, it should work just fine.

Note: Sun informed me that the 32000 character limitation appears to be removed in Sun's Java 1.5.0 beta 1, and this appears to be the case. There's a significant performance problem with long paragraphs, though; sometimes it takes several minutes (!) for Swing to display a long paragraph, and every additional output or resize of the window causes this delay.

The Swing version allows mixed fonts and colors, but has fewer features than the AWT interface. Until Sun fixes the aforementioned bug, there's not much point in spending more time on it.

Swing GUI Screenshot

AWT User Interface

The AWT user interface has several modes. The two-line conversion mode and programming mode are shown below. The AWT mode does more than the Swing mode because I spend more time optimizing the AWT mode for use on handheld devices. 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, 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 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 Swing GUI, (shown above under Java Web Start,) use:
javaw -cp frink.jar frink.gui.SwingInteractivePanel [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 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.

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]]

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 very rough at this moment, but it has some syntax highlighting and automatic indenting. 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

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.

setEngineering[true]

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

Simple 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

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.

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.

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.

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.

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 above 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.

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" 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.

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.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.removeItem["two"]
array
now contains ["one", "three"]

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 bit