Fun with static types

The real issue is one of “type decoration”, i.e., putting in little tokens here and there to give hints to the compiler as to what is going on. No one wants to do this. The dynamic typing camp sacrifices a certain amount of “compile-time” error checking to avoid having to decorate the code. The static typing camp uses smart tools to minimize decoration, and they “grin and bear it” otherwise. To caraciture both camps, the dynamic type camp says “it is so painful to type ‘integer’ that I never ever am willing to do it, but I'll live with runtime exceptions” whereas the static-typing camp says “my type system is so smart I almost never have to add declarations, but the thought of the computer ‘guessing’ my intent and ‘dwimming (verb form: Do What I Mean)’ my code is anathema. So I'd rather type the occasional type declaration to avoid it.”
As I mentioned before, I'm very much in the “dynamic” camp. When I have to work with a language which is not only statically typed, but provides no tools for reducing the amount of type decoration, and furthermore still allows one to write meaningless expressions that appear to be meaningful, I end up feeling encumbered. So I don't think of “lightweight” and “type declarations” go together very well. I'm sure some agree and others disagree.

“Re: cheerful static typing (was: Any and Every … (was: Eval)) [1]”

I quote this because this seems to be indicative of the current “anti- static type” camp of programmers that seems to be very popular these days, which seems to come down to, “I hate typing at the keyboard.” That, and “I hate thinking about my program [2]” (and here you clearly see what side of the debate I'm on—I'm a static-type fascist). But over the past few days I came up with an example (and I wouldn't be surprised if someone else hasn't thought of this) where static typing can actually increase the expressiveness (read: less typing required) of a language.

I'll start with an example in C (I would use Ada, but it's been years since I last used it, I don't have my references handy, and it would be about twice as verbose, so there's some merit to the “too many keys” argument of the dynamicists):

{
  FILE *in;
  FILE *out;
  int   c;

  in  = fopen("input","r");
  out = fopen("output","w");

  /*-----------------------------
  ; yes, there's a bug here, but
  ; that's due to a bug in the
  ; design of the C Standard Library
  ; API.  feof() only returns TRUE
  ; *after* we've attempted to 
  ; read past the last character.
  ;-----------------------------*/

  while(!feof(in))
  {
    c = fgetc(in);
    c = toupper(c);
    fputc(c,out);
  }

  fclose(out);
  fclose(in);
}

It's not a terrible amount of code (as say, compared to the equivalent in this age's Cobol—Java) but since we already have the types (that the fascist language C requires) why not do something other than simple type checking? Why not put that knowledge to use and have the compiler write some code for us? We could instruct the compiler (for our now hypothetical language) that if it sees an assignment of the form:

character-type “=” file-type

it should read the next byte from the file. Conversely, if it sees

file-type “=” character-type

it should then write the given character to the file.

>
```
{
FILE in;
FILE out;
char c;
in = fopen("input","r");
out = fopen("output","w");
while(!feof(in))
{
c = in;
out = toupper(c);
}
fclose(out);
fclose(in);
}
```

It may look unusual, but a similar notion exists in another language right now, one that is (or rather, was) quite popular: Perl.

{
  open(IN,"input");
  open(OUT,">output");
  while($line = <IN>)
  {
    print OUT $line;
  }
  close(OUT);
  close(IN);
}

(Of course, since Perl is dynamic, you have to have the file handle between the angle brackets, which tells Perl you want to do a read from the file. Attempting to do:

#!/usr/bin/perl

while($line = <STDIN>)
{
  <STDOUT> = $line;
}

fails with

Can't modify <HANDLE> in scalar assignment at ./t.pl line 5, near "$line;"
Execution of ./t.pl aborted due to compilation errors.

so it's almost what we're doing here, but just in one special case)

Now, while we're at it, why not add a bit more syntactic sugar and use a common object oriented notation (and at the same time, if a [DELETED-function-DELETED] method takes no formal parameters, just dump the superfluous paranthesis):

{
  File in;
  File out;
  char c;

  in  = File.read("input");
  out = File.write("output");

  while(!in.eof)
  {
    c   = in;
    out = c.toupper;
  }

  out.close;
  in.close;
}

Wait a second … let's go even futher. Why bother with the actual function names read and write? By making the file types more specific, and with a smart enough compiler to call destructors upon leaving the functional scope, we can indeed cut out even more clutter:

{
  FileIn  in  = "/tmp/input";
  FileOut out = "/tmp/output";
  char    c;

  while(in)
  {
    c   = in;
    out = c.toupper;
  }
}

So, we've instructed the compiler to note

file-input-type “=” string

and therefore open the requested file for input; the same can be said for file-output-types as well. In a boolean context, it makes sense to check if the file is at the end.

Now, in all this, I've been transforming the data, but if I want to skip even that, why should I have to write:

{
  FileIn  in  = "/tmp/input";
  FileOut out = "/tmp/output";
  char    c;

  while(in)
  {
    c   = in;
    out = c;
  }
}

When I could just:

{
  FileIn  in  = "/tmp/input";
  FileOut out = "/tmp/output";

  out = in;
}

But is that expecting too much? In the previous example, we get an actual file copy, but what if we don't want that? What if we want to copy not the file, but the “variable” in itself? Well, here, types again come to the rescue because copying the contents of an “input file” to the contents of another “input file” doesn't make semantic sense, so in that case, it's the variables themselves that are copied, not the data in the files they represent:

{
  FileIn in1 = "/tmp/input";
  FileIn in2;

  in2 = in1;	// now in2 and in1 reference
  ...		// the same input file
}

The argument could be made that just like polymorphism and operator overloading, this will lead to inscrutable code, but personally, I'd love a language that would allow me to do such things (and I'm not even sure what to call this … it's not exactly a type conversion). I've also glossed over how to code these conversions but I'm sure that just like operator overloading in C++ a syntax can be evolved.

[1] http://people.csail.mit.edu/gregs/ll1-discuss-archive-html/msg01231.html

[2] /boston/2006/06/05.1

Gemini Mention this post

Contact the author