Language Design

Posted 9 Dec 2000 at 22:43 UTC by apm Share This

This article describes the Suneido language.  As the language designer, I am looking for comments, criticisms, comparisons, and suggestions.  I am also looking for feedback on this article.  Does it adequately describe the language?  Is it understandable?  Is there anything that could/should be taken out?   Is there anything that should be added?

Here is a simple example of Suneido code:

/* 
multi-line comment 
*/ 
calc = function (x, y = 0, dbl = False) 
    { 
    sum = x + y // single line comment 
    if (dbl is True) 
        sum *= 2 
    return "result is " $ sum 
    } 
calc(123) => "result is 123" 
calc(123, 456) => "result is 579" 
calc(10, dbl:, y: 5) => "result is 30" 
calc("123") => "result is 123"

Notice:

  • C/C++/Java style comments, either /* */ or //
  • Functions are declared using the keyword ``function''
  • Functions (and classes) are "values" - they can be assigned to variables etc.
  • Function arguments can have default values
  • Function calls can use named arguments, in which case order is not significant.  If a value isn't supplied for named argument, it defaults to True.
  • Semicolons are not required (but are allowed)
  • Variables do not need to be declared, and may contain any type - i.e. Suneido is a dynamically typed language
  • is, isnt, not, and, or are normally used but ==, !=, !, &&, || are allowed
  • Suneido includes all of the C/C++ operators, including assignment operators (e.g. *=), increment, decrement, and bit manipulation, and adds regular expression operators ~~ and !~
  • Automatic conversions between numbers and strings.
  • String concatenation is done with the "$" operator, not by overloading "+".  This makes "123" + 456 and 123 $ "456" unambiguous.
  • Suneido includes the same control statements as C/C++ if-else, while, do-while, switch, but unlike C/C++ conditions must evaluate to true or false or you'll get an error.  Switches are also slightly different - you can't "fall through" cases, and cases allow multiple expressions rather than integer constants.  There is also a "foreach" statement (see below).

Functions can take variable numbers of arguments:

max = function (@args) 
    { 
    max = args[0] // will throw exception if no args 
    foreach (value x in args) 
        if (x > max) 
            max = x 
    return max 
    }
max(3, 6, 2, 5) => 6 
max("joe", "fred", "sam", "mike") => "sam"

@args puts all the arguments into a Suneido "object" (see below).  foreach iterates through all the values in the object, in this case all the arguments.

You can also pass a pre-assembled set of arguments, using @.

ages = #(23, 67, 34, 19) 
max(@ages) => 67

Memory is garbage collected - there is no explicit allocation or freeing.  However, finalization is not supported yet, so resources (e.g. Windows handles) must be explicitly released.

The basic data types in Suneido include: boolean (True and False), number, string, and object.

Suneido has a single numeric type - decimal floating point.  Keeping numbers in decimal rather than binary allows exact representation of decimals, e.g. for amounts of money.  Numbers have 16 digits of precision with an exponent range of plus or minus 127.

Strings are not null terminated so they can store arbitrary binary data as well as text.  Strings are immutable i.e. there is no way to "modify" an existing string.  (Objects are the only basic data type that is mutable.)  This means that substring (s.Substr(i,n)) does not need to do any copying - it just creates a new string that points to part of the old one.  For speed, concatenation is "lazy"; it creates a linked list of string segments, which are automatically combined when a single string is required.  This greatly reduces the amount of allocation and copying required to manipulate strings.

Suneido could be called a "pure" object-oriented language in the sense that all values (including literals) are "objects" that can have methods.  For example:

"hello world".Substr(3,2) => "lo"
97.Chr() => "a"

However, unlike some object-oriented languages such as Smalltalk or Java, Suneido has standalone functions as well as methods in classes.

User defined methods can be added to the built-in classes by creating methods in specially named classes: Numbers, Strings, Dates, Objects, etc.

Suneido has only two "scopes", global and local.  Global names must be capitalized i.e. start with an upper case letter.  Globals are either builtin (to the executable) or user defined in libraries in the database. Global names are not variables - they cannot be assigned to by code.  Names that start with a lower case letter are local to a single function.  Currently there are no packages or modules with separate namespaces.

Like Java, Suneido compiles to a stack oriented "byte code" which is then interpreted.  For example:

function (x) { return x * 100 }.Disasm()
=>  push auto x
    push literal 100
    *
    return

