Advertisement

Scripting Language Genesis

Started by November 01, 2004 07:28 AM
116 comments, last by cmp 20 years, 2 months ago
Quote:
Original post by umbrae
Quote:
If a feature is almost free, doesn't use up syntax that would be better used for something else, and isn't actively dangerous, you should put it in. In those kinds of cases, wondering whether or not people will want to use them is silly.

People who don't want to use them won't, and people who do want to use them will. If it turned out that nobody used them, which I very much doubt would be the case, you've hardly wasted any work, since all you've had to do is expose the method type that must already exist inside the engine.

I agree with you on some level, but what I don't want in a language is a cacophony of features. When I was reading about scheme I really liked it's philosophy. It's very minimalistic and doesn't add features even if they might be a bit useful. As I remember php started out as a very fast, small scripting language. But it has now grown, and uses up more resources.

The key phrase here being if a feature is almost free. A lot of what PHP can do doesn't come for free. In the case of methods, on the other hand, the data must already be stored. It's only a question of making it available to the scripter, and I think that makes it almost free.



For all its minimalism, Scheme has a problem: its minimalism. Because it lacks any kind of user-defined types, a module system, or exception handling, amongst other things, each Scheme implementation provides these terribly important things in a slightly different, and therefore incompatible, manner.



A good way to design a language is to make a minimalist core; something semantically similar to Scheme would be ideal, although it need not be syntactically similar. Then define a 'standard prelude'; this is automatically included in all programs. The prelude defines common control structures, data types and shorthand notation for useful declarations (of classes or multimethods, for example). Layered on top of that are the various components of the 'standard library', which are included only when requested.
Quote:

The point in question is whether to allow closures as a parameter for a method.
class list    // a normal function    void each(void(object value) m)// calling code, this is okaylist a = [1,2,3]a.each(an_object.a_method)// is this okay? (closures a replacement for a method?)a.each    system.println(value)

I'm in two minds about this, I think it is good, and makes sense - but I also think there needs to be a difference in syntax when defining closures. You could define both, and let overloading take it's course
class list    // this takes a function    void each(void(object value))    // this takes a closure    void each()(void(object value) m)

I think that there is too much ambiguity when defining closure accepting methods just by having the function definitions as the closure. Because this language is currently lined up to be a tabbed block language, it makes it harder because I think that something like this doesn't look right:
class base    // a closure accepting method    void method(integer i, object() m, boolean b)// calling that methodbase bb.method(4,    if (i > 7)        return new integer    else        return new decimal    , true)


How to indent it is not well defined, and it is hard to see what is happening. You could make a rule that method parameters always have to be at the back, but then you still need the ending ")". It is for this reason that I think that closure accepting methods need to have a different syntax, and I am considering only allowing one closure per method.

I think you have me confused.



Firstly, think about what would be in the method bodies of the two versions of list.each. They'd be exactly the same. In fact, if they weren't exactly the same, people would complain loudly, because the class would never behave as expected. If two functions must contain the same code, that's often a clue that there should only be one function.



Secondly, I don't suggest putting the code in the function call. Instead, I'd put it after the function call, in the manner of a normal indented block.
class base  // a closure accepting method  void method(integer i, boolean b, object() m)// calling that methodbase bb.method(4,true)    if (i > 7)        return new integer    else        return new decimal

This gets weird if you have something like the following:
bool is_it? (object() m)if is_it?()        return true    print "It is."else    print "It isn't."

CoV
Quote:
Original post by Mayrel
A good way to design a language is to make a minimalist core; something semantically similar to Scheme would be ideal, although it need not be syntactically similar. Then define a 'standard prelude'; this is automatically included in all programs. The prelude defines common control structures, data types and shorthand notation for useful declarations (of classes or multimethods, for example). Layered on top of that are the various components of the 'standard library', which are included only when requested.

True, that's what I am hoping to do. The 'core' is, objects - packages - methods - variables - classes, the addition is the syntax, how to define lists, integers, closures etc.

