Log in

No account? Create an account
the Stu programming language's Journal
[Most Recent Entries] [Calendar View] [Friends]

Below are the 20 most recent journal entries recorded in the Stu programming language's LiveJournal:

[ << Previous 20 ]
Saturday, February 26th, 2011
11:20 pm
I am becoming more convinced we need to fork the C language.

  • string constants being of type "const char*" force const discipline onto the language, instead of const discipline being optional. this is a mistake since it breaks old code (and not ever modern coder believes in const discipline)

  • strict alias checking is a mistake. it breaks old code and ruins a perfectly good systems-programming idiom without offering a syntactically straightforward replacement.

  • arithmetic overflow optimizations are a mistake. they break old code and ruin a perfectly-good systems-programming idiom.

There doesn't seem anything terrible in the latest spec, but I'm sure more is coming.
Tuesday, November 17th, 2009
2:49 am
1:20 am
Sunday, November 15th, 2009
10:57 am
Saturday, November 14th, 2009
6:06 pm
2:02 pm
revisiting the design
I haven't worked on Stu in a while, but since Google's released Go and I strongly disagree with the design of it, and other C replacements like Cyclone have come out, I figured it's a good time to revisit the high-level goals and design of Stu and put it on the table for myself to think about revisiting.

Read more...Collapse )
Friday, May 23rd, 2008
10:22 am
I'm poking at Stu again.

The last time I posted, I was still puzzling over how to make the "statement delay operator" => as nicely useable as possible.

That's still a good question, but in the interim I've decided I need to change the symbology, because I want to use '=>' for something else.

the something elseCollapse )
Monday, August 22nd, 2005
11:48 am
more on =>
I posted some time ago about the introduction to stu of the "=>" statement prefix:

   f := fopen(filename, "r") => fclose(f);
   ... do stuff with f ...
   if something { return none; }
   return result;
which causes code to be deferred so it is run at the end of a lexical scope.

I mentioned I wanted to do something else to allow the above to be more automatically specified by the fopen itself, since it knows fclose is the balancing function. If fopen (or a variant thereof) returns a tuple with the file handle and a file-closing closure, I need someway to specify calling that, and I gave as an ugly example I didn't like:

   (f, =>) := fopenx(filename, "r");

Here's my current thought:

   f := fopenx(filename, "r") => ();

This is ugly and easily confused with the original example, so it may look pretty dumb. But if you look at what the actual spec is that produces this, I think it's pretty reasonable. Read more...Collapse )
Monday, December 13th, 2004
8:51 pm
Note to self for future use: Read more...Collapse )
Monday, September 6th, 2004
4:52 am
RAINA - Resource Acquisition Is Not Allocation
C++ introduced the slogan RAII--Resource Acquisition Is Initialization--a misnomer since the important part of the idiom is _deacquisition_. The C++ idiom's viewpoint is roughly "we have a good model for initializing/deinitializing data as it is allocated/deallocated--constructors and destructors--and applying this to resource acquisition eases the task of deacquisition". So a more accurate slogan might be RAIA: Resource Acquisition Is Allocation. The thing is, the C++ metaphor is nice for lexically scoped resource acquisition, but otherwise it's pretty messy (auto_ptr and all its ilk). In fact, RAIA is important in the face of things like exception handling because C++ doesn't offer any other simple mechanisms for coping with exceptions.

RAIA does not, in fact, play nice with garbage-collected allocation; exposing finalization to users is fraught with peril. Hence Stu's slogan: Resource Acquisition Is Not Allocation. (Stu also strikes this pose because I am against viewing all problems as nails for the OO hammer.) What's needed is a different lexically-scoped resource acquisition scheme, one which deals with exceptions.

Stu introduces two mechanisms. The general flexible mechanism is the => statement delay operator, which causes the statement following it to be executed when the current lexical scope ends (or when an exception is thrown). So if we had a C-like file I/O library, we might write this:

   f := fopen(filename, "r") => fclose(f);
   ... do stuff ...
   if [bad thing happens] then return none; endif;
   return result;