Linking is done dynamically at run time.  This means there are no restrictions on compiling code with calls to undefined functions or inheriting from undefined base classes.  Of course, if you try to actually access something undefined, you'll get a run time error.

Objects

Suneido has a single "universal" container type - objects.  Unlike other languages like Python or Ruby, you don't have to worry about what type of container to use.  Objects can be used as vectors (single dimensional arrays) or as "maps" or both at the same time.  Internally, objects have a vector part and a hash map part.  Classes and instances are implemented as objects containing methods and data members.

x = Object() 
for (i = 0; i < 6; ++i) 
    x[i] = i // access as a vector 
x.Add("six") // same as x[6] = "six"
x => #(0, 1, 2, 3, 4, 5, "six")
x = Object(name: "fred", age: 25) 
x.married = True
x => #(name: "fred", age: 25, married: True) 
x.name => "fred"
m = "age" 
x[m] => 25 // same as x.age

Exceptions

Suneido has exception handling similar to C++ or Java, with try, catch, and throw.  However, Suneido exceptions are strings rather than class instances.

try
    ...
catch (exception)
    ...

The catch portion is optional if you simply want to ignore exceptions:

try Database("destroy mytable")

An uncaught exception calls a Handler function - defined in the standard library as the debugger.

You can throw your own exceptions:

if (x < 0) 
    throw "square root: invalid negative argument: " $ x

Blocks

Smalltalk style "blocks" are a recent addition to Suneido.  Basically, a block is a section of code within a function, that can be called like a function, but that operates within the context of the function call that created it (i.e. shares its local variables).

Blocks can be used to implement user defined "control constructs".  (In Smalltalk, all control constructs are implemented with blocks.)  For example, you could implement your own version of "foreach":

for_each = function (list, block)
    {
    for (i = 0; i < list.Size(); ++i)
        block(list[i])
    }
list = #(12, 34, 56)
for_each(list)
    { |x| Print(x) }

=> 12     34     56

Suneido treats a block immediately following a function call as an additional argument.

Blocks can also be used to execute sections of code in specific "contexts".  For example, the Catch function traps exceptions and returns them.  (This is useful in unit tests to verify that expected exceptions occur.)

catcher = function (block)
    {
    try
        return block()
    catch (x)
        return x
    }
catcher( { xyz } ) => "unitialized variable: xyz"

But the interesting part is that a block can outlive the function call that created it, and when it does so, it keeps its context (set of local variables). For example:

make_counter = function (next)
    { return { next++ } }
counter = make_counter(10)
Print(counter())
Print(counter())
Print(counter())
=>  10
    11
    12

In this example, make_counter returns a block. The block returns next++.  You see this type of code in Lisp / Scheme.

Classes

Classes are read-only objects containing methods (functions) and members (data).  For example:

counter = class
    {
    New(start = 0)
        { .count = start }
    Next()
        { return .count++ }
    }
ctr = new counter
Print(ctr.Next())
Print(ctr.Next())
Print(ctr.Next())
=>  0
    1
    2

Within a method, the special name "this" refers to the current object.  So members and methods of the current object can be referred to as "this.name" but this is normally shortened to simply ".name".

Member / method names are public if they start with an upper case letter and private if they start with a lower case letter.  Private names can only be accessed by methods of the class.  In the above example, "Next" is a public method and "count" is a private member.

