Multi-dimensional Analogue Literals in Scala

This article was inspired by Eelis's C++ "Multi-Dimensional Analog Literals" website:

	http://weegen.home.xs4all.nl/eelis/analogliterals.xhtml#header

It struck me that you could do an even nicer job in Scala. C++ has operator overloading; Scala has operator syntax for methods. Parsing is very different in the two languages, though. In C++, a sequence of ------ characters will be treated as a number of successive unary decrement operators, "--". In Scala, such a sequence will be treated as a single method, named "------". However, this introduces some opportunities. It turns out, for instance, that we can improve on the C++ analog_literal's problem of requiring 2N+1 characters to represent the number N.

You can see the code for my Scala solution below. But first read on to see Scala-fied analogue literals in action.

AnalogueLine

To paraphrase Eelis: if you've ever felt that integer literals like '4' don't convey the true size of the value they denote, well, use an analogue literal instead. Like this:

	import net.snowtiger.analogue.AnalogueLiteral._

	var i = O~~~I

Single-dimensional analogue literals are of type AnalogueLine, and offer a "size" integer property. However, using the power of Scala's implicit type conversions, they can be combined with normal integers using standard operators:

	assert ( (O~~~I).size == 4 )
	assert ( O~~~I == 4 )
	assert ( O~~~I + 3 == 7 )
	assert ( 5 + O~~~I == 9 )
	assert ( 5 * O~~~I == 20 )
	assert ( O~~~I + O~~I == 7 )
	assert ( O~~~I - O~~I == 1 )

Anlogue lines represent the natural numbers, from zero upwards. Zero is coded as a single 'O' character:

	assert ( O == 0 )

Already we have a useful function - you need never worry about mistaking an 'O' for a '0' again! Larger numbers are represented by adding a series of ~ characters, terminated by an 'I':

	assert ( OI == 1 )
	assert ( O~I == 2)
	assert ( O~~I == 3)
	assert ( O~~~~~~~~~I == 10)

In order to make analogue numbers easier to read (and implement) we introduce an O character in positions which are multiples of 10:

	assert ( O~~~~~~~~~OI == 11)
	assert ( O~~~~~~~~~O~I == 12)
	assert ( O~~~~~~~~~O~~I == 13)
	assert ( O~~~~~~~~~O~~~~~~~~~O~~~~~I == 26)

AnalogueRectangle

Two-dimensional literals are also nice to have. These are of type AnalogueRectangle, and provide width, height and area properties:

	assert ( ( O~~~~I
	           |    I
	           |    I
	           |~~~~I ).width == 5 )

	assert ( ( O~~~~I
	           |    I
	           |    I
	           |~~~~I ).height == 3 )

	assert ( ( O~~~~I
	           |    I
	           |    I
	           |~~~~I ).area == (O~~~~I * O~~I).size )

	assert ( ( O~~I
	           |  I
	           |  I
	           |  I
	           |  I
	           |~~I ).area == ( O~~~~I
	                            |    I
	                            |    I
	                            |~~~~I ).area )

One disadvantage of the "N+1 characters" rule for AnalogueLines, compared to the 2N+1 of the C++ literals, is that AnalogueRectangles become rather squashed in the horizontal axis with standard fonts. You could try using a fat font. You can't, I'm afraid, put spaces between your ~ characters: O ~ ~ ~ I will not work in Scala.

On the plus side, properties "top" and "side" are also available. These give AnalogueLines, which as usual are also usable as standard Ints:

	assert ( (O~~~~I
		  |    I
		  |    I
		  |~~~~I).top == O~~~~I )

	assert ( (O~~~~I
		  |    I
		  |    I
		  |~~~~I).side == 3 )

It is possible to create rectangles with a minimum size of 1 on each side:

	var unitSquare = ( OI
		 	   |I )

Note that we often need to encase rectangles in brackets, as above, to prevent Scala's semi-colon inference algorithm terminating the statement after the first line.

You can also, if you like, generate analogue literals using standard object constructors. If you do so, you will also need to import the concrete class you are now directly referencing:

	import net.snowtiger.analogue.AnalogueRectangle

	assert ( (O~~~~I
		  |    I
		  |    I
		  |~~~~I) == new AnalogueRectangle(5, 3) )

