Frink

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

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

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

Frink follows a rapid release schedule and is updated often. That doesn't mean that old programs will be invalidated, but that new, useful features and optimizations are added all the time.

Keep an eye on the What's New page to see new features and keep abreast of its developments.

While that page is the most detailed and constantly-updated source of information about changes in Frink, I also announce new features on Twitter at @frinklang. And if you want to follow Alan's personal ramblings for some reason, those are at @aeliasen.

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

General Frink list (low-traffic):

To receive periodic notifications of general interest about the Frink language, please join the Frink mailing list. (Hosted by Yahoo! Groups. Link opens in new window.) This is a lower-traffic list, mostly for announcements.

Subscribe to Frink Enter Email Address:

Detailed Frink list (higher-traffic):

For detailed discussions about particular programs, programming help, algorithms, and details of the Frink language, please join the Frink-discuss mailing list. (Hosted by Yahoo! Groups. Link opens in new window.) This is the appropriate place for posting programming questions and will probably be a higher-traffic list.

Subscribe to Frink-discuss Enter Email Address:

This list is intended to discuss problems in math, science, and physics and explore solutions using the Frink language.

Frink Science mailing list. (Hosted by Yahoo! Groups. Link opens in new window.)

Subscribe to FrinkScience Enter Email Address:

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

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

Quick Start: On many platforms, if you already have Java installed, you can start Frink in the GUI mode by simply downloading and double-clicking the frink.jar file. For more startup options, see the Downloading Frink section.

Another method of installation requires Java Web Start, which is installed with most versions of Java. Using Java Web Start is used to be 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

If you don't have a recent version of Java, you can get it from Sun. (Link opens in new window.) (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. Warning: Most major browsers have now disallowed Java plugins to run in the browser. However, if you have a Java Virtual Machine installed, you probably still have Java Web Start installed, which you need to invoke from the command-line with the command javaws . In Fedora, you can make sure this is installed by installing the icedtea-web package from your package manager, e.g. as root, typing dnf install icedtea-web and then one of the commands below. In Debian-like environments, you may install this by installing the icedtea-netx package, e.g. as root, typing apt-get install icedtea-netx and then one of the commands below. Warning: If you're using Java version 7u51 or later, they silently and incompatibly decided to change default security settings so you'll need to open the Java Control Panel to allow Frink to run. Otherwise you will see a dialog that says something like "Application blocked by security settings" or "Your security settings have blocked a self-signed application from running." (This silent change was made after 12+ years of the aforementioned method working fine.) The best way to allow Frink to run is to follow the instructions listed here and add http://futureboy.us to the exceptions list in step 7. Note: As always, Java's instructions and installer are terrible, and the Java Control panel on Windows may actually be under your Start menu as Java | Configure Java , or under your Windows Control Panel, or if you start your Control Panel and don't see it, Java's control panel will be hidden under "32-bit Control Panel." And sometimes you'll have multiple versions of Java installed and the one that gets started isn't the latest version. I had lots of problems until I manually uninstalled all the versions of Java on the Windows machine, reinstalled the latest version, and uninstalled Frink and reinstalled it. Sorry about that. Windows and Java integration is terrible. (The icedtea-web package for Fedora and other installations contains a vastly better implementation of Java Web Start.) Click one of the options below to install Frink: (see the screenshots below): Swing Interface Prettier. Requires Java 1.5.0 or later. Note: If your browser no longer supports Java in the browser, you can probably install this from the command-line by typing: javaws https://futureboy.us/frinkjar/frink.jnlp

Swing Interface with standard libraries. This is a version of Frink that contains a variety of standard libraries and useful programs. It's a larger download, but the standard libraries change somewhat infrequently and should only get downloaded when changes are made. Note: If your browser no longer supports Java in the browser, you can probably install this from the command-line by typing: javaws https://futureboy.us/frinkjar/frinkwithlibs.jnlp

AWT Interface - Not as pretty as the Swing mode, but will run on older JVMs. Note: If your browser no longer supports Java in the browser, you can probably install this from the command-line by typing: javaws https://futureboy.us/frinkjar/frinkawt.jnlp

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 per year 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.

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.

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.

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

If the applet doesn't work for you, try the web interface. 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.

Quick Start: On many platforms, if you already have Java installed, you can start Frink in the GUI mode by simply downloading and double-clicking the frink.jar file.

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, like Android, 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:

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

Download frink.jar.