Quote:
I think you have me confused.



Firstly, think about what would be in the method bodies of the two versions of list.each. They'd be exactly the same. In fact, if they weren't exactly the same, people would complain loudly, because the class would never behave as expected. If two functions must contain the same code, that's often a clue that there should only be one function.



Secondly, I don't suggest putting the code in the function call. Instead, I'd put it after the function call, in the manner of a normal indented block.
...code...

I'm sold. Just a few things. What about multiple method parameters? I can see that it would be easy if the method only had one method parameter, but if it had two, would it still be allowed to have a closure? Even with one method variable, will it have to be the last parameter for it to work as a closure?

I think that if the last parameter is a method pointer, and only if, it is allowed to be a closure.

Edit: What in the world should I do about exceptions... Forget them? Kill the VM? Continue, pretending nothing happened? Write syntax to handle them... and have more work writing this vm and language?
Advertisement
when i was thinking about closures a problematic aspect occured to me, if you say sth. (btw. sth. means something, it's a abbreviation commonly used in our english books) like lamda{ ?1.do_it() } how does the compiler know of what type ?1 is?
he can't know, so the lamda function should be a template class at least in my current language design, but template classes are incomplete types, so you can't create an object of them, ergo lamda function shouldn't really exist.
i think this problem would only disappear if you would use a dynamicly typed language, or you would define the argument's type.
Quote:
Original post by umbrae
Quote:

Firstly, think about what would be in the method bodies of the two versions of list.each. They'd be exactly the same. In fact, if they weren't exactly the same, people would complain loudly, because the class would never behave as expected. If two functions must contain the same code, that's often a clue that there should only be one function.



Secondly, I don't suggest putting the code in the function call. Instead, I'd put it after the function call, in the manner of a normal indented block.
...code...

I'm sold. Just a few things. What about multiple method parameters? I can see that it would be easy if the method only had one method parameter, but if it had two, would it still be allowed to have a closure?

I don't see why not. But that might be odd. Because of your syntax, the only way I can think of to seperate things out would be ugly.
void myif (bool test, void () when_true, void () when_false)  if test    when_true()  else    when_false()// I've used a 'staggered' block to create two blocks.myif x == 12    print "x == 12"  print "x != 12"

Given this ugliness, if you can have more than one 'closure' it might be worth going back to your original idea.
void myif (bool test) { when_true} else { when_false }  if test    when_true()  else    when_false()myif x == 12  print "x == 12"else  print "x == 12"

Quote:

Even with one method variable, will it have to be the last parameter for it to work as a closure?

It wouldn't have to be. But it would probably be less surprising that way.
Quote:

Edit: What in the world should I do about exceptions... Forget them? Kill the VM? Continue, pretending nothing happened? Write syntax to handle them... and have more work writing this vm and language?

Killing the VM is unforgivable. Pretending nothing happened is almost always a bad idea. I think that you can have exception handling almost for free. If we assume, for simplicities sake, that you can only have one try clause in a function, then you merely need to have a flag on each stack frame which indicates whether or not that frame has an exception handler. When an exception is thrown, travel down the stack until you get to a handler and execute it.
CoV
Quote:
Original post by cmp
when i was thinking about closures a problematic aspect occured to me, if you say sth. (btw. sth. means something, it's a abbreviation commonly used in our english books) like lamda{ ?1.do_it() } how does the compiler know of what type ?1 is?
he can't know, so the lamda function should be a template class at least in my current language design, but template classes are incomplete types, so you can't create an object of them, ergo lamda function shouldn't really exist.
i think this problem would only disappear if you would use a dynamicly typed language, or you would define the argument's type.

I was planning to not have lambdas as such, just my half-hearted closures. All statically typed - compiler errors only.

I think that lambda functions need to be a core part of the language in order to work, and it would be hard to do with C++. I think you would have to do some nasty stuff in order to get it to work.
Quote:
Quote:
Even with one method variable, will it have to be the last parameter for it to work as a closure?
It wouldn't have to be. But it would probably be less surprising that way.

I think that's the way I will do it. Also, are there many uses for a method that takes more than one closure? The things that I was thinking about would work just as well having either two nested closures, or one closure after another (filter + map, etc). I was thinking that the loss would not be too great only having one closure per method. It would also make the syntax a lot easier.

Quote:
Killing the VM is unforgivable. Pretending nothing happened is almost always a bad idea. I think that you can have exception handling almost for free. If we assume, for simplicities sake, that you can only have one try clause in a function, then you merely need to have a flag on each stack frame which indicates whether or not that frame has an exception handler. When an exception is thrown, travel down the stack until you get to a handler and execute it.

At least one language that I have used had method level exception handling, no block level handling. If I implemented exceptions using closure syntax, I think it would work.
// not sure about the object involved - exception seemed okayexception.handle    if (e == exception.dividebyzero)        system.println("error : divide by zero")        // continue executing        return -1    else if (e == exception.nullpointer)        system.println("fatal error : null pointer")        // can't continue        exception.fatal.throw()
Quote:
Original post by umbrae
I was planning to not have lambdas as such, just my half-hearted closures. All statically typed - compiler errors only.

Lambdas can be statically typed.
Quote:

I think that lambda functions need to be a core part of the language in order to work, and it would be hard to do with C++. I think you would have to do some nasty stuff in order to get it to work.

Perhaps you mean something entirely different by lambdas. A lambda is an anonymous function. You can create it in much the same way as you'd create a named function. It's no problem.
Quote:

Quote:
Killing the VM is unforgivable. Pretending nothing happened is almost always a bad idea. I think that you can have exception handling almost for free. If we assume, for simplicities sake, that you can only have one try clause in a function, then you merely need to have a flag on each stack frame which indicates whether or not that frame has an exception handler. When an exception is thrown, travel down the stack until you get to a handler and execute it.

At least one language that I have used had method level exception handling, no block level handling. If I implemented exceptions using closure syntax, I think it would work.
// not sure about the object involved - exception seemed okayexception.handle    if (e == exception.dividebyzero)        system.println("error : divide by zero")        // continue executing        return -1    else if (e == exception.nullpointer)        system.println("fatal error : null pointer")        // can't continue        exception.fatal.throw()

I don't like that. [smile]
try    ... do something..catch (sys.DivideByZeroException e)    system.println("error: divive by zero")    // Rather than a magic '-1' meaning continue, we    // tell the exception to resume where it left off.    e.resume();catch (sys.NullPointerException e)    system.println("fatal error: null pointer")    // Generally, rethrowing an error is better than throwing    // an entirely new error.    e.throw()
CoV
Advertisement
Quote:
Original post by Mayrel
Quote:
Original post by umbrae
I was planning to not have lambdas as such, just my half-hearted closures. All statically typed - compiler errors only.

Lambdas can be statically typed.

True.
Quote:
Quote:

I think that lambda functions need to be a core part of the language in order to work, and it would be hard to do with C++. I think you would have to do some nasty stuff in order to get it to work.

Perhaps you mean something entirely different by lambdas. A lambda is an anonymous function. You can create it in much the same way as you'd create a named function. It's no problem.

I was thinking about lambdas that aren't statically typed - the lambda can be called with any object. Statically typed lambdas wouldn't have this problem of course.
Quote:
Quote:
// not sure about the object involved - exception seemed okayexception.handle    if (e == exception.dividebyzero)        system.println("error : divide by zero")        // continue executing        return -1    else if (e == exception.nullpointer)        system.println("fatal error : null pointer")        // can't continue        exception.fatal.throw()

I don't like that. [smile]
try    ... do something..catch (sys.DivideByZeroException e)    system.println("error: divive by zero")    // Rather than a magic '-1' meaning continue, we    // tell the exception to resume where it left off.    e.resume();catch (sys.NullPointerException e)    system.println("fatal error: null pointer")    // Generally, rethrowing an error is better than throwing    // an entirely new error.    e.throw()

I'm not in favour of using try blocks, I think that exceptions thrown in the whole method should be caught, or not at all (ie it's your method, you should be responsible for the exceptions thrown in it). How about:
.. code here ..catch (exception.dividebyzero e)    system.println("error: divive by zero")    // tell the virtual machine to continue    e.resume()    // or actually return from the function with a certain value (don't resume)    return 0catch (exception.nullpointer e)    system.println("fatal error: null pointer")    // Pass this error onto the next method (if it wasn't this methods fault    // eg a null parameter passed to it - otherwise we should handle it)    e.throw()    // or we made a really bad mistake and cannot continue (incorrect algorithm,    // the object is in a unrecoverable state, etc.)    exception.fatal.throw()

How do you resume from a divide by zero? By making the result zero?
no, you would simply say that a divide by zero throws a System.DivideByZero exception, wich then would have a throw or rethrow method.

btw.: it would be at least methodicaly interesinting, if every mehtod, since it is an object, would habe method named install_handler or catch, wich would accecpt a closures. this way you would save another keyword ;)
but it would be a bit hard to use, since you would have to define ever handler in front of the code.

