C&C on a language design

Started by
14 comments, last by Joakim_ar 17 years, 7 months ago
The following is a bit detailed, please skip down to the considerations if you're not up for reading all of it. I am very interested in programming languages and programming paradigms. From time to time I've been designing and implementing small scripting languages mostly for the fun of it. So far, none of the scripting languages I've actually got around to implement have been languages that I'd actually want to use later; however, I'm currently designing a general purpose dynamic scripting language that I hope will be of use to me and possibly others as well. The language supports multiple programming paradigms, namely object oriented, procedural, functional and possibly aspect oriented programming to some degree. These days I think it goes without saying that it's garbage collected. The most important construct in the language is the closure, which is defined like this (I think I stole the syntax from somewhere):

{|argument1, argument2| ... }
Where ... is the code of the closure. The value of the last statement in the closure is the return value. The language has no keywords; there are a number of closures that are available from the beginning however, including the if-closure:
# This is a comment
# syntax: if(condition, thenClosure, elseClosure)
if(x < 23, {
    y = -1
}, {
    y = 1
})
# or functional style:
y = if(x < 23, {-1}, {1})
Implementing 'else if' seems impossible using this scheme, hence consideration 1 below. The language does not support any other constructs containing code than the closure; in particular, it doesn't support regular functions. Instead of using a function, you'd do this:
fib := {|n| if(n < 2, {1}, {fib(n-2) + fib(n-1)})}
So fib now contains a closure that takes n as an argument, and can be called like this:
fib(7) # result = 13 
This has one important side effect, which leads to consideration 2. Since the language is dynamically - but strongly - typed, there are a number of runtime errors that may occur. I'd like to be able to handle these in the language, and exceptions is a natural way to handle this. This will require another couple of built in closures, namely:
throw(MyException()) # throws an exception
try(closure, handlerClosure) # catches exceptions, ie.
try({
    ...
}, {|exception|
    if(exception.isA(SomeException), {
        ...
    }, {
        throw(exception)
    })
})
Checking if an object is of some class doesn't seem very elegant, which leads me to consideration 3. Objects and classes are supported through another built-in closure:
MyClass = class(SuperClass, constructorClosure)
This closure constructs an object of the SuperClass type and passes it to the consructorClosure for modification (addition and overriding of methods and member variables), ie.
Vector = class(Object, {|self, x:0, y:0|
    self.add = {|vector| Vector(self.x + vector.x, self.y + vector.y)}
    self.addToSelf = {|vector|
        self.x += vector.x
        self.y += vector.y
    }
    # etc.
})
Vector is now a class - that is, a closure that when applied will return an object of type Vector. v = Vector(3, 5) v.addToSelf(Vector(0, 1)) It should be noted that a class can only inherit from one other class, and that there is no notion of interfaces. A problematic side effect of this approach is that there is no way to call the constructor of the parent class with custom arguments from the new constructor, which is consideration 4. The last thing that I'm going to talk about are the scoping rules. As you probably know, a closure has access to any enclosing scopes; in this language in particular, it has write access. The problem is, if you're assigning to a variable in a closure, how does the computer figure if you ment to assign to variable local to this scope or a variable from one of the enclosing scopes? This is consideration 5. Considerations These are things I'm having trouble deciding on or coming up with. I'd appriciate comments and critisism on the purposed solutions (if any) as well as the language in general. 1) Implementation of 'else if'. I'm not sure how to go about this at all. It's not strictly necessary of course. 2) Implementation of a return statement. One solution is to ignore the return statement alltogether and force the user to model the control flow so that it'll allways reach the last line. I'm not sure I want to to that, but since you'll often be using a closure inside a closure inside that closure that is a function in your mind, the program needs a clue on where to return from. This could be done by throwing a ReturnException with the return value that any closure wrapped by the function closure automatically catches, ie.:
function := {|closure|
    {|*args|                 # the * syntax is for a variable number of args
        try({
            closure(*args)   # the * syntax here is to expand the args list into arguments
        }, {|exception|
            if(exception.isA(ReturnException), {
                exception.returnValue
            }, {
                throw(exception)
            })
        })
    })
}
return := {|value| throw(ReturnException(value))}
# The resulting syntax for creating a function would be:
f = function({|a, b|
    return(100 * a - b)
})
3) Try/catch-syntax. A 'case of' pattern matching construct like they have in SML would be nice, but then again, quite hard to implement. Any ideas? 4) Calling the non-default constructor of the parent class. It's not strictly necessary to be able to do this, but if it can be done without killing the syntax, it would be a wonderful ability. On a side node, I think I'm going to make objects's member variables write protected outside of it's methods, but making everything fully readable (and callable of course). Does this seem like a good idea to you? 5) Local variables vs. variables from enclosing scopes. My solution to distinguishing these two is to require that you use a special assignment operator the first time you assign to a variable (and thereby create it). The scope that this operator is used in is the scope of the variable, ie.
x := 99
{
    x := 10
    print("Inner closure x = " + x.toString())
    # prints "Inner closure x = 10"

    # x has already been 'declared', so now you 
    # have to use the normal assignment operator.
    x = 20   
    print("Inner closure x = " + x.toString())
    # prints "Inner closure x = 20"

}() # <- call this closure immediatly