A few operations, such as addition and scaling, are available on analogue rectangles. It is usually necessary to bracket up the analogue literals when using them like this, because unlike the ~ used in analogue lines, the | operator has quite a low precedence in Scala.

	val threeBy3 = (O~~I
		        |  I
		        |  I
		        |~~I)

	assert ( (OI
		  |I) * 3 == threeBy3 )

	assert ( (OI
		  |I) + (O~I
		         | I
		         |~I) == threeBy3 )

Imagine, as Eelis did, how nice it would be to set the size of a graphics object using an analogue literal. With Scala, you can easily create an implicit conversion to a Point or Rectangle class, and write:

	window.setSize( (O~~~~~~~~~O~~~~~~~~~I
		 	 |                   I
		 	 |                   I
		 	 |                   I
		 	 |                   I
		 	 |                   I
		 	 |                   I
		 	 |                   I
		 	 |                   I
		 	 |                   I
		 	 |                   I
		 	 |~~~~~~~~~O~~~~~~~~~I) * 10 )

Notice that it is neither necessary nor possible to use a different "tenth character" in the vertical dimension. I leave it as an exercise for the reader to implement this feature for rectangles and cuboids...

AnalogueCuboid

Speaking of which, it's time to visit the third dimension. A different symbol is used to represent each non- horizontal line of an analogue cuboid, like so:

	val size5cube = ( O~~~~I
			  |+    L
			  | +    L
			  |  +    L
			  |   +    L
			  |    O~~~~I
			   \   !    I
			    \  !    I
			     \ !    I
			      \!    I
			       !~~~~I )

Three-dimensional analogue literals are of type AnalogueCuboid, and have properties height, width, depth and volume. Unlike the C++ implementation, there are no serious restrictions on the proportions of a cuboid - anything from a 1x1x1 cube upwards is possible:

	assert ( ( OI
		   |OI
		    !I ).volume == 1 )

	assert ( ( O~~I
		   |O~~I
		    !~~I ).volume == ( OI
				       |OI
				       |!I
				       |!I
				        !I ).volume )

	assert ( ( O~~~I
		   |O~~~I
		   |!   I
		   |!   I
		    !~~~I ).volume == ( O~~~I
					|+   L
					 \+   L
					  \O~~~I
					   !~~~I ).volume )

Handy methods are also available to return the top, side and front AnalogueRectangles:

	val cuboid4x6x3 = ( O~~~I
			    |+   L
			    | +   L
			    |  O~~~I
			    |  !   I
			    |  !   I
			    |  !   I
			     \ !   I
			      \!   I
			       !~~~I )

	assert (cuboid4x6x3.top == ( O~~~I
				     |   I
				     |   I
				     |~~~I ) )


	assert ( cuboid4x6x3.side == ( O~~I
				       |  I
				       |  I
				       |  I
				       |  I
				       |  I
				       |~~I ) )

	assert ( cuboid4x6x3.front == ( O~~~I
				        |   I
				        |   I
				        |   I
				        |   I
				        |   I
				        |~~~I ) )

And, as with rectangles, you can add and scale cuboids:

	assert ((( O~~~I
		   |+   L
		   | +   L
		   |  O~~~I
		   |  !   I
		   |  !   I
		   |  !   I
		    \ !   I
		     \!   I
		      !~~~I ) * 10 ).volume == 72000 )

Finally, all analogue literals have toString() methods which display them in their pictorial form, together with their size characteristics. This gives us a handy way to check the right syntax for "analogue" construction of literals, particularly cuboids:

	println ( new AnalogueCuboid(6,3,4) )

Gives us:

	O~~~~~I
	|+     L
	| +     L
	|  +     L
	 \  O~~~~~I
	  \ !     I
	   \!     I
	    !~~~~~I = (6x3x4)=72

I hope you have enjoyed this Scala-style excursion into the wonderful world of analogue literals. I know you are now desperate to browse, download and start using the code, so without further ado I present...

The Code

AnalogueLiteral Base class for all three dimensions, plus companion object providing constants and implicit conversions.
AnalogueLine Class defining one-dimensional analogue literals.
AnalogueRectangle Class defining two-dimensional analogue literals.
AnalogueCuboid Class defining three-dimensional analogue literals.
AnalogueTester Unit tests.
analogue.jar Compiled classes.

Enjoy!

Although, having written all this, I've just realised that "analogue" (or even "analog") is a fairly daft term for a representation of a non-continuous numerical quantity. (Too late!)

MBD June 2011