Note that this assumes that if fopen() fails it returns none and fclose() accepts none simply to simplify this sort of code. (The first thing the code will actually do is 'cast away' none, since fread()/fgets() sorts of codes probably won't accept none.)

The second mechanism I want to design is to make the above more automatic. We write a single wrapper function around fopen and fclose which returns a tuple containing the filehandle and a void(void) closure that will fclose it:

fopenlex(filename, mode)
   f := fopen(filename, mode);
   return (f, func() { fclose(f); } );

then in some other routine we can call fopenlex in some special way that causes the void(void) closure to automatically be treated the same as the =>-ified statement earlier. For example, maybe we assign it to a special operator:

   (f, =>) := fopenlex(filename, "r");

But while => is a really cute operator for the original scenario, it's incredibly ugly here. The symbol "_" is already used to mean "ignore this". Suggestions? I'm also open to things that don't involve assignment to a magical special symbol; we could just prefix 'fopenlex()' with an operator which means 'return the first element of this tuple, and =>-ify the second element of it', although that's obviously less flexible.

Monday, August 2nd, 2004
7:15 am
syntax opinions?
Currently I annotate types which can be none by prefixing them with a ?; in other worse, ?List is either of type None or type List. In practice you would typedef this away most of time (more particularly, I plan to allow the definition of List itself to allow for it being None); but still, in looking through the source code, it seems somewhat ugly to me. I like the idea of the prefix symbol, but I'm not happy with ?. It's a good fit with the rest of the language--I use ? as an expression operator for C++'s dynamic_cast operation--and even makes some sense (it's expressing OCaml's "option" and supplies a sense of "maybe"), but it's just ugly. It usually leads a capital letter, and I don't think you want a space between them, and since it's tall, it's just messy. Some other symbol that's not tall would be better. I'd prefer not to overload some already standard operator. Now, in fact, types pretty much always appear in a special syntax (they always follow a colon), so they could be just about anything.

I'm not even sure what options there are. But most are tall and ugly.
   ?List     *List     &List
   !List     -List     @List
   .List     \List     #List
   `List     %List     $List
   ~List     |List     >List

I'm planning on using `a as OCaml's 'a, actually, so that's not an option. I guess I should just stick with ? and assume that my heavy use of none is going to go away, based on my last post.

6:36 am
Many months ago, I wrote up a realization I had about a problem with creating OO-esque versions of abstract data types. The idea is this. Suppose I create a List class; it contains a 'head' and a 'tail' pointer; the head contains the first item in the list, and the tail points to another list item (with the rest of the list) or none if the list is empty. (none is Stu for NULL)

The problem is this: if the system uses run-time dispatch on types to process operations, then, say, the message "getHead" on a list calls the method "getHead" defined by the List, and we get the head field, and return it. All well and good. But none is not of type List. So I iterate down the list by calling getTail, and eventually none is returned, but I can't send any messages to it. Of course, the empty list doesn't support getHead and getTail, so that's ok. And I could define none to take isEmpty and return True, I suppose. But you can imagine that there are then other messages you might send to lists; say, countLength. I can keep pushing all of these on none, but meanwhile I'm also using none for other data structures besides Lists, like the unused pointers in trees.

The problem at heart here is that a proper list element has dynamic type List, and none has dynamic type None. Object-oriented message dispatch proceeds from the dynamic type of an object. This notion of dynamic type is inherent in any language with inheritence, including C++. Consider that you have an object z of some class Bar which inherits from Foo. Some message is sent to z and is handled by inherited code defined in Foo. Somewhere in that code a self-send occurs--a virtual method call is made on this. Despite the code being in Foo, and thus seemingly to be code in which this is of type Foo, it is in fact not; the static type of this is Foo, but the dynamic type of this is Bar, since further message sends to this will go to Bar. (This definition of "static type" and "dynamic type" is my own; if there is some more standard terminology, I'd like to hear it.) It also means my language might have two kinds of polymorphism: static-type based and dynamic-type based, which is exactly the sort of mess that C++ has that I would like to avoid.

So it seems to me there are a few options:

  • Require client code to test for none everywhere. (This is the standard approach for this kind of List, but it's clearly not generalizeable.)
  • Hope that you can just push the right methods onto none for everything to work.
  • Create a special instance of List (and each other none-needing class) that has all the right methods. Possibly by subclassing.
  • Rely on the static type for the dispatch, instead of the dynamic type. This makes sense for things like a List that uses parametric polymorphism (i.e. C++ templates or ML-style), but it means you can't go all out dynamic OO. Specifically, if you ever take one of these objects and stick it in a heterogenous data structure, it's hard to recover it properly; if it is none, then which sort of none is it? (This actually probably applies to anything that doesn't use an instance of List, though.)

I'm curious if this problem has even been tackled anywhere else, but probably not. I saw mention in a recent discussion on the Lambda weblog about how statically typed languages could have an any type, but none do. So I don't think anyone's really thought through the problems with doing it.

Sunday, August 1st, 2004
12:54 am
This is an old writeup I never finished.

references, boxing, and packingCollapse )
Wednesday, April 28th, 2004
3:54 pm
Wednesday, December 17th, 2003
4:05 am
type inference of type sets
Stu's "union" type, the type variant, is a type which represents a set of other types. A variable v of variant type T can take on any of the types in T's set of allowed types. You can then match on v, matching its specific type against those cases, to do further processing.

The current type-inferencing engine is incredibly delicate, fragile, and I don't really understand how it works much of the time. I thought I could live with that, but it's posing a barrier to getting polymorphically parameterized types working, so I think I want to scale back the type-inferencer a little.

I'm hoping that the biggest thing I can simplify is the attempt to automatically recognize type variants. Consider:

StringOrInt variant { int; string; }
   x := 0;
   if (bar)
      x = "foo";
   return x;

Here, I currently will determine that x, and the return value of function foo, are of type StringOrInt.

Read more...Collapse )
Saturday, August 9th, 2003
12:51 pm
I'm thinking maybe a syntax like
List::foldleft(mylist, startval) with val,item {

although this doesn't allow for non {} statements in that syntax. But I like 'with' more than 'do'. Maybe 'of' instead?

Friday, August 1st, 2003
4:50 pm
Hrm. Ok, there is a problem with my set/subset type system plan.

Read more...Collapse )
Wednesday, July 30th, 2003
6:31 pm
I've finished doing all my null-pointer guards and turned on the full-blown checking. I also went through and removed almost all the type declarations from functions; there are 397 function definitions in the compiler, and 32 of them have one or more parameters' types specified, or a specified return value. Most of them should be able to be removed--the types should be inferrable, but they're not either due to bugs or due to ommissions (there are, coincidentally enough, 31 instances of "@TODO" in the source file); in a few places, I left in types to make it clearer what was going on.

However, my current approach to interprocedural type inference may not work properly for parametric polymorphic type inference: just because a particular call to foo(x) has x of type Bar doesn't mean foo only accepts Bar for x, but that is the sort of information I rely on for resolving certain kinds of type ambiguities currently. On the other hand, it may be that as long as I process the type inference in dependency order, I can rely on the current system with a reasonable amount of tweaks.
Monday, July 28th, 2003
6:56 pm
So last week I wrote both parts of the typechecking I needed to do static null-pointer checking; first, I added the ability to prefix any type name with a '?', meaning that it's allowed to accept 'none' as a value (this is a synonym for a variant of the original type and None); and second I changed the type -seen-by-the-inferencer of the constant 'none' to be of type None rather than of no-type-info (which allowed it to be used in any context without error). This resulted in a million errors in my program (the compiler itself), since I've been freely using none without using ?-stuff (since it didn't exist yet).

So I disabled the second change, and just started with the first one. Turning on the first one does nothing, since none of my types had ? in them. But then I started going through, adding '?' where I knew I needed them, and then getting a bunch of type errors, and going and manually doing the necessary runtime downcasting or what not to make it safe. (Where I don't have ?, I'm currently allowed to assign the value to be 'none' anyway, since the typechecker has no type information for 'none'. But if it has a ?, it knows it's illegal for me to use it directly without converting it to the non-? type.) After doing this for a while, I realized I was just guarding everything and throwing an error if it was none, rather than actually trying to refactor to avoid things being none unnecessarily, so I went back through and did some such refactoring in as many places as I could.

Today, bored with that approach, I tried turning on the 'none is of type None' thing again, to see how many errors I still had (using the 'none' with a type that has no ?-prefix), and there were still too many, but I noticed that the first one was a global variable which was something like 'm:Marker=none;' because I don't allow global variables to be assigned anything other than constants. (I parse them, but produce an error on output.)

So today I (a) allowed output of arbitrary computation for global initializers, by outputting a hidden function "__init()" and secretly calling it from main(); and then (b) I spent a long time writing code to track global-variable dependencies through global variables and functions they call for initialization. Then topologically sort that and build the __init() function so the calls are in the right order. Messy code since the functions might be recursive etc.

This seems to work pretty well; I was able to move huge swaths of initialization of globals back out of main into the declaration of the globals. This combined with Java-style "all functions can see all other functions in the file" makes for nice simple coding of stuff where you just using anything you want anywhere you want it and don't fret the details (until the compiler says 'hey, these globals are mutually recursive', which was actually true for one of mine, but I knew that in advance because I'd previously had to split the initialization into two stages).
Tuesday, July 22nd, 2003
12:48 pm
I'm going to have some kind of anonymous function lamba-esque thing, maybe a syntax like
fun (x,y) { ... }
(which is an expression). I want to have some syntactic sugar so you can write iterators that look more like C loops. I don't want to have to write List::foreach(foo, fun(x) { ... }) because I find that ugly. So instead you have a function call, and then after it some code { ... }, and the compiler can tell from the syntax that it's actually an extra parameter to the function call, but written outside the parentheses. The question is, where do the names of the formal parameters to the closure go? E.g. I have some list foo, and I say something like

       ... do something with x ...

then where do I define that 'x' is the parameter? To make it clearer, let's have two parameters, x & y.

foreach(foo) x,y { ... } I'm leaning towards this one but I don't really like how it comes out when written multi-line as above
foreach(foo):x,y { ... } (overloads ':' in a way I don't like)
foreach(foo) { ...x...} (can only have one parameter, and it's always named 'x')
(x,y) = foreach(foo) { ... } prevents foreach() from having a real return value
let (x,y) = foreach(foo) in { ... } will utterly confuse ML programmers, and sort of has the return value problem, although technically you could write z = let (x,y) = ...
foreach (x,y) in foo do {...} only allows one parameter to foreach besides the closure; no longer looks like the function call it is, in fact looks misleading like x&y are the parameters

Opinions? Alternatives?

[ << Previous 20 ]
About LiveJournal.com