String Hierarchy Restructuring

On 13 Aug 2008 we committed a fairly major restructuring of the type hierarchy. There may be some consequences for your programs.

What Happened

String, which was a (parameterized) object, is now a trait. This means that various other kinds of object will be able to extend String. In fact, the idea is that it will be possible to add new subtypes of String at will, without any further restructuring.

The things that you get from String literals like "Hello World" are now JavaStrings. JavaString extends String, so in most cases, you won't care.

Moreover, neither String nor JavaString is defined in FortressBuiltin. String is in FortressLibrary, and is thus implicitly imported everywhere. JavaString is defined in a separate file (it is still "native").

What May Break

In a few case, you may see some impact on your code, i.e., you may get weird error messages.

  • Case statements that compare characters with string literals won't compile. The string literals are JavaStrings, and the case is compiled into a MATCH operation. But the MATCH operator appears to have the wrong argument types… The solution is to fix the interpreter/compiler, but in the meantime, just importing JavaString.{...} allows your case statement to compile.
  • Although JavaString is a subtype of String, List⟦JavaString⟧ is not a subtype of List⟦String⟧. This may bite you. For example in
    Loading...
    the type of the constructed list is List⟦JavaString⟧, because the dynamic type of the object denoted by x is JavaString, not String. If this matters, the fix is to put an explicit type in the List constructor, i.e., to write
    Loading...
    Yes, I know that it's ugly. I believe that static type inference will make this go away.
  • If you are writing code that cared that String was really FortressBuiltin.String, you will obviously have to convince it otherwise. Some of the static type inference tests cared, and had to be bent to the will of the new Lord.

Printing

Now that we have tree-structured (lazy) strings, it makes no sense to convert them to JavaStrings just so that they can be printed. So the printing architecture has been revamped.

component Writer defines two new Fortress objects (implemented as wrappers on Java objects) Writer and BufferedWriter. These both extend trait WriteStream, which does essentially everything that a FileWriteStream used to do. Writer is unbuffered; BufferedWriter is buffered. A BufferedWriter is constructed by giving it a WriteStream, on which it will emit its characters when it fills up or is flush()ed. Thus, it's OK to stack one BufferedWriter on top of another.

You can make a new Writer on a file by giving the object constructor a file name as argument, just as you used to do with FileWriteStream. In addition, the component Writer defines two Writers

Loading...

which are already open and ready for your use. Print s is now defined to (roughly) be stdOut.write(s), and similarly for println. In addition, there are new top-level functions errorPrint and errorPrintln, which write to stdErr. The availability of stdOut and stdErr will make it much simpler to write a Fortress program that decided to write to the standard output or to a file depending on its input or a command line argument.

Printing and Concurrency

Because the Writer objects are wrapped Java objects rather than Fortress objects, they are not subject to Fortress concurrency control. If multiple threads in a Fortress program write characters concurrently to a single Writer, they will be serialized in an arbitrary order. If you don't like this behaviour, have each thread make its own new writer by wrapping a BufferedWriter around the underlying Writer.

This buffering is done automatically for any write of a String: a buffer of sufficient size is allocated, the String is written to it, and the buffer is flushed. This has the effect of making all writes of a single string appear atomic. Thus, one way of making a batch of output appear atomically on a writer is to concatenate all of the fragments together and then write the whole of the concatenated string with one print function call or write method invocation. Because concatenation does not copy its argument strings (unless they are very short), this is quite efficient.

Printing is not (yet) transactional. So, anything written on a Writer in a transaction will not be undone if the transaction aborts. Hence, when the transaction is retried, the output may appear a second (or third ...) time. If you are putting print statements inside a transaction for debugging purposes, this is probably what you want to see. If you are printing inside a transaction for other purposes, you may want to reconsider this behaviour.