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]