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

S

svend

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
 
J

Janwillem Borleffs

svend said:
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
 
L

Lasse Reichstein Nielsen

Janwillem Borleffs said:
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
 
S

Svend Ezaki Tofte (DIKU)

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
 
L

Lasse Reichstein Nielsen

Svend Ezaki Tofte (DIKU) said:
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;
}
}
}
 
S

Svend Ezaki Tofte (DIKU)

[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
 
L

Lasse Reichstein Nielsen

Svend Ezaki Tofte (DIKU) said:
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
 
D

Dom Leonard

Lasse said:
"Svend Ezaki Tofte (DIKU)" writes:

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.


... 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
 
D

Dr John Stockton

JRS: In article <[email protected]>, seen
in Svend Ezaki Tofte (DIKU)
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.
 
S

Svend Ezaki Tofte (DIKU)

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 :)
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?
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 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
 
S

Svend Ezaki Tofte (DIKU)

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)
 
D

Dom Leonard

Svend said:
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
 
S

Svend Ezaki Tofte (DIKU)

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
 
R

Richard Cornford

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)
}

----
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.
 
D

Dom Leonard

Svend said:
On Wed, 5 Nov 2003, Dom Leonard wrote:
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 :)

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
 
D

Dom Leonard

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
 
R

Richard Cornford

Dom Leonard said:
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.
 
L

Lasse Reichstein Nielsen

Svend Ezaki Tofte (DIKU) said:
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;
}

What do you mean?

That I hadn't testet the code. It works fine. :)
Does this refer to the above also?

Yep.


/L
 
S

Svend Ezaki Tofte (DIKU)

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 ... )
 
D

Dom Leonard

Richard Cornford wrote:

... 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
 

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,009
Latest member
GidgetGamb

Latest Threads

Top