New is the constructor.  (C++ and Java use the name of the class instead of "New", but this wouldn't work for Suneido because it allows "anonymous" classes i.e. with no name.)  Base class New's are automatically called first.  You can supply arguments to base class constructors by making the first statement of New a call to "super".  (Similar to Java.)

class
    {
    New(x, y, z)
        {
        super(x, y)
        ...

"super" can also be used to call base class versions of methods:

mystack = class : Stack
    {
    Push(x)
        {
        ...
        super.Push(x)
        ...
        }

Normally, "calling" a class is the same as creating an instance.  (This can be changed by defining a method called "CallClass".)  So the following are equivalent:

ctr = counter(5)
ctr = new counter(5)

Any method in Suneido can be a "class" or "static" method, providing it doesn't need to set any members.  If a method is called on a class rather than an instance, "this" will be the class itself, and therefore read- only.

If a nonexistent member is accessed, Suneido will call a method with the same name with no arguments, if present.  This allows you to change a data member to a method without having to alter code that uses it.  (Bertrand Meyer calls this "Uniform Access" - a client should be able to access a property of an object using a single notation, whether the property is implemented by memory or computation.)

If a nonexistent method is called, Suneido will call a method called "Default" if it is present.  This can be used to implement delegation, or to handle methods that are not pre-determined.

Since classes and instances are objects, and have methods to get their base class, it is easy for code to inspect methods and members.  Java calls this "reflection".

Stack.Members()
=> #("Pop", "Top", "Push")

Object syntax can also be used to access methods or members "indirectly":

m = "Add"
ob[m](value) // equivalent to ob.Add(value)

"Missing" Features

  • no declarations
  • no preprocessor i.e. #define or #include
  • no enums
  • no multiple inheritance (although this may be added)
  • no operator overloading
  • no goto
  • no explicit pointers
  • no explicit free'ing of memory
  • no separate module or package namespaces
  • no non-local returns from blocks
  • no "protected" members / methods (just private and public)
  • no "static" class data members (just class methods)

Summary

The Suneido language is:

  • small, simple
  • object-oriented
  • dynamically typed - no declarations
  • functionally similar to Smalltalk
  • familiar to anyone who has programmed in C, C++, Java etc.
  • safe - automatic memory management (garbage collection) and no pointers

PS., posted 9 Dec 2000 at 22:47 UTC by apm » (Journeyer)

For more information see: www.suneido.com

let's see, posted 9 Dec 2000 at 23:12 UTC by cmm » (Journeyer)

a kinda nice looking language that picks up the following misfeatures:

  • gaudy syntax (C/C++/Java/Perl)
  • no macro facility (Java). hint: macros don't suck when done right. C preprocessor is not done right.
  • automatic string<->number conversion (Perl/shells)
  • a too simplistic class-based object system (well, everybody has one)
  • floating-point-only arithmetic, with bitwise operations

I understand you are having fun. without luck, your next effort will produce a better language. with luck, your next effort will lie in improving some existing wheel instead.

but there's nothing wrong with having fun, of course.

Indeed., posted 9 Dec 2000 at 23:33 UTC by egnor » (Journeyer)

Yes, cmm was on the mark.

You're adding a point somewhere in the middle of the space populated by Java, ECMAscript, Python, Smalltalk, and so on. Unless I missed something, you're not introducing any interesting new features, just mixing and matching the attributes of other languages. Some people will like some of your choices; others won't.

That space is already pretty crowded, and IMHO the benefit of accepting a feature set that is slightly closer to one's "ideal language" is nowhere near the staggering cost of adopting an entirely new language all over again.

It's quite easy (and rather entertaining) to build your own toy language, starting with a simple interpreter and adding features all day. (These toys are easily recognized by their dynamic type systems, interpreted environment, and lack of deviation from the well-established programming paradigms with which the author happens to be familiar.) I've done it myself. It's very important, however, that you don't take yourself too seriously -- nobody is actually going to use your language, and, no, you're not the next Guido van Rossum or Larry Wall.

If you really want to learn about programming language design, you'd do well to leave your "comfort zone". Learn some non-mainstream languages like Haskell and Mercury, and use them to write nontrivial programs. You'll probably hate it, but the experience will forever change the way you look at programming, and make all the languages I cited at the beginning of this reply (including yours) seem like the endless permutations on the same old boring theme that they are.

You have contributed one thing, though; your misformatted post exposes a bug in Advogato's HTML sandbox, since the "Reply" link is also preformatted.

Toy languages, posted 10 Dec 2000 at 01:16 UTC by raph » (Master)

Dennis Ritchie, in his LinuxWorld interview, said:

At least for the people who send me mail about a new language that they're designing, the general advice is: do it to learn about how to write a compiler. Don't have any expectations that anyone will use it, unless you hook up with some sort of organization in a position to push it hard. It's a lottery, and some can buy a lot of the tickets. There are plenty of beautiful languages (more beautiful than C) that didn't catch on. But someone does win the lottery, and doing a language at least teaches you something.

Couldn't have said it better myself.

One other observation, though: in most applications these days, the language per se pales in importance compared with the surrounding runtime and available libraries. In almost all cases where a language has succeeded, it's because of the surround, not the language itself. For example, a lot of people like Java the language, but dislike the huge, constantly changing cloud of libraries and so on surrounding the language. Yet, even though Java has failed miserably as a desktop platform, the libraries do give nice access to networking, threads, and so on. Thus, you're seeing Java catch on quite nicely in server applications.

Similarly, much of Perl's success comes from a surprisingly strong library, much of which is a compatibility layer over the Unix API, so that Perl apps are considerably more portable than in, say C. If you're doing the kinds of things for which the Perl library is strong, it's an excellent choice.

Thus, while your language looks fun, I would have been considerably more interested if you had posted a plausible plan for making sure its runtime and libraries were of consistently high quality.

Comments, posted 10 Dec 2000 at 04:16 UTC by nymia » (Master)

Actually, I have no comments about the syntax and semantics of Suneido. What I'd like to mention here are some of my ideas about intermediate languages. I think you know where I'm getting at, a language generated by your language that is capable of being compiled by a native compiler. What I'm looking at is not about language grammar and its built-ins, but, the ability of the language to generate libraries that are callable by other languages.

Just imagine the opportunity for a given language, say Suneido, where it can call a member function or even any function written in C, C++, Java, Python, Perl, VB, MyLanguage, YourLanguage, Etc. Morever, these languages can also call your methods and functions. That can only happen if we as language designers can come up with a way of building an intermediate language specification where native compilers can just parse the language and generate a native binary format. Is there anyone here interested in pursuing this idea?

Anyway, my only comments about the grammar of Suneido are too simple. Just add more production rules to the parser so you can add inheritance as well. Also, it would be more ok if you strictly target the abstract stack machine. That way, you'll prevent newbies from messing around with data scoping. Plus, you'll be able to add recursion in Suneido as easy as pushing and popping local vars off the stack. Another is try to provide a collection facility where any object of any class can be added into a collection.

I wish you good luck on your language.

Language mixing, posted 10 Dec 2000 at 04:29 UTC by hp » (Master)

nymia: Microsoft's .NET runtime does exactly the language mixing you're talking about. You can write objects in any language, then use them from any language.

Intermediate languages, posted 10 Dec 2000 at 05:28 UTC by egnor » (Journeyer)

I think nymia is describing two things.

One is the notion of an intermediate language. Language front ends (for as many languages as you care to support) generate intermediate "bytecode" which is then translated to native code (for as many processors as you care to support). The advantage of this approach is that if you want to support N languages on M processors, you don't have to write NxM compilers. If the IL can be expressed in a file, it also makes it possible to distribute a single "binary" that can be compiled for the target processor.

While an IL is clearly a good idea in theory, its implementation is harder than you might think. UNCOL is a famous old failed effort in this area. The main challenge is that different languages are more different than you might expect, and have their own requirements on the optimizer and code generation system.

In practice, people implementing high-level languages often use C as an IL; Simon Peyton Jones once proposed "C--", a subset of C designed specifically for use as an IL. These days, the Java bytecode is often considered an IL (compilation happens at runtime via a JIT). The GNU Compiler Collection has had some success with its internal IL, called RTL.

Two, you're suggesting a way for code written in different languages to call each other. We have that today, in that almost every language can call C and be called by C. The problem is that you need a specific calling model; forcing two object-oriented languages to use a common C layer is constraining. There are a number of object-oriented interoperability layers; COM and CORBA are among the best known.

Any such layer is necessarily a "greatest common factor" solution, which means that the calling model in question can't capture the full richness of any particular language's features. (COM does not support exceptions, for example.) In many cases, the "impedance mismatch" may be large indeed; consider the issues when calling between purely functional or declarative languages and conventional imperative languages.

Microsoft .NET happens to provide both of the above, but the idea is hardly new -- I'm surprised you haven't heard of the systems I'm citing, or observed the limitations they must of necessity embrace.

Bytecode and virtual machine design is important, posted 10 Dec 2000 at 06:43 UTC by pphaneuf » (Journeyer)

Here is an interesting paper on the Inferno virtual machine, Dis. Stealing design from the Java VM is not a very good idea, considering it's amazingly poor performance, in large part due to its design.

That paper explains why a stack-based design is not very good.

In "The Practice of Programming" book (by Brian Kernighan and Rob Pike), they show a table comparing an implementation of an example program with some langages (C, two different C++ implementations, Java, AWK, Perl) on two platforms (PC running Windows NT and SGI R10000-based machine). The C implementation was clearly the fastest, with something along a third of a second running time, in 150 lines of code. The Java code was the slowest, at something like 9 seconds and only a bit less code (between 100 and 150 lines). C++ was around 1.7 second I think, and I don't remember AWK (over 2 seconds, but still clearly faster than Java!), but the real surprise was Perl: 1.0 and 1.8 seconds (on the two platforms), in 18 lines of code (the smallest by large amount), similar or faster than C++!

Credit a well-done virtual machine and intermediate code.

The speed of Perl is mostly due to its design, trying to do as much as possible in C code. They made sure you could apply a regex to a whole array in a single instruction, so that the array walking could be done in fast, optimized C code instead of done in Perl, for example.

Also, do not underestimate the free speed that inlining can give you. The dynamic linking can prevent that. What you can do is keep the dynamic linking, but add a way to statically link code together, but don't do it in the dumb C linker style (which just ties code together), make the linker analyze the code and optimize it further, letting it find places where the types are statically known and shortcircuiting the dynamic call for example (or even inlining the code, if small enough). The linker has to be "close" enough to the source code to have enough information at hand though.

IL Design -- The Ultimate Solution, posted 10 Dec 2000 at 12:09 UTC by moshez » (Master)

There is a good language which was a good IL 20 years ago: Scheme. Any language can be compiled to Scheme, and Scheme's semantics are rich enough to allow inter-calling between related languages.

Features Scheme has which makes it a very good language to compile code into:

  • lexical scoping built in
  • high-level macro facilities
  • garbage collection
  • continuations

The last one makes it much better then Java, say, as an IL: with continuations you can simulate exceptions, co-routines, iterators, threads, restartable exceptions, etc.

Scheme as an IL, posted 10 Dec 2000 at 13:20 UTC by cmm » (Journeyer)

yup, Scheme is often touted as a perfect IL, but so what I've seen so far suggests that this is true only in theory (or when implementing academia-oriented stuff where down-to-metal performance issues are not very important).

the following issues complicate using Scheme as a "production" IL:

  • for most languages, continuations are too powerful -- and continuation support has a price which many people don't want to pay. like not being able to use the stack straightforwardly, for example.
  • Scheme has manifest (aka "strong dynamic") typing model. this is actually my preferred typing model for lots of philosofical reasons, but it is bound to get in your way when you don't want all those tag bits around.

that said, I know that some people has implemented Java by translating to (extended) Scheme, with apparently pretty good results.

and the fact that the C-- effort has so far seen at least one major redesign (no, the project is not at all dead) is further evidence that a universal and performant IL is far, far from trivial.

Re: Toys Languages, posted 10 Dec 2000 at 14:43 UTC by hadess » (Master)

raph: I read the article and I was gonna say that.

apm: What is the goal of your language ? At one point all languages have a specific goal... I don't use anything apart from C and Perl nowadays. Calling Perl scripts from C programs, launching C programs from Perl script. C is fast, Perl is fast to write, what is your language good at ?

comments, posted 10 Dec 2000 at 14:49 UTC by apm » (Journeyer)

egnor wrote:

You're adding a point somewhere in the middle of the space populated by Java, ECMAscript, Python, Smalltalk, and so on. Unless I missed something, you're not introducing any interesting new features, just mixing and matching the attributes of other languages.

Given my goals, that's not so bad. I wanted something that would be familiar, and therefore easy to learn.

The obvious alternative would have been to use an available language such as Python or Ruby. When I started this project several years ago, that didn't seem as obvious as it might today.

raph wrote:

One other observation, though: in most applications these days, the language per se pales in importance compared with the surrounding runtime and available libraries. In almost all cases where a language has succeeded, it's because of the surround, not the language itself.

I agree, and that's actually where Suneido is coming from, although the article isn't about that. The language is a component of an integrated application development system including a database. The goal wasn't to break new ground with the language, but to have something more substantial than the "scripting" languages that are commonly supplied with databases.

nymia wrote:

What I'd like to mention here are some of my ideas about intermediate languages. I think you know where I'm getting at, a language generated by your language that is capable of being compiled by a native compiler.

Yes, that's an interesting area. I have thought about this, but have not done any work on it. Both C and Java VM would be interesting targets. Eifel is a good example of using C. However, it is not such a good route for an interactive development environment. I hadn't considered Scheme as an IL. It doesn't seem as "universal" as C.

Anyway, my only comments about the grammar of Suneido are too simple. Just add more production rules to the parser so you can add inheritance as well. Also, it would be more ok if you strictly target the abstract stack machine. That way, you'll prevent newbies from messing around with data scoping. Plus, you'll be able to add recursion in Suneido as easy as pushing and popping local vars off the stack. Another is try to provide a collection facility where any object of any class can be added into a collection.

Suneido does support inheritance, and recursion, and collections do allow adding values of any type.

Thanks for the comments everyone. I will continue to not take myself too seriously, and will cancel my plans to change my name to Guido Wall :-)

UNWIND-PROTECT ?, posted 10 Dec 2000 at 16:43 UTC by dan » (Master)

If you don't support finalizers on objects (which is fair enough, it seems that nobody ever gets it right anyway), you should seriously look at something like Common Lisp's UNWIND-PROTECT, or the `finally' arm of java's try/catch. That is, a construct with a body that is executed, and a `cleanup form' that is executed afterwards, no matter how the control flow leaves the body - even if the body throws an exception, the cleanup form should be executed before the exeption handler is run

Actually, this is still necessary even if you do have finalizers when the foreign resource is something like a network socket and needs to be released in a timely fashion (i.e. something else is waiting for it). Finalizers may not happen until the next gc

re. unwind-protect / finally, posted 10 Dec 2000 at 17:29 UTC by apm » (Journeyer)

Good point, several people have asked about this and I plan to add "finally" to Suneido's exception handling.

Also, I put some notes on weak references vs finalization in a diary entry. (See apm)

"finally" considered harmful, posted 10 Dec 2000 at 23:30 UTC by ncm » (Master)

It was the addition to Java of the "finally" clause that proved beyond doubt that its designers really didn't understand what exception handling is about.

The whole point of exceptions was to be able to ignore them everywhere else, and concentrate on them in one place. In Java, rather than collecting exception handling into one or a few clearinghouse modules, you end up scattering it throughout the system, cluttering up the logic about as much as with error-code return values.

Adding random features from other languages won't improve yours, particularly if you don't understand them or their architectural consequences.

huh?, posted 11 Dec 2000 at 00:31 UTC by cmm » (Journeyer)

ncm: what exactly is wrong with "finally", except that you don't like it?

it does the same thing that the destructors of automatic variables do in C++, basically, n'est pas?

FWIW, nobody has ever claimed that "unwind-protect" is a bad idea in Lisp.

(I'm assuming that Java's "finally" is analogous to Lisp's "unwind-protect". I may be wrong, 'cause I don't do Java and sometimes guesses about such things just don't work. if that is the case, sorry).

Uniform Access, posted 11 Dec 2000 at 05:19 UTC by Dacta » (Journeyer)

If a nonexistent member is accessed, Suneido will call a method with the same name with no arguments, if present. This allows you to change a data member to a method without having to alter code that uses it. (Bertrand Meyer calls this "Uniform Access" - a client should be able to access a property of an object using a single notation, whether the property is implemented by memory or computation.)

This is the biggest thing I liked about the Delphi Object Pascal language. It has a concept called "properties" where you can declare an object using syntax something like:

class TNewObject = class( TObject )
private
  fNumPeople : Integer;
public
  property NumPeople read fNumPeople write fNumPeople;
end;

Then, if you need to change how fNumPeople is calculated you can write:

class TNewObject = class( TObject )
private
  function getNumPeople: Integer;
  procedure setNumPeople( Value : Integer );  
public
  property NumPeople read getNumPeople write setNumPeople;
end;
and no class outside TNewObject needs to be changed.

You can also do things like having a read only property by not using a "write" keyword.

re. Uniform Access, posted 11 Dec 2000 at 15:14 UTC by apm » (Journeyer)

Doesn't C# have a similar feature? Not surprising since I believe it's the same designer as Delphi.

I'm not totally convinced that the benefits of "properties" justify adding another concept to the object model.

Something I have considered is providing a way to intercept / override setting members with a method. The end result would be functionally similar.

re. "finally", posted 11 Dec 2000 at 15:33 UTC by apm » (Journeyer)

I can appreciate that finally is unnecessary in C++ (and probably undesirable) since destructors can handle things like releasing resources better and cleaner.

However, in non-reference-counting garbage-collected languages (e.g. Java, Suneido) the equivalent to destructors - finalization, doesn't work all that well - there are a lot of problems with it. So "finally" or "unwind-protect" are perhaps more reasonable. Python and Ruby both have the equivalent of "finally".

An alternative in some circumstances is where you pass a block to a function that then aquires the resource(s), runs the block, and then releases the resource(s). (Kent Beck in Smalltalk Best Practices calls this pattern "Execute Around Method".) Presumably the block would be executed within an exception handler (e.g. try-catch, ensure) so the resources are released even if an exception occurs. Ruby uses this technique. Of course, you need something like blocks for this.

Re: Uniform Access, posted 11 Dec 2000 at 16:07 UTC by ingvar » (Master)

In CLOS, you can do something similar, by using something like:

(defclass my-class ()	; We inherit only the "standard class"
  ((my-slot :accessor my-slot))	; And we have a "set" and a "get" method
				; named "my-slot".
  )

From outside, you get a value by using (my-slot instance) and set it by using (setf (my-slot instance) new-value).

Now, assume we want to do something (say, sanity checkin, logging, wossname), we can change the class definition and define new access methods:

(defclass my-class ()
  ((my-slot :accessor secret-my-slot))
  )
(defmethod my-slot ((obj my-class))
 (let ((oldval (secret-my-slot obj)))
   (format t "Accessing my-slot in object ~a, value is ~a~%" obj oldval)
   oldval))

(defmethod (setf my-slot) (new-val (obj my-class)) (let ((oldval (secret-my-slot obj))) (format t "Setting my-slot (from ~a to ~a)~%" oldval new-val) (setf (secret-my-slot obj) new-val)))

It's (perhaps) slightly more verbose than the same in Delphi, but it is trivially doable.

finalize considered harmful!, posted 12 Dec 2000 at 09:09 UTC by taj » (Master)

Java programmers who use finalize liberally better rethink things a bit! finalize has none of the determinism that one expects in C++ - there is no real guarantee that things you depend on in the finally code are still true/available, and in implementations where they are, you might end up interfering with other GCable objects.

finalize, unlike C++ destructors, should be used as sparingly as gotos in C or C++, perhaps even less. Look at it this way - if your object is controlling a resource so important that you need to release it in finalize, it is probably better to release it manually in your code during a controlled cleanup() operation.

IMHO the only people who can look at finalize ever being the right thing to do are people implementing stuff with JNI or embedded systems folks using a VM with a fully deterministic GC.

Looks kind of similiar to elastiC, posted 14 Dec 2000 at 13:55 UTC by davidw » (Master)

Ever had a look at elastiC, at www.elasticworld.org? After a quick glance, the two look sort of similar.

Re:finalize considered harmful! (and finally considered harmful), posted 15 Dec 2000 at 00:56 UTC by Dacta » (Journeyer)

I don't think anyone disagrees with the need to be careful with the use of finalize.

You're not confusing the discussion on try..finally blocks with the only partially related issue of trying to use finalize like a destructor, are you?

I don't quite understand the issue with try..finally blocks. Is there a better way to guarrentee the release of some scarse resource (like database connections, say) than a construct like:

  getDatabaseConnectionFromPool()
  try
    do database stuff
  finally
    releaseDatabaseConnection
  end // try..finally block

Of course, you would have that wrapped in some kind of exception handling (probably so the exceptions all are handled in the same location), but the handling of the exceptions isn't really related to the fact that you always need to release that database connection (no matter if an exception occurs or not).

Most popular modern languages seem to support somethign like this - even the next version of VB is supposed to have it.

Why I don't like `finally', posted 20 Dec 2000 at 02:06 UTC by dan » (Master)

Since the discussion seems to have turned into a debate on the merits og Java's "finally" construct, let me add that I actually don't like the way that it's been made part of try/catch. try/catch is about handling exceptional conditions; finally is about resource release. The two things are different. You can quite legitimately want to release a resource while leaving the exception to be handled later (an example in Java might be that you want to call close() on a JDBC ResultSet and then drop into the general SQLException handler several levels up in the stack). This means catching SQLException and then throwing it again, which is a bit silly.

Compare CL, in which HANDLER-CASE and UNWIND-PROTECT are two separate forms for separate tasks.

New Advogato Features

New HTML Parser: The long-awaited libxml2 based HTML parser code is live. It needs further work but already handles most markup better than the original parser.

Keep up with the latest Advogato features by reading the Advogato status blog.

If you're a C programmer with some spare time, take a look at the mod_virgule project page and help us with one of the tasks on the ToDo list!

X
Share this page