FAQFAQ   SearchSearch   MemberlistMemberlist   UsergroupsUsergroups   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

December 14th -- Deeper recursion, slice-assigning arrays

 
Post new topic   Reply to topic     Forum Index -> MiniD
View previous topic :: View next topic  
Author Message
JarrettBillingsley



Joined: 20 Jun 2006
Posts: 457
Location: Pennsylvania!

PostPosted: Fri Dec 15, 2006 6:14 pm    Post subject: December 14th -- Deeper recursion, slice-assigning arrays Reply with quote

Fixed
  • MDState.printStack() now uses ?s to print out the stack values to avoid std.format errors from printing out strings.
  • MDState.startTraceback() no longer adds a spurious debug location to the traceback, so the point of error is no longer reported twice.


Changed
  • When slice-assigning an array, if the value that is being assigned to the slice is another array, the values will be copied out of the other array instead of the array itself becoming the value. That is, writing something like
    Code:
    local a1 = [1, 2, 3, 4, 5];
    local a2 = [6, 7, 8];
    a1[1 .. 4] = a2;

    Will end up with a1 containing [1, 6, 7, 8, 5], since the values were copied out of a2. The length of the slice and the length of the source array must match, as in D.
  • The interpreter now "unrolls" function calls, so that a function call in script no longer equals a function call natively. This way the script recursion depth has been increased greatly, since it's usually no longer limited by the call stack depth of the native code. I've tested it with calls to native functions, exceptions, deep recursion etc. and it seems to hold up, so hopefully it's good.
  • MDValue.opCmp no longer throws an error if you try to compare ints with floats. It'll just cast the int to a float and compare them. Because of this, it's no longer a (stupid) error to compare ints and floats in MiniD.


Added
  • Added a table.each function, which allows for callback iteration over a table. With this method, you can iterate through a table by using a callback method that takes three parameters: the table being iterated, the key, and the value. This should be slightly faster than iterating using a foreach loop, but you sacrifice some expressiveness. Additionally, you can "break" out of the loop by returning 'false' from the callback. Example usage:
    Code:
    local t = {x = 1, y = 2, z = 3};
    local num = 0;

    t:each(function(table, k, v)
    {
       writefln("table[", k, "] = ", v);

       ++num;
       if(num == 2)
          return false;
    });


    This will output:

    Code:
    table[x] = 1
    table[y] = 2


    Since the callback returns false when it's iterated through two elements, it doesn't iterate over the third.


That's all.

Oh, yeah. I plan on making some rather major changes to MiniD in the next couple of weeks. As in a module system, and possibly an end to the colon method syntax!
Back to top
View user's profile Send private message
r.lph50



Joined: 27 Nov 2006
Posts: 21
Location: New Zealand

PostPosted: Mon Dec 18, 2006 3:27 am    Post subject: Reply with quote

Pretty cool Very Happy

The example,

Code:
t:each(function(table, k, v)
{
   writefln("table[", k, "] = ", v);

   ++num;
   if(num == 2)
      return false;
});


is much like how I use javascript in my day job (which I like-ish). But with your syntax changes, have you thought about doing ruby style anonymous functions/closures (i.e. what ruby people call blocks/procs)?
Back to top
View user's profile Send private message AIM Address
JarrettBillingsley



Joined: 20 Jun 2006
Posts: 457
Location: Pennsylvania!

PostPosted: Mon Dec 18, 2006 1:34 pm    Post subject: Reply with quote

That's a really cool feature that I'd love to implement, but I haven't settled on an unambiguous syntax for it that fits in with the rest of the language. Maybe something like:

Code:
t:each()(table, k, v)
{
    // blah
};


The Ruby-style "parameters in pipes inside the braces" doesn't really fit in with the rest of MiniD, but the problem with the above syntax is that it'd be parsed as two function calls, followed by the anonymous function's code block, and the second "function call" would have to be converted into the parameter list for the anonymous function. Really ugly, though the syntax is the nicest-looking and least-obtrusive that I've been able to come up with.
Back to top
View user's profile Send private message
r.lph50



Joined: 27 Nov 2006
Posts: 21
Location: New Zealand

PostPosted: Mon Dec 18, 2006 10:38 pm    Post subject: Reply with quote

The problems of having bitwise operators?

Is this too crazy:
Code:

t:each { (table, k, v) =>
    // blah
};
or
Code:

t:each( { (table, k, v) =>
    // blah
});

its akin to what scala and C# 3.0 do (seeing as they are in the same boat as minid in not having perl style hashes, and I guess not wanting to use ->)
Back to top
View user's profile Send private message AIM Address
JarrettBillingsley