(Double-clicking the file you just downloaded might start Frink in GUI mode, depending on your operating system. If that's not enough, read on.)

See the Running Frink section below for directions for starting full Frink programs, running from the command-line, etc.

Quick Start: On many platforms, if you already have Java installed, you can start Frink in the GUI mode by simply downloading and 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!

(Windows batch file) frink.bat

(Linux/Unix shell script) frink

(Note that the Linux/Unix shell script above has the option to run with the rlwrap program, which gives you the ability to use the up/down arrows to repeat and edit calculations, and even perform unit and function completion! See the instructions inside that file for configuring rlwrap, and downloading the optional associated files: unitnames.txt and functionnames.txt)

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.

The most general way to start Frink is to launch the frink.gui.FrinkStarter class:

java -cp frink.jar frink.gui.FrinkStarter [options]

(The above starter scripts use this class. Look at them first.) By default, this starts in text mode but allows many command-line options to start in different modes:

Switch Description --swing Starts in Swing GUI mode --gui Starts in Swing GUI mode --awt Starts in AWT GUI mode --fullscreen Starts fullscreen --prog Starts in programming mode with a blank program -open filename Starts the specified filename in programming mode (this option is passed by double-clicking a file in Windows if you have file associations set up.)

Other start options are listed below, if you want to use them. I'd suggest using one of the scripts above and modifying it.

To run the jar file in text mode (only), 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]

The Swing 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 AWT GUI, which gives access to several modes, including programming mode, use:

javaw -cp frink.jar frink.gui.InteractivePanel [options]

To run the jar file and start the AWT GUI in programming mode, use:

javaw -cp frink.jar frink.gui.ProgrammingPanel [filename]

To run the jar file and start the Swing GUI in programming mode, use:

javaw -cp frink.jar frink.gui.SwingProgrammingPanel [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.

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

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:

Switch Description -f filename Allows you to specify multiple Frink source files to load and run. Multiple -f filename options may be specified. If this option is specified, the specified file 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. For example, to load your own definitions from mydefs.frink before loading the main program main.frink , you may do something like: frink -f mydefs.frink main.frink args -k Remain in interactive mode after loading files or parsing command-line arguments. This is very useful if you want to load definitions from one or more files and then go into an interactive session. -u filename Specify 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. The sample program encodings.frink also demonstrates how to list all of the encodings available on your system (and their aliases.) 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.) --sandbox Enables 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-errors Ignores 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 arguments after the name of the program to be executed are passed to the program as an array called ARGS .

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.

Experimental, Unsupported executable for Windows: frinkx.exe.

