This article was inspired by Eelis's C++ "Multi-Dimensional Analog Literals" website:
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.
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 '
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)
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
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...
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) )
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...
|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.|
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