Joined: 20 Jun 2006
Posts: 457
Location: Pennsylvania!

PostPosted: Mon Dec 18, 2006 11:32 pm    Post subject: Reply with quote

Hm. I'm not entirely fond of the syntax (=> seems to be a carryover from some functional languages, which I don't really get along with Wink ), but it seems pretty simple to parse. As long as the parameters come inside the braces, it's completely unambiguous as to when a trailing function (block) is being used, even if the pipes are used a la Ruby (since the compiler knows that it's parsing a trailing literal, the bitwise or doesn't come into play). But I'm not sure if I like that syntax Smile The other option would be to have some kind of symbol after the called function to indicate that a trailing literal is coming up, i.e.

Code:
t:each()::(table, key, value)
{
    // fooo
};


(The 'each' must still have those empty parens so that it gets parsed as a function call, since parens can't be elided in MiniD as in Ruby.)

See, the optimal version in my mind would look a lot like a native language construct, like a loop or something.

Code:
t:each(table, key, value)
{

} // no semicolon here


But I'm not sure how easy that is to parse, since (key, value) is really the parameter list for the literal, and right now it'd be parsed as the argument list for the t:each method call. Additionally, since it's not really a true language construct, it's not entirely obvious that break and continue won't work in the block, and that returning in the block will return from the literal, not the enclosing function, so this syntax may be a little misleading.

So how's t:each()::(table, key, value){}; ?It's pretty unobtrusive, but makes it obvious enough that something weird is going on so that it can't be mistaken for a construct that can be broken/continued. How would this fit into the grammar.. it'd just be an optional ending to call expressions.

Thoughts?
Back to top
View user's profile Send private message
r.lph50



Joined: 27 Nov 2006
Posts: 21
Location: New Zealand

PostPosted: Tue Dec 19, 2006 1:14 am    Post subject: Reply with quote

it would be kinda cool if break and continue worked in the block instead of your planned use of return (break becoming the return false, continue has nothing to map onto as yet?).

Anyway, that was actually quite an interesting/cool looking solutions, but the thought of mine that dominated the rest, is that with :: after the parameter list, you get limited to a ruby style block (which I guess is what I initially mentioned), instead of being able to use multiple anonymous functions as parameters themselves. This maybe a choice you want to make (perhaps for implementation's sake - sorry I can't offer help as of yet..), but it'd be nice to see how often in real usage one would want to pass in a anonymous function versus named functions versus blocks. Yeah, I don't know.
Back to top
View user's profile Send private message AIM Address
r.lph50



Joined: 27 Nov 2006
Posts: 21
Location: New Zealand

PostPosted: Tue Dec 19, 2006 1:45 am    Post subject: Reply with quote

erm, none of which need stop you from going ahead
Back to top
View user's profile Send private message AIM Address
JarrettBillingsley



Joined: 20 Jun 2006
Posts: 457
Location: Pennsylvania!

PostPosted: Tue Dec 19, 2006 4:24 pm    Post subject: Reply with quote

Quote:
it would be kinda cool if break and continue worked in the block instead of your planned use of return (break becoming the return false, continue has nothing to map onto as yet?).


Now that I've thought about it some more, I'm inclined to agree with you. This would have to be made a sort of convention throughout the libraries. I guess it's no different from the kind of goofy way opApply is implemented in D (in fact, it's exactly the same, since this is how foreach loops work in D -- the loop is converted into a delegate literal, with the breaks and continues and gotos changed into return statements that return various codes).

Continue would simply become returning anything other than false -- it would just skip to the next iteration.

Quote:
with :: after the parameter list, you get limited to a ruby style block (which I guess is what I initially mentioned), instead of being able to use multiple anonymous functions as parameters themselves. This maybe a choice you want to make (perhaps for implementation's sake - sorry I can't offer help as of yet..), but it'd be nice to see how often in real usage one would want to pass in a anonymous function versus named functions versus blocks.


Well.. I guess it could be changed to a postfix expression all its own, so you can do things like:

Code:
t:foobar(k, v)
{
    // block 1
}::(a, b, c)
{
    // block 2
};


I.e. chaining the :: function literals, and both the (k, v) and (a, b, c) literals will be passed as parameters to t:foobar. It would be equivalent to

Code:
t:foobar(function(k, v)
{
    // block 1
}, function(a, b, c)
{
    // block 2
});


But I really don't know how often you need to pass multiple function literals into a function, and if you do, I don't know if the chained :: syntax would be very.. enlightening as to what's going on. That, and what if you need to pass a function literal into a function literal that's being passed to a function? Scary.

I think I'd make it so that only one :: is allowed for a call expression, and the function literal that follows is treated specially -- changing continues and breaks into returns.