(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

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:

Android - All of Frink's functionality, including graphics, is available on the Android mobile phone platform. See the Frink on Android page for more information on using Frink on Android. As Android is a stable, complete, widely-available, well-designed, multi-platform and multi-vendor environment, it will probably be the primary platform for Frink on handheld devices in the future. Other proprietary platforms that require extensive porting and packaging for every phone model may go away.

- All of Frink's functionality, including graphics, is available on the Android mobile phone platform. See the Frink on Android page for more information on using Frink on Android. As Android is a stable, complete, widely-available, well-designed, multi-platform and multi-vendor environment, it will probably be the primary platform for Frink on handheld devices in the future. Other proprietary platforms that require extensive porting and packaging for every phone model may go away. Sony Ericsson P800, P802 or P900, and Motorola A920 or A925 (and perhaps other Symbian 7.0 devices with the UIQ2 user interface.)

(and perhaps other Symbian 7.0 devices with the UIQ2 user interface.) Nokia Communicator 92xx (Nokia 9210, 9210i, and 9290) (and perhaps other Symbian 6.0 devices with the "Crystal" (wide-screen) user interface). You need 3 to 4 megabytes of free memory to run Frink.

(and perhaps other Symbian 6.0 devices with the "Crystal" (wide-screen) user interface). You need 3 to 4 megabytes of free memory to run Frink. Sharp Zaurus (despite the filename, this isn't ARM-processor specific--it's pure Java, and might work on other platforms that use the .ipk package format.) Note: I need help testing and improving this installation package. Please contact Alan Eliasen if you have experience with Zaurus installer packages. Hint for helpers: an .ipk file is just a .tar.gz file, so you can open it up and poke around, but I don't have a Zaurus to test on.

(despite the filename, this isn't ARM-processor specific--it's pure Java, and might work on other platforms that use the package format.) Note: I need help testing and improving this installation package. Please contact Alan Eliasen if you have experience with Zaurus installer packages. an file is just a file, so you can open it up and poke around, but I don't have a Zaurus to test on. Experimental: Nokia 9300, Nokia 9500 Communicator (and perhaps other Symbian Series 80 devices.) I need help testing this one. The Series 80 devices evidently crash when you try to add menubars, so some functions are impossible to access. This is a jar file customized for Series 80, and not an .sis installer.

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

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.

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.

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

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

Option Description --width int Sets the width of the window in pixels. --height int Sets the height of the window in pixels. --fontsize int Sets the font size in points.

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

Java 8 contains my algorithmic improvements to make large integers work much more quickly for multiplication, base conversion, etc. (It only took the Java people 11+ years to import my patches.)

For long-running programs, if you're using Sun's Java Virtual Machine (JVM), use the -server command-line switch to the java executable. This starts the Server VM which optimizes more aggressively and often improves performance of long-running programs by a factor of 2, but at the expense of increased start-up time. Note that the server VM may not be available if you just downloaded the Java Runtime Environment (JRE), and not the full Java Software Development Kit (SDK).

command-line switch to the executable. This starts the Server VM which optimizes more aggressively and often improves performance of long-running programs by a factor of 2, but at the expense of increased start-up time. Note that the server VM may not be available if you just downloaded the Java Runtime Environment (JRE), and not the full Java Software Development Kit (SDK). If you're not starting long-running programs, and want the fastest start-up time, don't use the Server VM.

Like all Java programs, Frink can often run much faster if you allow the JVM to use more memory at startup, leading to less-frequent garbage collection. In Sun's implementation, this is achieved by passing the options -Xmx <size> (for maximum Java heap size) and -Xms <size> (for initial Java heap size) to the java executable. The size arguments are something like 256M for 256 megabytes. Note that this is at the expense of other processes running on your system, and should be used sparingly because allocating too much memory may cause your system to swap excessively or run out of memory if set too high.

(for maximum Java heap size) and (for initial Java heap size) to the executable. The arguments are something like for 256 megabytes. Note that this is at the expense of other processes running on your system, and should be used sparingly because allocating too much memory may cause your system to swap excessively or run out of memory if set too high. (Probably obsolete) If you're doing mostly large integer work (factoring, primality testing, other number theory,) where most of the runtime is spent in mathematical operations on very large integers, the Kaffe Virtual Machine compiled with the incredibly fast GMP numerical libraries works literally thousands of times faster than Sun's VM and its horribly naïve algorithms. Warning: Make sure that GMP is compiled with the configure option --enable-alloca=malloc-reentrant or you'll blow out the stack and crash with very large integers. Warning: As of the 2004-07-18 release of Kaffe, you must now explicitly pass the -Xnative-big-math argument when running Kaffe in order to use the GMP libraries.

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.

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:

Quantity Fundamental Unit Name 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 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 . As of the 2018-12-11 release, if a number with a decimal point has the "exact exponent" indicator ee , it is turned into an exact rational number or integer. For example, the new exact SI value for Avogadro's number 6.02214076ee23 will become an exact integer. The exponents can also be negative, which will usually lead to an exact rational number, such as 6ee-1 which produces the exact rational number 3/5 (exactly 0.6) . 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.1, 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.

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:

1

1000000

1_000_000 (also one million, just maybe more readable.)

(also one million, just maybe more readable.) 1E6 (1.0x10 6 , or approximately a million (floating-point))

(1.0x10 , or approximately a million (floating-point)) 1EE6 (1x10 6 , or exactly a million (integer))

(1x10 , or exactly a million (integer)) million

1 million

24.5E-10 (24.5 x 10 -10 )

(24.5 x 10 ) eighty

four score + seven

1 (a dimensionless exact integer)

(a dimensionless exact integer) 1.0 (a floating-point number)

(a floating-point number) 1. (also a floating-point number)

(also a floating-point number) 1/3 (a rational number, preserved as a fraction)

(a rational number, preserved as a fraction) 1 quadrillion

gallon

1 gallon

56 gallon

56 gallons

foot

54.2 feet

furlong

hogshead

2 USD ("USD" is the ISO-4217 currency code for the U.S. Dollar)

("USD" is the ISO-4217 currency code for the U.S. Dollar) 2 dollars (for now, shorthand for the U.S. dollar)

(for now, shorthand for the U.S. dollar) 16 tons

6 ounces

1 gram

8 milligrams (most common prefixes are allowed.)

(most common prefixes are allowed.) 8 mg (abbreviations of most prefixes and units are also allowed)

(abbreviations of most prefixes and units are also allowed) 7 kilowatts

7 kW

1.21 gigawatts

1.21 GW

9 seconds

9 sec

9 s

1/24 day

100001000101111111101101\\2 (a number in base 2)

(a number in base 2) 1000_0100_0101_1111_1110_1101\\2 (a number in base 2 with underscores for readability)

(a number in base 2 with underscores for readability) 845FED\\16 (a number in base 16... bases from 2 to 36 are allowed)

(a number in base 16... bases from 2 to 36 are allowed) 845fed\\16 (a number in base 16)

(a number in base 16) 845_fed\\16 (a number in base 16 with underscores for readability)

(a number in base 16 with underscores for readability) 0x845fed (Common hexadecimal notation)

(Common hexadecimal notation) 0x845FED (Common hexadecimal notation)

(Common hexadecimal notation) 0xFEED_FACE (Hexadecimal with underscores for readability)

(Hexadecimal with underscores for readability) 0b100001000101111111101101 (Common binary notation)

(Common binary notation) 0b1000_0100_0101_1111_1110_1101 (Binary with underscores for readability)

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.

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 a single string.

Note: As of the 2016-07-06 release, the double-question-mark operator now returns a single string with newlines separating each entry, instead of a list of 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.

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.

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.

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

Example Description 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. 10⁻²³ exponentiation using Unicode superscript characters. This is equivalent to 10^-23 . See the Unicode Operators section below for more details. 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 -> cups Sample recipe conversion ("sugar" is a density for now.) 1/4 mile / (4.23 seconds) -> miles/hour Dragster average speed. Note the parentheses required because space is multiplication which has same precedence as division. 329 mph / (4.23 seconds) -> gravity Dragster average acceleration in g's. foot conforms meters Conformance 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 feet Equals 3 (feet^2) or, more simply, 3 feet^2 . Square squares the unit on its immediate right-hand side. 3 sq feet Same as square 3 cubic feet Equals 3 (feet^3) or, more simply, 3 feet^3 . Cubic cubes the unit on its immediate right-hand side. 3 cu feet Same as cubic 3 feet squared Equals (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 cubed Equals (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.

The implementation of factorials is subtle and important enough to warrant a few notes. The factorial operator ! follows after an expression and has a precedence above exponentiation, following normal mathematical precedence rules. Yeah, when I say 6! or n! or (n-m)! , I'm not just being super-enthusiastic about that number. It's a common mathematical operator.

Reminder: the factorial of a non-negative integer n! is the product of all the numbers from 1 to n . For example, the factorial 6! is equal to 1*2*3*4*5*6=720 . These grow rapidly and become hard to calculate, but Frink calculates them exactly.

Here are some notes about the implementation of factorials:

Factorials are calculated once and cached in memory so further recalculation is fast.

There is a limit to the size of factorials that gets cached in memory. Currently this limit is 10000! . Numbers larger than this will not be cached, but re-calculated on demand.

. Numbers larger than this will not be cached, but re-calculated on demand. When calculating a factorial within the caching limit, say, 5000! , all of the factorials smaller than this will get calculated and cached in memory.

, all of the factorials smaller than this will get calculated and cached in memory. As of the 2017-04-04 release, calculations of huge factorials larger than the cache limit 10000! are now calculated by a binary splitting algorithm which makes them significantly faster on Java 1.8 and later. (Did you know that Java 1.8's BigInteger calculations got drastically faster because Frink's internal algorithms were contributed to it?)

are now calculated by a binary splitting algorithm which makes them significantly faster on Java 1.8 and later. (Did you know that Java 1.8's BigInteger calculations got drastically faster because Frink's internal algorithms were contributed to it?) As of the 2017-04-04 release, functions that calculate binomial coefficients like binomial[ m,n ] are more efficient because of the use of binary splitting algorithms, especially for large numbers. Note: binomial[ m,n ] 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.

are more efficient because of the use of binary splitting algorithms, especially for large numbers. The function factorialRatio[a, b] allows efficient calculation of the ratio of two factorials a! / b! , using a binary splitting algorithm.

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)

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

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

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.

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"

