Creating true copies (of objects) in JS (possible?)

Discussion in 'Javascript' started by svend, Nov 2, 2003.

  1. svend

    svend Guest

    I'm messing with some code here... Lets say I have this array:

    a1 = [1,"2",new Array(2.5,3,3.5),4];

    And I apply slice(0) on it, to create a copy:

    a2 = a1.slice(0);

    But this isn't a true copy. If I go a1[2][1] = 42, and then
    alert(a2[2][1]) I will see 42 there too. I'm doubting, but I was
    wondering if there is a general solution to this problem?

    Regards,
    Svend
     
    svend, Nov 2, 2003
    #1
    1. Advertising

  2. "svend" <> schreef in bericht
    news:...
    >
    > But this isn't a true copy. If I go a1[2][1] = 42, and then
    > alert(a2[2][1]) I will see 42 there too. I'm doubting, but I was
    > wondering if there is a general solution to this problem?
    >


    The problem is caused by the array within the array. While slice returns a
    copy of the non-array elements, array elements remain references.

    You could try to use a method that checks the types of the elements and
    applies slice() upon array elements to provide a copy:

    Array.prototype.clone = function () {
    var tmp = [];
    for (i in this) {
    if (this.constructor == Array) {
    tmp = this.slice(0);
    } else {
    tmp = this;
    }
    }
    return tmp;
    }

    var a1 = [1,"2",new Array(2.5,3,3.5),4];
    var a2 = a1.clone();
    a1[2][1] = 42;
    alert(a2[2][1]);

    Note that this example doesn't work with arrays within array elements.


    JW
     
    Janwillem Borleffs, Nov 2, 2003
    #2
    1. Advertising

  3. "Janwillem Borleffs" <> writes:

    > Array.prototype.clone = function () {
    > var tmp = [];
    > for (i in this) {
    > if (this.constructor == Array) {
    > tmp = this.slice(0);


    Why not
    tmp = this.clone();
    ?

    > Note that this example doesn't work with arrays within array elements.


    Then it would.

    /L
    --
    Lasse Reichstein Nielsen -
    DHTML Death Colors: <URL:http://www.infimum.dk/HTML/rasterTriangleDOM.html>
    'Faith without judgement merely degrades the spirit divine.'
     
    Lasse Reichstein Nielsen, Nov 2, 2003
    #3
  4. On Sun, 2 Nov 2003, Lasse Reichstein Nielsen wrote:

    > > Array.prototype.clone = function () {
    > > var tmp = [];
    > > for (i in this) {
    > > if (this.constructor == Array) {
    > > tmp = this.slice(0);

    >
    > Why not
    > tmp = this.clone();
    > ?


    The method of looping into nested array elements will only work for
    primitives. I was looking for a more generic method (without hope
    of there being one). As far as I can tell, there's no way of
    cloning x = new MyOwnObject();

    And imagine if an array had itself as an element!

    It's in conjunction with a curry function I'm interested in this. If there
    is no way to assure the accumulated arguments integrity, special care
    might be needed, as some code might have unintentional side effects.

    Regards,
    Svend
     
    Svend Ezaki Tofte (DIKU), Nov 2, 2003
    #4
  5. "Svend Ezaki Tofte (DIKU)" <> writes:

    > The method of looping into nested array elements will only work for
    > primitives.


    I don't understand. It should work for any Array (except recursive
    ones).

    It might not be what you want, though.

    > I was looking for a more generic method (without hope of there being
    > one). As far as I can tell, there's no way of cloning x = new
    > MyOwnObject();


    Not generally, no. That would require you to be able to find the
    prototype object (which can be prevented), and even if you made a
    verbatim copy, it might not be functional, because the methods
    could have been created by the constructor and must be unique to
    the object.

    In object oriented thinking, you don't want to *copy* an object.
    Objects have identity, and a copy of the object would not be the same
    object.

    If you know that your array is "just" an array (a container for
    values), and is never used as a proper object, then copying makes
    sense (an equivalent container for the same values). In that case,
    you probably don't want to recurse anyway.

    > And imagine if an array had itself as an element!


    That is a problem. It can be fixed, though:

    Array.prototype.clone = function() {
    var tmp = [];
    this.clone = function(){return tmp;}
    for (var i in this) {
    if (i == "clone") {continue;}
    if (this.constructor == Array) {
    tmp = this.clone();
    } else {
    tmp = this;
    }
    }
    delete this.clone;
    return tmp;
    }


    > It's in conjunction with a curry function I'm interested in this. If there
    > is no way to assure the accumulated arguments integrity, special care
    > might be needed, as some code might have unintentional side effects.


    If you use functional methods like currying at the same time as side
    effects, you should sit down and decide which behavior is the one you
    want. There are probably several different ways the two can interact that
    can all make sense. Good luck :)

    Umm. Curry. :)
    ---
    function curry(func,num) {
    var accumulator = [];
    var self;
    return function dummy() {
    self = self || this;
    for (var i=0;i<arguments.length;i++) {
    accumulator[accumulator.length] = arguments;
    }
    if (accumulator.length>=num) {
    return func.apply(self,accumulator);
    } else {
    return dummy;
    }
    }
    }
    ---

    /L
    --
    Lasse Reichstein Nielsen -
    DHTML Death Colors: <URL:http://www.infimum.dk/HTML/rasterTriangleDOM.html>
    'Faith without judgement merely degrades the spirit divine.'
     
    Lasse Reichstein Nielsen, Nov 2, 2003
    #5
  6. On Mon, 3 Nov 2003, Lasse Reichstein Nielsen wrote:

    > [lrn mentions alot of sensible stuff]


    Well, those are all true, and I havedn't honestly thought this through
    very well. I just wanted to see, if this was possible.

    > That is a problem. It can be fixed, though:
    >
    > Array.prototype.clone = function() {
    > var tmp = [];
    > this.clone = function(){return tmp;}
    > for (var i in this) {
    > if (i == "clone") {continue;}
    > if (this.constructor == Array) {
    > tmp = this.clone();
    > } else {
    > tmp = this;
    > }
    > }
    > delete this.clone;
    > return tmp;
    > }


    Clever enough! I wouldn't have thought of that...

    > If you use functional methods like currying at the same time as side
    > effects, you should sit down and decide which behavior is the one you
    > want. There are probably several different ways the two can interact that
    > can all make sense. Good luck :)


    Very true. There's a longer history behind this post though. I asked here,
    a few months ago, on how to generally curry things. I got an answer, much
    like the one you've given below.

    I found it had some problems though, as it worked via a closure. Each time
    a function had accumulated enough arguments to execute, it executed,
    returning the correct result. But any subsequent calls to the functions,
    would yield the first result, as the previous arguments where not
    forgotten.

    I fixed this, by making it reset to earlier accumulated arguments, after
    it had executed, but before returning a result.

    In returning the accumulator function, things became even trickier, since
    this is essentially nested closures. I couldn't think of a sane way to do
    it using closures, so I just changed the functions, so the accumulated
    arguments were a function parameter.

    So, I think I've succeeded in eliminating all side effects of the currying
    function, except the whole reference instead of by value. Of course, you
    can shrug it off. JavaScript simply isn't a purely functional language, so
    trying to make into one, is doomed to fail. Still, I like the ability to
    curry stuff. It's kinda handy when you're lazy (I know it doesn't save
    cycles). Plus, it's pretty geeky ;)

    > function curry(func,num) {
    > var accumulator = [];
    > var self;
    > return function dummy() {
    > self = self || this;
    > for (var i=0;i<arguments.length;i++) {
    > accumulator[accumulator.length] = arguments;
    > }
    > if (accumulator.length>=num) {
    > return func.apply(self,accumulator);
    > } else {
    > return dummy;
    > }
    > }
    > }


    I'm on my way to bed, so I'm not sure how your function works, but as far
    as I can see, it wouldn't handle the closures well would it?

    I made a write up here, btw, using material from the last time I posted
    here ;)

    http://www.svendtofte.com/code/curried_javascript/

    Regards,
    Svend
     
    Svend Ezaki Tofte (DIKU), Nov 3, 2003
    #6
  7. "Svend Ezaki Tofte (DIKU)" <> writes:

    > I found it had some problems though, as it worked via a closure. Each time
    > a function had accumulated enough arguments to execute, it executed,
    > returning the correct result. But any subsequent calls to the functions,
    > would yield the first result, as the previous arguments where not
    > forgotten.


    Doh. Yes, ofcourse. I hadn't thought of that. Each call to the original
    function should yield a *new* function, not the same old function with
    an accumulator that have changed.

    ---
    function curry(func,args) { // args is optional argument. Must be array.
    if (!args) {args = [];}
    return function() {
    var newargs = args.slice[0];
    for (var i=0;i<arguments.length;i++) {
    newargs.push(arguments);
    }
    if (newargs.length >= func.length) {
    return func.apply(this,newargs);
    } else {
    return curry(func,newargs);
    }
    }
    }
    ---
    (I hadn't thought of func.length! Smart!)

    Then you can write:
    ---
    function add(x,y,z) {return x+y+z;}
    var cadd = curry(add);
    var cadd1 = cadd(1);
    var cadd2 = cadd(2);
    var cadd13 = cadd1(3);
    var cadd15 = cadd1(5);
    var cadd27 = cadd2(7);
    alert(cadd13(10)+cadd15(9)+cadd27(13));
    ---

    > I fixed this, by making it reset to earlier accumulated arguments, after
    > it had executed, but before returning a result.


    That still only allows you to use the curried function sequentially. You
    can't give it one argument, and then call the resulting function twice
    on different second arguments.

    > In returning the accumulator function, things became even trickier, since
    > this is essentially nested closures. I couldn't think of a sane way to do
    > it using closures, so I just changed the functions, so the accumulated
    > arguments were a function parameter.


    Hey, me too! :)

    > So, I think I've succeeded in eliminating all side effects of the currying
    > function, except the whole reference instead of by value. Of course, you
    > can shrug it off. JavaScript simply isn't a purely functional language, so
    > trying to make into one, is doomed to fail. Still, I like the ability to
    > curry stuff. It's kinda handy when you're lazy (I know it doesn't save
    > cycles). Plus, it's pretty geeky ;)


    What I can't see is why it is a problem that arrays and objects are passed
    by reference. They are that in non-curried Javascript too. Example:
    ---
    var a = [1,2,3];
    function foo() {
    a[0]=37;
    a[1]=42;
    return 13;
    }
    function bar(arr,func) {
    return arr[0]+func()+arr[1];
    }
    bar(a,foo);
    ---
    Side-effects can happen inside the function itself. I don't see a problem
    in them happening between calls to a curried function.

    [bad curry]
    > I'm on my way to bed, so I'm not sure how your function works, but as far
    > as I can see, it wouldn't handle the closures well would it?


    No.

    > I made a write up here, btw, using material from the last time I posted
    > here ;)
    >
    > http://www.svendtofte.com/code/curried_javascript/


    I am getting tired too :)
    I can't seem to get the examples working ...
    Ah. I removed the "this." in front of "add", and then it worked. Must
    be because I as running it inside a call to eval or something. I don't
    think you need the "this."-prefix. You can refer directly to a named
    function from inside itself.

    It seems to be working fine now.


    Btw, in your "Object locator for Netscape 4", you really shouldn't
    use a browser detector. Object detection is safer. :)

    Svend Tofte ... Are you by any chance related to Mads?
    /L
    --
    Lasse Reichstein Nielsen -
    DHTML Death Colors: <URL:http://www.infimum.dk/HTML/rasterTriangleDOM.html>
    'Faith without judgement merely degrades the spirit divine.'
     
    Lasse Reichstein Nielsen, Nov 3, 2003
    #7
  8. svend

    Dom Leonard Guest

    Lasse Reichstein Nielsen wrote:
    > "Svend Ezaki Tofte (DIKU)" writes:
    >
    >
    >>I found it had some problems though, as it worked via a closure. Each time
    >>a function had accumulated enough arguments to execute, it executed,
    >>returning the correct result. But any subsequent calls to the functions,
    >>would yield the first result, as the previous arguments where not
    >>forgotten.


    From code at the URL quotes below can see how you (Svend) solved the
    "problem" incurred by not having separate copies of accumulated
    arguments, but would point out that the inner function

    return function (){
    // arguments are params, so closure bussiness is avoided.
    return accumulator(arguments,sa.slice(0),n);
    }

    does not prevent nested closures from being created. It does defeat
    reference to outer closure variables and parameter names by using "sa"
    and "n" as formal parameter names of the "accumulator" function called.

    FWIW, the alternative of copying both previous and freshly supplied
    arguments to a new accumulation array obviates the need to consider
    reinstatement of closure variables, and using the (make) curry function
    itself to accumulate old and new argument arrays can avoid the creation
    of deeply nested closures.


    >
    > Each call to the original
    > function should yield a *new* function, not the same old function with
    > an accumulator that have changed.
    >
    > ---
    > function curry(func,args) { // args is optional argument. Must be array.
    > if (!args) {args = [];}
    > return function() {
    > var newargs = args.slice[0];


    This code is not unlike some I posted some months back when discussing
    curried functions, so I am familiar with its behaviour. I concur that
    args must be an Array object since argument objects within functions
    lack Array.prototype methods and hence the .slice method called here.

    The down-side to requiring "args" to be an array, however, is that a
    function currying itself must explicitly convert its argument object
    into an array when calling "curry".

    My comment is that copying old and new arguments into a third array
    explicitly created as an Array object within "curry" can simplify the
    calling process.


    <snip>


    >>
    >>http://www.svendtofte.com/code/curried_javascript/

    >
    >


    > ... to get the examples working ...
    > Ah. I removed the "this." in front of "add", and then it worked. Must
    > be because I as running it inside a call to eval or something. I don't
    > think you need the "this."-prefix. You can refer directly to a named
    > function from inside itself.
    >



    I would tend to put that more strongly myself: remove the "this." in
    front of "add". Not just to run within an evaluator, but because it
    actually requires that "add" be a method of its "this" value. This is
    false in javascript if "add" were defined as a nested function and
    called without object qualification. For those unfamiliar with the
    outcome, "this" within "add" would take on the value of the global
    object in such a case.



    HTH

    Dom
     
    Dom Leonard, Nov 3, 2003
    #8
  9. JRS: In article <>, seen
    in news:comp.lang.javascript, Svend Ezaki Tofte (DIKU)
    <> posted at Sun, 2 Nov 2003 23:01:47 :-
    >
    >And imagine if an array had itself as an element!


    If that can happen, then an instance of a recursive process needs to
    check whether it has arrived at an ancestor, or employ other means to
    prevent indefinite recursion. Or the process documentation should
    forbid the case.

    --
    © John Stockton, Surrey, UK. ?@merlyn.demon.co.uk Turnpike v4.00 IE 4 ©
    <URL:http://jibbering.com/faq/> Jim Ley's FAQ for news:comp.lang.javascript
    <URL:http://www.merlyn.demon.co.uk/js-index.htm> JS maths, dates, sources.
    <URL:http://www.merlyn.demon.co.uk/> TP/BP/Delphi/JS/&c., FAQ topics, links.
     
    Dr John Stockton, Nov 3, 2003
    #9
  10. On Mon, 3 Nov 2003, Lasse Reichstein Nielsen wrote:

    > Doh. Yes, ofcourse. I hadn't thought of that. Each call to the original
    > function should yield a *new* function, not the same old function with
    > an accumulator that have changed.


    I wrote that page a few months ago, and wanting to use it the other day,
    found absolutely nothing worked. Surprise! I messed with it a little, and
    figured out it was the closures giving the problems.

    > function curry(func,args) { // args is optional argument. Must be array.
    > if (!args) {args = [];}
    > return function() {
    > var newargs = args.slice[0];
    > for (var i=0;i<arguments.length;i++) {
    > newargs.push(arguments);
    > }
    > if (newargs.length >= func.length) {
    > return func.apply(this,newargs);
    > } else {
    > return curry(func,newargs);
    > }
    > }
    > }
    > ---
    > (I hadn't thought of func.length! Smart!)
    >
    > Then you can write:
    > ---
    > function add(x,y,z) {return x+y+z;}
    > var cadd = curry(add);
    > var cadd1 = cadd(1);
    > var cadd2 = cadd(2);
    > var cadd13 = cadd1(3);
    > var cadd15 = cadd1(5);
    > var cadd27 = cadd2(7);
    > alert(cadd13(10)+cadd15(9)+cadd27(13));
    > ---


    I'll have to test this code out. From the looks of it, I don't see how it
    would work. Wouldn't fx. cadd13 always return 14 now? Because it's
    newsargs array would contain 1, 3, and 10?

    I'm in a bit stripped for time right now, so I unfortunantly don't have
    the time to play around with the code as I want to. But the thread will be
    bookmarked in google groups, and I'll have to return, if any questions
    arise :)

    > > I fixed this, by making it reset to earlier accumulated arguments, after
    > > it had executed, but before returning a result.

    >
    > That still only allows you to use the curried function sequentially. You
    > can't give it one argument, and then call the resulting function twice
    > on different second arguments.


    What do you mean?

    > > In returning the accumulator function, things became even trickier, since
    > > this is essentially nested closures. I couldn't think of a sane way to do
    > > it using closures, so I just changed the functions, so the accumulated
    > > arguments were a function parameter.

    >
    > Hey, me too! :)


    Does this refer to the above also?

    > Side-effects can happen inside the function itself. I don't see a problem
    > in them happening between calls to a curried function.


    I suppose you are right. It's not like it's preventable anyway. I'm sure
    some very unreadable code can be made that way :)

    > > I made a write up here, btw, using material from the last time I posted
    > > here ;)
    > >
    > > http://www.svendtofte.com/code/curried_javascript/

    >
    > I am getting tired too :)
    > I can't seem to get the examples working ...
    > Ah. I removed the "this." in front of "add", and then it worked. Must
    > be because I as running it inside a call to eval or something. I don't
    > think you need the "this."-prefix. You can refer directly to a named
    > function from inside itself.


    I will remove the this. prefix. I'm not really sure why I had it there...
    But how would this behave then, for anonymous functions?

    > It seems to be working fine now.
    >
    >
    > Btw, in your "Object locator for Netscape 4", you really shouldn't
    > use a browser detector. Object detection is safer. :)


    Heh, don't hold that against me. That was written three or four years ago,
    as a first year CS student told me about recursion, to navigate the NS4 dom
    tree... ah, those were the days!

    > Svend Tofte ... Are you by any chance related to Mads?


    Heh, second time I get that, and no :)

    Regards,
    Svend
     
    Svend Ezaki Tofte (DIKU), Nov 3, 2003
    #10
  11. On Mon, 3 Nov 2003, Dom Leonard wrote:

    > From code at the URL quotes below can see how you (Svend) solved the
    > "problem" incurred by not having separate copies of accumulated
    > arguments, but would point out that the inner function
    >
    > return function (){
    > // arguments are params, so closure bussiness is avoided.
    > return accumulator(arguments,sa.slice(0),n);
    > }
    >
    > does not prevent nested closures from being created. It does defeat
    > reference to outer closure variables and parameter names by using "sa"
    > and "n" as formal parameter names of the "accumulator" function called.


    I know closures are not prevented. But accumulation of arguments are
    prevented. Which was the goal. Closures prevented this, argument passing
    solved this.

    > FWIW, the alternative of copying both previous and freshly supplied
    > arguments to a new accumulation array obviates the need to consider
    > reinstatement of closure variables, and using the (make) curry function
    > itself to accumulate old and new argument arrays can avoid the creation
    > of deeply nested closures.


    I don't understand this?

    > I would tend to put that more strongly myself: remove the "this." in
    > front of "add". Not just to run within an evaluator, but because it
    > actually requires that "add" be a method of its "this" value. This is
    > false in javascript if "add" were defined as a nested function and
    > called without object qualification. For those unfamiliar with the
    > outcome, "this" within "add" would take on the value of the global
    > object in such a case.


    Reminds me of a paper Waldemar wrote on JS 2.0. That if you understood
    that, you've been reading too many language specs ;) [1]

    Everytime I feel like I know JS, I see more of the language, and just how
    truly twisted it really is... !

    Regards,
    Svend

    [1] http://www.mozilla.org/js/language/evolvingJS.pdf (page 5)
     
    Svend Ezaki Tofte (DIKU), Nov 3, 2003
    #11
  12. svend

    Dom Leonard Guest

    Svend Ezaki Tofte (DIKU) wrote:
    > On Mon, 3 Nov 2003, Dom Leonard wrote:


    >
    >>FWIW, the alternative of copying both previous and freshly supplied
    >>arguments to a new accumulation array obviates the need to consider
    >>reinstatement of closure variables, and using the (make) curry function
    >>itself to accumulate old and new argument arrays can avoid the creation
    >>of deeply nested closures.

    >
    >
    > I don't understand this?
    >


    Now we both have a problem, I'm confused too :)

    Looking at your code more closely I am left wondering if the "need to
    reset" more properly belongs to a previous version of the function.
    Rewritten to take out the outer variables which are hidden by parameter
    names in nested function "accummulator", I got:


    function curry(func,args,space)
    {
    function accumulator(moreArgs, cumeArgs, n)
    {

    // copy existing and number of remaining

    var saPrev = cumeArgs.slice(0); // shallow copy
    var nPrev = n; // to reset

    // extend parameter array

    for(var i=0;i<moreArgs.length;i++,n--) {
    cumeArgs[cumeArgs.length] = moreArgs;
    }

    // if enough to execute, execute function
    // and restore arguments (which are held in closure)

    if ((n-moreArgs.length)<=0) {
    var res = func.apply(space,cumeArgs);
    // reset vars, so curried function can be applied to new params.
    // cumeArgs = saPrev;
    // n = nPrev;
    return res;
    }
    else
    {
    return function (){
    // arguments are params, so closure bussiness is avoided.
    return accumulator(
    arguments,
    cumeArgs.slice(0), // shallow copy
    n // updated arguments to come
    );
    }
    }
    }

    return accumulator(
    [],
    Array.prototype.slice.apply(args), // shallow copy
    func.length - args.length //arguments still to come
    );
    }

    ----

    Curried functions appears to copy existing, accumulated arguments into a
    formal parameter array passed to "accumulator" executing and creating a
    closure one level down from the call. Effectively this parameter array
    is the "new accumulation" array I saw as logically required.

    What I now can't see, or find examples for, is why there would be a need
    to restore the parameter array when a nested call to "accumulator"
    executes "func" - should not the copy of accumulated arguments in its
    2nd formal parameter go out of existence anyway?


    Regards,

    Dom
     
    Dom Leonard, Nov 4, 2003
    #12
  13. On Wed, 5 Nov 2003, Dom Leonard wrote:

    > Looking at your code more closely I am left wondering if the "need to
    > reset" more properly belongs to a previous version of the function.
    > Rewritten to take out the outer variables which are hidden by parameter
    > names in nested function "accummulator", I got:
    >
    > [lotsa code snipped]


    Mmmm, I like that even more. Of course, totally parametizing the arguments
    eliminates the whole closure bussiness even more. I'll have to rip that
    for my script ;)

    > What I now can't see, or find examples for, is why there would be a need
    > to restore the parameter array when a nested call to "accumulator"
    > executes "func" - should not the copy of accumulated arguments in its
    > 2nd formal parameter go out of existence anyway?


    But, and this is without testing your code (I see you've commented out the
    reset part I had in there), the function will remember the passed
    arguments.
    Or what? I'm not sure why it should fall out of scope, and just be
    forgotten?
    The function returned by curry, accumulator, will keep accumulating
    arguments,
    and will never remove any.

    Call it again and again, and it'll stick the new variables onto the end of
    all the old ones.

    Or am I misunderstanding your point? :)

    Regards,
    Svend
     
    Svend Ezaki Tofte (DIKU), Nov 4, 2003
    #13
  14. "Dom Leonard" <> wrote in message
    news:S_Opb.536$...
    <snip>
    >Looking at your code more closely I am left wondering if the
    >"need to reset" more properly belongs to a previous version
    >of the function. Rewritten to take out the outer variables
    >which are hidden by parameter names in nested function
    >"accummulator", I got:
    >
    > function curry(func,args,space)

    <snip>
    > }
    >
    > ----
    >Curried functions appears to copy existing, accumulated arguments
    >into a formal parameter array passed to "accumulator" executing
    >and creating a closure one level down from the call. Effectively
    >this parameter array is the "new accumulation" array I saw as
    >logically required.


    >What I now can't see, or find examples for, is why there would be
    >a need to restore the parameter array when a nested call to
    >"accumulator" executes "func" - should not the copy of accumulated
    >arguments in its 2nd formal parameter go out of existence anyway?


    This is an intriguing problem and I thought that I would have a go at
    it. I didn't like the idea of accumulating the parameters in an array so
    I traded that off against an increased number of function objects, but
    along the way I gained the ability to pass some, or all, of the
    arguments to the original call to the curry function with the function
    that is to be executed.

    This is my version:-

    <html>
    <head>
    <title></title>
    <script type="text/javascript">

    function curry(f){
    var obj = this; //global, unless this function
    //is assigned as a method.
    arguments.callee.depth = 0;
    function getArgFunc(args, af){
    function argFunc(x){
    var retVal;
    var self = arguments.callee;
    function getArgs(){
    var ar = self.getArgs();
    ar[ar.length] = x;
    return ar;
    }
    if(self.depth == f.length){
    retVal = f.apply(obj, getArgs());
    }else{
    retVal = getArgFunc(arguments, getArgs);
    }
    return retVal;
    }
    argFunc.depth = args.callee.depth+1;
    argFunc.getArgs = af;
    for(var c = 1;c < args.length;c++){
    argFunc = argFunc(args[c]);
    if((typeof argFunc != 'function')||(!argFunc.getArgs)){
    break;
    }
    }
    args = null;
    return argFunc;
    }
    return getArgFunc(arguments, function(){return [];});
    }

    </script>
    </head>
    <body>

    <script type="text/javascript">

    function add(x,y,z,a) {return x+','+y+','+z+','+a;}

    var cadd = curry(add);

    var cadd1 = cadd(1);
    var cadd2 = cadd(2);
    var cadd13 = cadd1(3);
    var cadd15 = cadd1(5);
    var cadd27 = cadd2(7);

    var cadd13A = cadd13(10);
    var cadd159 = cadd15(9);
    var cadd27D = cadd27(13)
    document.write('cadd13A(6) = '+cadd13A(6)+' :<br>');
    document.write('cadd159(8) = '+cadd159(8)+' :<br>');
    document.write('cadd27D(5) = '+cadd27D(5)+' :<br>');

    document.write('cadd13A(1) = '+cadd13A(1)+' :<br>');
    var cadd134 = cadd13(4);
    document.write('cadd134(5) = '+cadd134(5)+' :<br>');

    var cadd12 = cadd(1,2);
    var cadd123 = cadd12(3);
    document.write('cadd123(5) = '+cadd123(5)+' :<br>');
    document.write('cadd12(6,7) = '+cadd12(6,7)+' :<br>');
    document.write('curry(add,4,5,6,7) = '+curry(add,4,5,6,7)+' :<br>');
    document.write('cadd2(4,6,8) = '+cadd2(4,6,8)+' :<br>');
    document.write('cadd27(100,120) = '+cadd27(100,120)+' :<br>');
    var cadd98 = curry(add,9,8)
    document.write('cadd98(7,6) = '+cadd98(7,6)+' :<br>');
    </script>
    </body>
    </html>

    Richard.
     
    Richard Cornford, Nov 5, 2003
    #14
  15. svend

    Dom Leonard Guest

    Svend Ezaki Tofte (DIKU) wrote:
    > On Wed, 5 Nov 2003, Dom Leonard wrote:
    >

    <snip>
    >
    >
    > Of course, totally parametizing the arguments
    > eliminates the whole closure bussiness even more. I'll have to rip that
    > for my script ;)
    >


    Great, progress is in progress :)


    >
    >>What I now can't see, or find examples for, is why there would be a need
    >>to restore the parameter array when a nested call to "accumulator"
    >>executes "func" - should not the copy of accumulated arguments in its
    >>2nd formal parameter go out of existence anyway?

    >
    >
    > But, and this is without testing your code (I see you've commented out the
    > reset part I had in there), the function will remember the passed
    > arguments.


    Okay, I've got it now (well...)

    A bug in the unmodified version was that calling "curry" shallow copied
    arguments into local variable "sa", and then passed that array to
    "accumulator" by *reference*.

    The first call to "accumulator" now operated on an array held in the
    outermost closure, and interferred with re-use of the outermost curried
    function.

    Copying existing arguments into a parameter array in the outermost
    closure peformed a neccessary dynamic copy, made it consistent with the
    same operation present in the closure created by "accumulator", and
    hence removes the need to "reset" argument arrays (which probably was
    not a perfect solution, but at this point who cares :)



    > Or what? I'm not sure why it should fall out of scope, and just be
    > forgotten?


    If "accumulator" executes "func" it does not return a local nested
    function, and so does not create a closure. On exit its formal
    paraameters will not be held in scope anywhere. If the calling function
    does not hold a variable set to the parameter array copied in call, it's
    gone.

    Hopefully you will now find creaation of nPrev and saPrev variables, and
    the business of resetting parameter arrays unnecessary.

    Regards,

    Dom
     
    Dom Leonard, Nov 5, 2003
    #15
  16. svend

    Dom Leonard Guest

    Snap, we both posted at same time :)

    It is certainly intriguing code usage, and I'll have a look at your
    version when I get off line. For your interest and comparison, the
    version I posted some monts ago went:

    ----

    function curry(func,oldArgs)
    { function accumulator()
    { var newArgs = arguments;
    if(newArgs.length ==0)
    return accumulator; // no change
    if(oldArgs)
    { newArgs=new Array();
    newArgs.push.apply(newArgs,oldArgs);
    newArgs.push.apply(newArgs,arguments);
    }
    return (newArgs.length >= func.length) ?
    func.apply(this,newArgs) :
    curry(func,newArgs);
    }
    return func.length ? accumulator : func;
    }


    ----

    It contains some "belts and braces" for boundary conditions that may not
    be needed, and possibly could use Array.prototype.splice - I just didn't
    see where at the time, and Svend's version is coming along too :)

    Cheers,

    Dom
     
    Dom Leonard, Nov 5, 2003
    #16
  17. "Dom Leonard" <> wrote in message
    news:l6%pb.116$...
    >Snap, we both posted at same time :)


    >It is certainly intriguing code usage, and I'll have a look
    >at your version when I get off line. For your interest and
    >comparison, the version I posted some monts ago went:
    >
    > ----
    >
    > function curry(func,oldArgs)
    > { function accumulator()
    > { var newArgs = arguments;
    > if(newArgs.length ==0)
    > return accumulator; // no change


    That possibility didn't occur to me, but it does need to be covered.

    > if(oldArgs)
    > { newArgs=new Array();
    > newArgs.push.apply(newArgs,oldArgs);
    > newArgs.push.apply(newArgs,arguments);
    > }
    > return (newArgs.length >= func.length) ?
    > func.apply(this,newArgs) :
    > curry(func,newArgs);
    > }
    > return func.length ? accumulator : func;


    And thinking about the possibility that the parameter adding function
    may be called without an argument it also occurred to me that the
    function (func) itself may not require any parameters, which you have
    covered here. However, if the function needs no parameters (func.length
    == 0) shouldn't the value returned at this point be the result of
    calling that function? As my version allows the initial call to curry to
    be supplied with all of the required parameters and will execute the
    function if they are all present I went with executing the function in
    curry if func.length == 0.

    > }


    > ----
    >
    >It contains some "belts and braces" for boundary conditions
    >that may not be needed, and possibly could use
    >Array.prototype.splice - I just didn't see where at the time,
    >and Svend's version is coming along too :)


    Being reminded of the belts and braces was useful, here are my
    corrections for those (at least the ones I have noticed so far):-

    function curry(f){
    var retVal,obj = this; //global, unless this function
    //is assigned as a method.
    arguments.callee.depth = 0;
    function getArgFunc(args, af){
    function argFunc(x){
    var retVal;
    var argsLen = arguments.length;
    var self = arguments.callee;
    function getArgs(){
    var ar = self.getArgs();
    if(argsLen){ar[ar.length] = x;}
    return ar;
    }
    if(self.depth >= f.length){
    retVal = f.apply(obj, getArgs());
    }else if(!argsLen){
    retVal = self;
    }else{
    retVal = getArgFunc(arguments, getArgs);
    }
    return retVal;
    }
    argFunc.depth = args.callee.depth+1;
    argFunc.getArgs = af;
    for(var c = 1;c < args.length;c++){
    argFunc = argFunc(args[c]);
    if((typeof argFunc != 'function')||(!argFunc.getArgs)){
    break;
    }
    }
    af = (args = null);
    return argFunc;
    }
    retVal = getArgFunc(arguments, function(){return [];});
    return (f.length)?retVal:retVal();
    }

    Richard.
     
    Richard Cornford, Nov 5, 2003
    #17
  18. "Svend Ezaki Tofte (DIKU)" <> writes:

    > I'll have to test this code out. From the looks of it, I don't see how it
    > would work. Wouldn't fx. cadd13 always return 14 now? Because it's
    > newsargs array would contain 1, 3, and 10?


    No, cadd13's internal array is [1,3]. Each function gets a new array, so
    they don't mess up each other's.

    It can be shortened using some of the tricks you have used in the
    later discussion, and made to not require arrays, but also accept
    arguments objects:

    function curry(func,args) { // args is optional argument.
    if (!args) {args = [];}
    return function curried() {
    if (arguments.length == 0) {return curried;} //optimization, !essential
    var newargs = Array.prototype.slice.call(args,0); // array or arguments
    Array.prototype.push.apply(newargs,arguments); // someone was smart!
    if (newargs.length >= func.length) {
    return func.apply(this,newargs);
    } else {
    return curry(func,newargs);
    }
    }
    }

    It doesn't preserve the object that the original function is a method
    of, since it can't know it. Should it remember some object that a
    partially applied function is assigned to? I don't think so. As it is
    now, you can assign a partially applied curried function as a method
    and have it work correctly when you give it the remaining arguments
    directly:

    var o1 = new Object();
    o1.foo = -1000000;
    var o2 = new Object();
    o2.foo = 42;
    o1.cadd = curry(function(x,y,z){return x+y+z+this.foo});
    o2.cadd13 = o1.cadd(1,3);
    alert(o2.cadd13(-9));

    The most meaningfull result of this code is that the this.foo
    referst to o2, since cadd13 is a method of o2 when it is called.
    If it had remembered that cadd was a method of o1, then it would
    give a different, and IMO incorrect result.



    It is easy for functions to curry themselves:

    function add(x,y,z) {
    if (arguments.length < add.length) {
    return curry(add,arguments);
    }
    return x+y+z;
    }


    > > That still only allows you to use the curried function sequentially. You
    > > can't give it one argument, and then call the resulting function twice
    > > on different second arguments.

    >
    > What do you mean?


    That I hadn't testet the code. It works fine. :)

    > Does this refer to the above also?


    Yep.


    /L
    --
    Lasse Reichstein Nielsen -
    DHTML Death Colors: <URL:http://www.infimum.dk/HTML/rasterTriangleDOM.html>
    'Faith without judgement merely degrades the spirit divine.'
     
    Lasse Reichstein Nielsen, Nov 5, 2003
    #18
  19. Everyone who responded to this thread,

    I feel really bad for having started a thread, and having generated so
    many good responses, and then not even having time to fully look at the
    answers (that isn't the sole justification of this thread of course!).

    I find it interesting though, how an issue like currying can be applied in
    a language like JS. Perhaps when winter time comes, I'll be posting
    again... with more questions ;)

    Regards,
    Svend

    (I've got to go read some psych bullshit now, if you'll excuse me ... )
     
    Svend Ezaki Tofte (DIKU), Nov 5, 2003
    #19
  20. svend

    Dom Leonard Guest

    Richard Cornford wrote:

    <snip>


    > ... it also occurred to me that the
    > function (func) itself may not require any parameters, which you have
    > covered here. However, if the function needs no parameters (func.length
    > == 0) shouldn't the value returned at this point be the result of
    > calling that function?


    I considered the effect some months ago, but ultimately thought the
    "desirable" behaviour may be unknowable. A curried funtion, pursuant to
    the currying operation in javascript, has a function length property of
    zero. If on account of that, currying a curried function returns the
    curried function, then "curry" on a curried function results in the
    identity transform.

    In the version I propsosed, the same identity transform results from
    calling a curried function with no parameters - it returned itself.

    The question remaining is what should the curry operation, at the top
    level, do with a function that has a zero length property? Options are
    to return the function (my example), calling the function (your example)
    or throw an exception that curry was attempted on a function expecting
    no arguments.

    Whilst I tend to the last option, it's not hard and fast. In an
    reasonable program I would not expect the problem to arise, but am happy
    to defer to any theoretician with a rule for what should happen.



    kind regards,

    Dom
     
    Dom Leonard, Nov 7, 2003
    #20
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Siemel Naran

    Does true ^ true return false?

    Siemel Naran, Jun 17, 2004, in forum: C++
    Replies:
    19
    Views:
    708
    Chris Theis
    Jun 18, 2004
  2. Chip
    Replies:
    6
    Views:
    2,696
    E. Robert Tisdale
    Jan 8, 2005
  3. Andy Leszczynski
    Replies:
    4
    Views:
    351
    Erik Max Francis
    Oct 13, 2005
  4. Pierre Quentel

    "0 in [True,False]" returns True

    Pierre Quentel, Dec 12, 2005, in forum: Python
    Replies:
    59
    Views:
    1,083
    Grant Edwards
    Dec 16, 2005
  5. bdb112
    Replies:
    45
    Views:
    1,425
    jazbees
    Apr 29, 2009
Loading...

Share This Page