There are some issues, though. One, what about return statements? As it is now, it's obvious that returning from the literal just returns from the literal. But if I were to implement the new syntax, changing breaks and continues and whatnot, what about return statements?

Code:
function search(t, val)
{
   t:each::(table, k, v)
   {
      if(v == val)
         return true;
   }
   
   return false;
}


This is misleading, because it looks like the "return true" will return from 'search' but it really only returns from the literal.

The other issue, which is the main reason foreach works the way it does now, is that with the :: syntax, you're creating a new closure every time that expression is evaluated. For often-called procedures or for nested loops, this is very time- and memory-hogging indeed.

Code:
function search(arr, val)
{
   // assuming arr is a 2-d array

   // This outer loop is instantiated every time 'search' is called.
   arr:each::(a, k, v)
   {
      v:each::(a2, k, v)
      {
         // This body is instantiated as a new closure with
         // every iteration of the outer loop.
         
         if(v == val)
            return true;  // assuming this works
      }
   }

   return false;
}


With foreach loops, there is much less garbage created.

One solution (believe me, I ran over these issues when I was deciding how foreach loops would work) would be to make those :: literals a special kind of closure, one which is instantiated once when the code is loaded into memory and is just reused over and over. The problem with this is that it greatly increases the complexity of initialization of the loaded code, and there's the issue of where the hell those "static" closures would be stored.

So.. I could introduce this new syntax, but make it clear that there are performance considerations when using it.
Back to top
View user's profile Send private message
r.lph50



Joined: 27 Nov 2006
Posts: 21
Location: New Zealand

PostPosted: Wed Dec 20, 2006 4:29 am    Post subject: Reply with quote

Here is my slightly tired/confused thinking (I am in a UTC+13 timezone):

What was I thinking originally was it would be nice to have a smaller, more generic syntax for both ruby style blocks and anonymous functions (static closures). That didn't come out in my last post very well, and so I am like I don't know what I was thinking Confused I'm having a bit of a mismatch between my thinking and with D.

Anyway.. what about saving :: blocks for special casing (every function can have at most one and they are more limited than static closures, sorta like a foreach with more possible variables (at least in future)), and use
Code:
function(param) { body }
for everything else. Now, is the :: block special casing necessary? If the use case is like most ruby blocks, it would for when you want to inject a piece of code into a limited scope. Or for iterating, but to make it more applicable than just foreach, some kind of yield function would be needed. Until then, they aren't really all that great.

example use case to see if it adds something to foreach...
Code:
foreach(i, v; [1, 2, 3, 4, 5], "reverse")
    write(v, " ");
becomes
Code:
local a = [1, 2, 3, 4, 5]
a:reverse::(i, v) {
    write(v, " ");
}


Please excuse my ignorance about implementation issues and I hope this hasn't been a waste of time.
Back to top
View user's profile Send private message AIM Address
r.lph50



Joined: 27 Nov 2006
Posts: 21
Location: New Zealand

PostPosted: Wed Dec 20, 2006 4:36 am    Post subject: Reply with quote

As for return.. is it still orthogonal if return returns from the enclosing function in ::blocks so
Code:
t:search(val)
{
    t:each::(k, v)
    {
        if(v == val)
            return true;
    }
    return false;
}
works as one would expect. (Hrm, does it look too much like t:each is an inner function?)
Back to top
View user's profile Send private message AIM Address
JarrettBillingsley



Joined: 20 Jun 2006
Posts: 457
Location: Pennsylvania!

PostPosted: Wed Dec 20, 2006 8:20 pm    Post subject: Reply with quote

Quote:
If the use case is like most ruby blocks, it would for when you want to inject a piece of code into a limited scope. Or for iterating, but to make it more applicable than just foreach, some kind of yield function would be needed. Until then, they aren't really all that great.


I think you're right. Maybe all this stuff would be better handled with a generator/coroutine type. That way, you'd get the benefits of faster table iteration like with t:each(), but you could have the benefits of a foreach loop.

I know Squirrel has both -- generators and coroutines, where generators are kind of like "lightweight" coroutines (it doesn't create a new stack like coroutines). I'll have to look into that, since it would be nice to have both.

Quote:
Please excuse my ignorance about implementation issues and I hope this hasn't been a waste of time.


Not at all! It's good to bounce ideas back and forth. I've certainly put a lot more thought into this stuff now that we've had this discussion than I probably would have.
Back to top
View user's profile Send private message
Display posts from previous:   
Post new topic   Reply to topic     Forum Index -> MiniD All times are GMT - 6 Hours
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2005 phpBB Group