Name Description array Value must be an array. boolean Value must be a boolean value true or false (and not just a type that can be coerced to boolean; see the Truth section.) date Value must be a date/time. dict Value must be a dictionary. set Value must be a set. regexp Value must be a regular expression. subst Value must be a substitution (search-and-replace) expression. string Value must be a string. unit Value 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.

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.

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.



positive[x] := x > 0





var x is positive = 1

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

Function Definition 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"]

Ha ha...just kidding. There are no global variables in Frink. However, if you need to access some sort of "global" values from anywhere in your program, without passing them explicitly to each function, you can simulate it with class-level variables in a class that you define. These are defined using the class var keywords, and are similar to static class-level variables in languages like C++ and Java.

If you want to use a "global" variable in only a few functions, you can encapsulate those functions into a class .

For samples of class-level variables and how to access them, see classtest.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, for example: \u210e . In addition, a Unicode character can be specified with anywhere from one to six hexadecimal digits by placing the digits in brackets, for example: \u{FF} or \u{1f638} (this is the Unicode character "GRINNING CAT FACE WITH SMILING EYES". So, yes, you can have kitty faces as variable names!) 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, "other symbols" 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

\u210e := h

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

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.

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

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

Frink allows C/C++/Java-style comments:



c = 1



a = 1

b = 2

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

You may define multiple functions with the same name but different number of arguments.

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.

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)

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.

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.

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.

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

Alternatively, there is a conditional operator (sometimes called the "ternary operator") that can be placed inside an expression.

condition ? trueClause : falseClause

If condition evaluates to true , then the result is trueClause , otherwise the result is falseClause .

The ternary conditional operator can be used in any expression:

println["I have $numCats " + ( numCats == 1 ? "cat" : "cats" ) + "."]

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:

True False true false Any non-empty string The 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.

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 a labeled next statement to jump to the next iteration of a higher loop. See the for loop section for an example.

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.

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

i=0

until i>1000000

{

i = i+1

}



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

i=0