method()
self.install_handler
if ?1 is System.DivideByZero
?1.rethrow()






Quote:

I was thinking about lambdas that aren't statically typed - the lambda can be called with any object. Statically typed lambdas wouldn't have this problem of course.

me too.
Here's my language ideas, close to the ones of GameMonkey, but tweaked to something I like better.

It is a case sensitive language that comes close to C in syntax, with curly braces for code blocks, sharp braces for table access as well as table specific syntax and paranthesis for function calling.

Assignments (= += -= *= /= ^= &= |=) to variables can not be made inside expressions - in other words, the lvalue must be the first variable in the statement. The = operator have multiple other meanings when not used in this context, such as in named function parameters and list creation (see the examples below).

Right now I'm planning on NOT having variables that are global to a script and it's funtctions, since that complicates the fact that functions are stored in variables and can be called from any script. In other words, a function can only access universal variables that are global to all scripts, it's parameters, it's own local variables and if applicable, it's mother object's members.

It will be compiled to bytecode and run by a virtual machine. The virtual machine will take care of memory handling using simple reference counting (since there are no pointers in the language).

The commands will be a subset of C plus the following commands: foreach(table, value [, key]), with(object). if(expression, expr_if_true, expr_if_false) will replace () ? () : () and dowhile(expr) will replace do {...} while(expr).