print("Outer closure x = " + x.toString())
# prints "Outer closure x = 99"
Does this seem like a good or a bad approach? Is there any better alternatives you can think of? I'd also be happy to discuss any of the missing details of the language (like loops) if anybody is interested. Thank you for your comments and critisism! Edit: I was possibly a tad sleepy when I typed this. The 'consideration' references were all messed up! Fixed. I hope. [Edited by - Joakim_ar on August 21, 2006 10:08:57 AM]
In case you were wondering what to put in your next christian game; don't ask me, because I'm an atheist, an infidel, and all in all an antitheist. If that doesn't bother you, visit my site that has all kinds of small utilities and widgets.
Advertisement
I don't think you're using 'closure' right. A closure is a specific instance of a block with bound variables and implies lexical scoping. I don't see you taking advantage of that anywhere... The syntax is a little weird to me, too. There are a lot of places where you need to "double nest" things (put them in parentheses and braces).
Thank you for the ciritisism :)
Yes the double nesting is a bid weird, there's no denying it. It does simplify the actual language a great deal though (very few language constructs).

On the closure side, I am actually taking advantage of it in the class example:
Vector = class(Object, {|self, x:0, y:0|    self.add = {|vector| Vector(self.x + vector.x, self.y + vector.y)}    self.addToSelf = {|vector|        self.x += vector.x        self.y += vector.y    }    # etc.})
In the addToSelf method, self is from the outer scope, and as it is a closure, it will keep 'knowing' self as long as the closure exists. I believe this defines a true closure.
In case you were wondering what to put in your next christian game; don't ask me, because I'm an atheist, an infidel, and all in all an antitheist. If that doesn't bother you, visit my site that has all kinds of small utilities and widgets.
Okay, I see. I like how the closure mechanism is also an anonymous function.
A possible solution to the 'else if' problem:

Instead of making the 'else if' mechanism related to the if closure, another closure that supports multiple alternatives can be introduced, with this syntax:
alternatives({condition1}, {action1}, {condition2}, {action2}, ...)

The first condition will be evaluated, and if it's true, action1 will be executed. Otherwise, it checks condition2 and executes action2 if it's true. This is repeated until there are no more condition/action pairs. The resulting syntax would be:
alternatives({x < 100}, {    print("x is less than one hundred")}, {x == 100}, {    print("x is equal to one hundred")}, {x > 100}, {    print("x is greater than one hundred")})

An unconditional else branch is obtained by replacing the condition with {true}. What do you think of this approach? And what should happen if there are no valid alternatives?

And a possible solution to the try/catch problem:
try({action}, ExceptionClass1, {action1}, ExceptionClass2, {action2}, ...)

It'll see if there's an exception thrown of the class ExceptionClass1, and if there is, execute action1. If not then proceed to ExceptionClass2/action2 and repeat, etc.
try({    # do some file input (kinda inspired by ruby,     # not necessarily the final approach)    readFile("test.txt", {|f|        while(!f.eof(), {            print(f.readLine())        })    })}, FileNotFoundException, {|e|    print("There was an exception: " + e.toString())})

I'm still very eager to read any comments, suggestions or critique!
In case you were wondering what to put in your next christian game; don't ask me, because I'm an atheist, an infidel, and all in all an antitheist. If that doesn't bother you, visit my site that has all kinds of small utilities and widgets.
Two other useful closures:
# A case/switch like mechanism# if x = 0 then ... else if x = 1 then ... else ...case(x,    0, {},    1, {},    2, {},    {})# A closure that allows the user to execute# specific code based on classdispatch(x,    Vector, {},    String, {},    Number, {},    {})
In case you were wondering what to put in your next christian game; don't ask me, because I'm an atheist, an infidel, and all in all an antitheist. If that doesn't bother you, visit my site that has all kinds of small utilities and widgets.
Have you looked at Smalltalk? Or Haskell/O'Caml/etc. ?
Nope, I invented the term "closure" all by myself.

...

It lends from a great deal of languages; all languages does this - when Sun wrote the Java specification, did you ask them if they had taken a look at C++?*

In fact, I think it's quite a discouraging comment, but if you meant to provide constructive critisism, feel free to clearify.

* Not implying that this language will have a fraction of Javas significance.
In case you were wondering what to put in your next christian game; don't ask me, because I'm an atheist, an infidel, and all in all an antitheist. If that doesn't bother you, visit my site that has all kinds of small utilities and widgets.
Another possible solution to the 'else if' problem (I don't know wether it works or not):
y = if(x < 0, {-1}, if(x>0, {1}, {0}) )
As Zahlman said, I recommend you look at Smalltalk.

This topic is closed to new replies.

Advertisement