until i>1000000

i = i+1



You can break out of an until loop using break and next statements exactly as specified in the while loop documentation above.

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...while loop. You can use a labeled next statement to jump to the next iteration of a higher loop. See the for loop section for an example.

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.

The do...until loop is much like the do...while loop, the only difference being that the body of the loop repeats until the condition becomes true.

i=0

do

{

i = i+1

} until 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

until i == 1000

You can break out of a do..until loop using break and next statements exactly as specified in the while loop documentation above.

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

The step is required to be specified for any range in which the limits are not dimensionless integers. For example:

for i = 0 miles to 1 mile step 1 foot

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 boundaries of a loop may also be boolean values:

for x = false to true

Other orderings are allowed such as true to false or true to true (the latter only goes through the loop once.)

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

a = ["zero", "one", "two"]

for x = a

println[x]

zero

one

two



The rangeOf[array] function returns an enumeration of all the indices in an array.

a = ["zero", "one", "two"]

for i = rangeOf[a]

println["index $i contains " + a@i]

index 0 contains zero

index 1 contains one

index 2 contains two



You can also use the for loop to iterate over the contents of Java objects. See the Iterating over Java Collections section of the documentation for more.

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 a labeled next statement to jump to the next iteration of a higher loop:

OUTERLOOP:

for i = 1 to 1000

{

for j = i to 1001

{

if i+j > 1000

next OUTERLOOP

}

}

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

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]

You can create an more flexible enumerating expression that does the same as the above by using the format: new range[1, 10] or new range[1, 10, 2] formats. Use this if you're going to assign to variables or use the range symbolically.

For programs that require nested for loops for which it's not known in advance how many nested loops will be needed, the multifor construct allows multiple loops to be created simply:

multifor [a, b, c] = [1 to 2, 1 to 3, 1 to 5]

println["$a $b $c"]

An similar (and technically better in almost all ways) way of writing the loop above would be to create new range objects:

bounds = [new range[1,2], new range[1,3], new range[1,5]]

multifor [a, b, c] = bounds

println["$a $b $c"]

A typical idiom for creating a multi-loop is to use the makeArray function to create array bounds. For example, the following creates in effect 8 nested loops, each running from 1 to 2. The results are assigned as an array to the variable d .

upper = 2

bounds = makeArray[[8], new range[1,upper]]

multifor d = bounds

println[d]

As of the 2012-09-04 release, the bounds of one loop can now depend on the bounds of the loops to its left, for example:

multifor [f,g] = [new range[1,3], new range[f+1,3]]

println["$f $g"]

You can use the next statement to prematurely jump to the next iteration of a multifor loop. You can use a labeled next statement to jump to the next iteration of a higher loop. See the for loop section for an example.

There's a version of the next statement that jumps to a specified level of a multifor loop. The leftmost/highest level is level 0, and the next levels increment to the right. The loop must be labeled and the index to jump to follows the label in the next statement. For example next LOOP i

The program multinexttest.frink demonstrates the use of this construct.

A Gray code is a sequence in which only one element of the sequence changes at a time. The best-known Gray code is called the "binary reflected Gray code" but a large number of different Gray codes are possible. You can iterate through finite and infinite Gray code sequences using the grayCode function.

The grayCode functions return an enumerating expression that returns an array of values.

Finite-length (n, k) Gray Code: An (n, k) Gray Code is a Gray code with n states per element and k elements. For example, the typical binary reflected Gray code is a (2, k) code. This is generated by calling grayCode[n, k] where n and k are integers. For example to generate a binary Gray code with 3 digits:

for c = grayCode[2, 3]

println[c]



[0, 0, 0]

[0, 0, 1]

[0, 1, 1]

[0, 1, 0]

[1, 1, 0]

[1, 1, 1]

[1, 0, 1]

[1, 0, 0]



Finite-length Gray Code with arbitrary states: You can generate a finite-length Gray code where each element can have an arbitrary set of states, specified as an array of arrays:

args = [ ["A", "B"], [1, 3, 5] ]

for c = grayCode[args]

println[c]



[A, 1]

[A, 3]

[A, 5]

[B, 5]

[B, 3]

[B, 1]



Infinite-length Gray Code: You can generate a infinite-length Gray code where each element has the same number of states. This is different from the other Gray code examples because the least-significant element is returned as element 0 in the array, which gets longer as more elements are needed. For example, to generate a binary reflected Gray code, use grayCode[2] :

for c = grayCode[2]

println[c]



[0]

[1]

[1, 1]

[0, 1]

[0, 1, 1]

[1, 1, 1]

[1, 0, 1]

[0, 0, 1]

[0, 0, 1, 1]

[1, 0, 1, 1]

[1, 1, 1, 1]

[0, 1, 1, 1]

...



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 .

There is also a three-argument version, eval[expression, rethrows, hidesLocals] where the hidesLocal argument is a boolean flag indicating if we want to hide local variables before evaluation.

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]

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