// Named parameters (optional of course, but very handy in some situations)npc = NPC.create(    name = "Christoffer",    x = 23,    y = 25,);// Native table syntaxmyobj = [    a = 10,    b = 15,    // Functions are stored in variables    add = function(/* params here */)    {        // Easy syntax to access memeber variables        return .a + .b;    }];// Functions can return tables[a, b, c] = someFuncionThatReturnsATableWithThreeElements();// And of course, it can be combined to do multiple // assigments in one statement (x = 10; y = 30; z = 20;)[x, y, z] = [10, 30, 20];
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.
void myif (bool test) { when_true} else { when_false }  if test    when_true()  else    when_false()myif x == 12  print "x == 12"else  print "x == 12"

you could also introduce a block keyword:
void myif(bool test, void() when_true, void() when_false)   if test      when_true()   else      when_false()myif x == 12  print "x == 12"  print "x != 12"or:myif x == 12  block    print "x "    print "== "    print "12"  block    pring "x != 12"

i have to admit the example is silly, but if you would say, that arguments are either sperated by commata or are shown as sublock of a function call, you could rip allmost everything out of the language, except methods.
if would be defined as if( bool test, void() when_true ) and as if( bool test, void() when_true, void() when_false).
I don't remeber it exactly but i think lisp does quite the same.

This topic is closed to new replies.

Advertisement