Tuesday, November 28, 2006

scopes and RAII

Classes in Hyper cannot have a destructor. I wanted it to be this way because the language uses a garbage collector, and then the execution of a destructor cannot be always guaranteed. So destructors would not be reliable anyway if you want it to release acquired resources. And without destructors there is currently no way to do RAII. This is not acceptable in my opinion. So we need at least one way to do it.

This is my proposition. I would like to introduce a new block statement, "scope". This would take care of resource acquisition and disposal. You would give it an object that support 2 methods: "enterScope()" and "leaveScope()". The scope block would call the first method upon entering the scope, and would make sure that the second one is called whenever execution leaves the scope (i.e. normal exit at the end, plus exceptions and jump statements like "return", "break", etc.). The first form of "scope" would support the declaration of a variable, with or without an initializer. But I think it would also be useful to have an anonymous variable (i.e. to give it no name) and/or to have no variable at all and simply use a temporary object on the stack.


procedure test(x : * Xyz)
# indented output
scope i : Indenter = x.getIndenter() # increase indentation
i.printLn("Hello world.")
# ...
end # get indentation back to the previous level

scope this.getMutex() # lock mutex
# ...
end # unlock mutex
end test

The first example uses an explicit variable and the second uses no variable at all. In the second example the mutex returned by "getMutex()" is used as the scope object. In this case the result of the scope expression is probably a pointer (or a reference) to a field, but it does not necessarily has to be a pointer.

This reminds me about how to treat temporaries. I suggest to let them exist until the function they appear in returns. This would be ideal for use with restricted pointers. Any temporary could then be pointed to by a restricted pointer. And those could be stored in variables, so it would be necessary to keep the temporaries alive until the function returns.

Monday, November 27, 2006

operations on numeric types

First a bit of info about the built-in numeric types, in case you never saw them before or in case you have forgotten. The floating point types: 'single', 'double' and 'real' (not the subject of this article). The integral types: 'byte', 'nat16', 'nat32', 'nat64', 'int16', 'int32', 'int64' and 'int'. A 'byte' is unsigned and 8 bits. The 'int*' types are signed integer types (specified size or native int type), 'nat*' are unsigned integer types (again, of the specified size or else the native size). The native ones, 'int' and 'nat' are equal in size and have the size of the target platform (32 or 64 bit).

I am still somewhat unsure about the operations on those types. For example: what type should the result of a unary minus on a 'nat16' be? I would say 'int32', because the result can require 17 bits and that would not fit into a 'int16'. And I want to avoid unexpected overflows as much as I can. But this leaves me with some unpleasant consequences. I have no type to use for the unary minus on a 'nat64'. And neither I have for the native 'nat', because it would require one more bit than the 'int' has. So at this time there is no unary minus available for those two types. This problem of course doesn't exist for the 'int*' types; for example the unary minus operation on a 'int16' returns an 'int16' again.

I propose the following 'solution': I give the 'nat*' types a member function 'truncateToSigned', or something like that, which discards the most significant bit and then returns a signed type of the same size as the original. Like this:

class nat16
# (...)
const procedure truncateToSigned() : int16
# truncate and convert to signed

This allows the programmer to do what he/she wants, but allows for data loss. But at least the 'corruption' is visible by looking at the name of the function!

I already provided the 'int*' types with a function to turn the sign. This is a way to write assignments like 'delta = -delta' like this: 'delta.turnSign' (will be available in the next compiler version). This function doesn't return a result and changes the value of the object itself. (So it is different from the unary minus, which does not change its object and instead returns a value.) I wasn't completely sure about the name, maybe I could have used something like 'negate' instead but I am not sure if that means what's intended (I am not a native English speaker).

I am thinking about a signed byte type. At this type the literal -1 has type int16. That looks like a bit of a waste for such a small number. So it would maybe be nice to add a type (e.g. 'sbyte') for unsigned 8-bit values. Then I could add to 'byte' also a member 'truncateToSigned' to return a signed equivalent. But to avoid loss the unary minus of 'byte' would still return an 'int16'.

Another proposition. We now have a way to make a signed from an unsigned but not the other way around. So I would like to add a member 'abs' (= absolute value) that gives the unsigned value. I don't really like 'abs' because it looks to short. But on the other hand, something like 'absoluteValue' looks too long. Suggestions and argumentations for a name are of course welcome :-)

P.S.: I converted my blog to the new Blogger Beta, and it looks like the RSS of the old articles is a bit messed up now