or use one of the makeArray functions described in the next section to create arrays and initialize them.

One-dimensional or multi-dimensional "rectangular" arrays can be constructed with the makeArray[dims, initialValue] function where dims is an array of integers indicating the dimensions of the array, and initialValue is the initial value to set in each cell. Multi-dimensional arrays are implemented as arrays of arrays.

Create a 1-dimensional array with 10 elements, with each element initialized to 0:

a = makeArray[[10], 0]

[0,0,0,0,0,0,0,0,0,0]

Create a 2-dimensional array with size 3x4, initialized to 0:

a = makeArray[[3,4], 0]

[[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]

If called without an initial value, the array is initialized as sparse and compact to conserve memory, but you may get errors if reading elements you haven't initialized.

a = makeArray[[3,4]]

[[], [], []]

Array elements can still be assigned to, and the appropriate rows will get extended to fit them:

a@2@2 = 0

[[], [], [undef, undef, 0]]

Note: All arrays can still be automatically extended by assigning to a value outside their currently-defined range. Creating an array with the makeArray methods does not prevent arrays from being extended nor prevent them from becoming non-rectangular. It is never necessary to pre-allocate a one-dimensional array of a specific size, as all arrays will automatically resize on assignment.

If initialValue is a function (it can be an Anonymous Function), then that function is called to provide the value for each cell. The function must have the same number of arguments as the dimensions of the array, and it is passed the indices of the cell. For example, element [0,0] of the array will be passed the arguments [0,0].

The following makes a cell with each value initialized to twice its index.

makeArray[[10], {|x| 2x}]

[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

A two-dimensional array requires a function with 2 arguments. The following builds a multiplication table:

makeArray[[5,5], {|a,b| a*b}]

[[0, 0, 0, 0, 0], [0, 1, 2, 3, 4], [0, 2, 4, 6, 8], [0, 3, 6, 9, 12], [0, 4, 8, 12, 16]]

If the makeArray function needs additional data, you can use the three-argument version makeArray[dimensions, function, data] which passes an arbitrary data expression to function as a last argument. For example, the following creates a "diagonal" matrix with the specified values passed in as an array as the diagonal entries:

array = [1,2,3]

d = length[array]

m = makeArray[[d,d], {|a,b,data| a==b ? data@a : 0}, array]

[[1, 0, 0], [0, 2, 0], [0, 0, 3]]

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.

[3,2,1]

Arrays can be automatically extended and used as a stack by using the methods:

array .push[ x ] : Appends an item to the end of the array (top of stack).

: Appends an item to the end of the array (top of stack). array .pop[] : Removes an item from the end of the array (top of stack) and returns it.

: Removes an item from the end of the array (top of stack) and returns it. array .peek[] : Returns the item from the end of the array (top of stack) without removing it from the array/stack. If the array is empty, this will return undef .

: Returns the item from the end of the array (top of stack) without removing it from the array/stack. If the array is empty, this will return . array .isEmpty[] : returns true if the array is empty, false othewise.

: returns if the array is empty, othewise. array .pushFirst[ x ] : pushes an item onto the beginning of an array.

: pushes an item onto the beginning of an array. array .popFirst[ x ] : removes an item from the beginning of an array and returns it.

: removes an item from the beginning of an array and returns it. array.pushAll[array1] : pushes all the items from array1 onto the end of array , modifying it in-place. You can also use the concat[a, b] function if you do not want to modify in-place.

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 array.pushFirst[x] and a.popFirst[] methods.

The contents of one array can be appended to another array using the array.pushAll[array1] method, which modifies the original array in place:

a = [1, 2, 3]

b = [4, 5, 6]

a.pushAll[b]

[1, 2, 3, 4, 5, 6]



You can also use the concat[a, b] function if you do not want to modify in-place.

The dimensions of a possibly-multi-dimensional array can be obtained with the array.dimensions[] method. The arrays do not have to be rectangular; this returns the highest index of any sub-array. For example, the following array has 2 rows and 3 columns so it returns [2, 3] .

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

a.dimensions[]

[2, 3]



You can concatenate two arrays (or other enumerating expressions) using the concat[array1, array2] function. (Note that this is a function, not a method on arrays!) This returns a new array that is the concatenation of the elements of both arrays. For now, the result is always an array, but this may change to be more memory-efficient by letting arbitrary enumerating expressions to follow each other.

This does not do any deep copying of the elements.

a = [1, 2, 3]

b = [4, 5, 6]

c = concat[a,b]

println[c]

[1, 2, 3, 4, 5, 6]



Items can be inserted into an array using the method array.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 array.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"]

A range of values can be removed with the array.remove[start, end] method which removes elements starting with index start (inclusive) and ending before index end (exclusive.) The number of items that will be removed is start-end . The return value is the number of items that were actually removed. (The end index is allowed to run off the end of the array.)

array = [0, 1, 2, 3, 4, 5]

array.remove[2,4]

array now contains [0, 1, 4, 5]

Similarly, a range of values can be removed with the array.removeLen[start, length] method which removes elements starting with index start (inclusive) and removes length number of items. The return value is the number of items that were actually removed (the length may run off the end of the array.)

array = [0, 1, 2, 3, 4, 5]

array.removeLen[2,2]

array now contains [0, 1, 4, 5]

Items with a specified value can be removed from an array using the method array.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"]

The array.removeAll[value] method removes all elements in the array which have the specified value.

array = [1,2,3,1]

n = array.removeAll[1]

[2, 3]

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

array = ["a", "b", "c"]

n = array.removeRandom[]

You can test if an item is contained in an array with the array.contains[value] method:

array = [1,2,3,1]

n = array.contains[3]

true

You can find the first indexes of items in an array with the methods:

array.indexOf[value]

array.indexOf[value, startIndex]

array.lastIndexOf[value]

array.lastIndexOf[value, startIndex]



If the item does not exist, these return -1 .

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

You can also specify an ordering function to be used by the lexicographicPermute method. The function should take two arguments and return -1, 0, or 1 to indicate if item a is less than, equal to, or greater than item b respectively. The following example sorts by length.

array = ["aa", "bbb", "c"]

f = {|a,b| length[a] <=> length[b]}

array.lexicographicPermute[f]

[[c, aa, bbb], [c, bbb, aa], [aa, c, bbb], [aa, bbb, c], [bbb, c, aa], [bbb, aa, c]]

You can obtain all of the combinations of the array by using the combinations[take] method. This returns an enumerating expression that lazily generates combinations. Note that the combinations are currently in lexicographical order, (without duplicates removed,) but this may change. For example, to take 3 items at a time from a list:

array = [1, 2, 3, 4]

array.combinations[3]

[[1, 2, 3], [1, 2, 4], [1, 3, 4], [2, 3, 4]]

The sample program pokerhands.frink demonstrates using this method to enumerate all possible 5-card poker hands.

You can take the first items from an array (or any enumerating expression) using the first[expr, num] function. This returns an enumerating expression that returns the first num items and discards the rest.

a = new range[1, 10]

println[first[a, 4]]

[1, 2, 3, 4]

The opposite of first[expr, num] is rest[expr, num] . This returns everything after the first num elements in the list.

a = new range[1, 10]

println[rest[a, 4]]

[5, 6, 7, 8, 9, 10]

If you only want the first element as as single expression, you can use first[expr] . This is like the ridiculously-named car function in other languages (whose name is a badly-chosen name from a 1950s-era computer. Please stop using the names car and cdr in modern languages.)

The opposite of first[expr] is rest[expr] . This returns everything after the first element in the list.

a = [1, 2, 3]

println[first[a]]

1

You can take the last items from an array (or any enumerating expression) using the last[expr, num] function. This returns an array that returns the last num items and discards the rest.

a = new range[1, 10]

println[last[a, 5]]

[6, 7, 8, 9, 10]

If you only want the last element as as single expression, you can use last[expr] .

You can get an arbitrary "slice" out of an array using the slice and sliceLength functions. (Note that these are functions, not methods.)

The slice[array, start, end] function returns a slice of an array starting with index start and ending before index end . If the indices are beyond the ends of the array, only existing items will be returned.

array = [0, 1, 2, 3, 4]

slice[array, 1, 3]

[1,2]

If start or end are the special value undef , then the slice will contain the start of the array or the end of the array respectively.

The sliceLength[array, start, length] function returns a slice of an array starting with index start and containing length items. If the indices are beyond the end of the array, only existing items will be returned.

array = [0, 1, 2, 3, 4]

sliceLength[array, 1, 3]

[1,2,3]

The inverse of slice and sliceLength are the functions removeSlice and removeSliceLength which remove the parts specified by the slice commands and return the rest.

The removeSlice[array, start, end] function removes array elements starting with index start and ending before index end . If the indices are beyond the ends of the array, only existing items will be removed. (Note that this is the inverse of what is returned by slice with the same arguments.)

If start or end are the special value undef , then the function will remove from the beginning of the array or to the end of the array respectively.

array = [0, 1, 2, 3, 4]

removeSlice[array, 1, 3]

[0, 3, 4]

The removeSliceLength[array, start, length] function removes a slice of an array starting with index start and containing length items. If the indices are beyond the end of the array, only existing items will be removed. (Note that this is the inverse of what is returned by sliceLength with the same arguments.)

array = [0, 1, 2, 3, 4]

removeSliceLength[array, 1, 3]

[0,4]

A list of lists can be flattened with the flatten[list] function:

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

println[flatten[a]]

[1, 2, 3, 4, 5, 6]

To obtain all of the possible subsets of the elements in the array, you can use the array.subsets[] method. This returns an enumerating expression which iterates through all the subsets of items in the array, including the empty set and the original set itself. Each return value is itself an array of elements, with the elements preserving the same order as in the original array:

a = [1,2,3]

println[a.subsets[]]

[[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]]

You can also request the subsets of the array and exclude either the empty set or the original set by using the array.subsets[allowEmptySet, allowOriginalSet] method. If allowEmptySet is true , the empty set will be returned as one of the elements. If false , the empty set will be excluded. If allowOriginalSet is true , the full original set will be returned as one of the elements. If false , the full original set will be excluded, and only "proper" subsets will be returned.

a = [1,2,3]

println[a.subsets[false, false]]

[[1], [2], [1, 2], [3], [1, 3], [2, 3]]

Note that output of the empty set and the original set are suppressed in the example above.

The contents of an array can be shuffled randomly with the array.shuffle[] method (using the Fisher-Yates-Knuth algorithm):

a = [1,2,3]

a.shuffle[]

a

[3, 1, 2]

The contents of an array can be cleared with the array.clear[] method:

a = [1,2,3]

a.clear[]

a

[]

While Frink does not have a complete implementation of matrix operations, (for an external class with some matrix operations, see Matrix.frink, and please contribute algorithms to that file,) it has some built-in methods that support matrix calculations.

The array.transpose[] method transposes the elements of a 2-dimensional array, like in matrix calculations. This means that rows and columns are switched. In other words, the element at array@i@j becomes the element at array@j@i .

array = [[1,2], [3, 4], [5,6]]

array.transpose[]

[[1, 3, 5], [2, 4, 6]]

Since arrays are also enumerating expressions, any of the enumerating expression functions such as first , last , etc. will work on arrays. Note that they are functions that work on a variety of data types, and not methods on the array. That is, you call them as first[array] and not array.first[] . See the following section for more information.

Some functions can return a lazily-produced list of results. These may be finite or infinite, and you often don't know in advance how long they will be, so the length function does not work on them. Frink calls these "Enumerating Expressions". For example, the function primes[] returns an infinite list of prime numbers. Other enumerating expressions may be finite, such as the commonly-seen 1 to 10 notation, usually used in a for loop, which sequentially returns the integers from 1 to 10.

You usually don't want to print out all of the values of an enumerating expression directly (because they may be infinite), but rather use it in a for loop and handle each value individually. After a value is returned from an enumerating expression, it is "forgotten", which helps reduce memory usage.

You can test if an expression is an enumerating expression with the isEnumerating[expr] function. Note that many of Frink's data types are also enumerating expressions, including array , dict , set , OrderedList , RingBuffer , etc., and will return true to this query.

This is a partial list of functions that have special behavior that makes them useful for working with (possibly infinite) enumerating expression. Note that arrays are enumerating expressions, so all of these will work with arrays.

first[ expr ] returns the first element of an array or enumerating expression as a single expression.

returns the first element of an array or enumerating expression as a single expression. first[ expr , count ] returns the first count elements of an array or enumerating expression. If the expression passed in is an array, the result is an array, otherwise it is a (possibly-infinite) enumerating expression.

returns the first elements of an array or enumerating expression. If the expression passed in is an array, the result is an array, otherwise it is a (possibly-infinite) enumerating expression. rest[ expr ] returns everything after the first element of an array or enumerating expression. If the expression passed in is an array, the result is an array, otherwise it is a (possibly-infinite) enumerating expression. This is the opposite of first[ expr ]

returns everything after the first element of an array or enumerating expression. If the expression passed in is an array, the result is an array, otherwise it is a (possibly-infinite) enumerating expression. This is the opposite of rest[ expr , num ] returns everything after the first num elements of an array or enumerating expression. If the expression passed in is an array, the result is an array, otherwise it is a (possibly-infinite) enumerating expression. This is the opposite of first[ expr , num ]

returns everything after the first elements of an array or enumerating expression. If the expression passed in is an array, the result is an array, otherwise it is a (possibly-infinite) enumerating expression. This is the opposite of last[ expr ] returns the last element of an array or enumerating expression as a single expression.

returns the last element of an array or enumerating expression as a single expression. last[ expr , count ] returns the last count elements of an array or enumerating expression. The result is an array.

returns the last elements of an array or enumerating expression. The result is an array. slice[ expr , begin , end ] returns a slice of an array or enumerating expression starting with index start and ending before index end . If the indices are beyond the ends of the array, only existing items will be returned. If begin or end is the value undef , the results will include the beginning or end of the expression respectively. If the expression passed in is an array, the result is an array, otherwise it is a (possibly-infinite) enumerating expression.

returns a slice of an array or enumerating expression starting with index and ending before index . If the indices are beyond the ends of the array, only existing items will be returned. If or is the value , the results will include the beginning or end of the expression respectively. If the expression passed in is an array, the result is an array, otherwise it is