Skip to content

Instantly share code, notes, and snippets.

@jed
Created November 28, 2011 13:14
Show Gist options
  • Save jed/1400363 to your computer and use it in GitHub Desktop.
Save jed/1400363 to your computer and use it in GitHub Desktop.
an attempt to make the new operator dynamically invokeable
// usage: newOperator(constructor, [arg1, arg2, ...])
// example: newOperator(Date, [1995, 11, 24])
// => should be identical to new Date(1995, 11, 24)
function newOperator(constructor, args) {
var i = args.length + 1
, params = []
, fn
while (i--) params[i] = "$" + i
params.push("return new $0(" + params.slice(1) + ")")
fn = Function.apply(null, params)
params = params.concat.apply(constructor, args)
return fn.apply(null, params)
}
@WebReflection
Copy link

right, I guess we both missed the whole point then ... too bad

function newOperator(C, a) {
  return Function(
    "C,a,i",
    "return new C(" +
      Array(a.length + 1).join(",a[i++]").slice(1)
    + ")"
  )(C, a, 0);
}

@jed
Copy link
Author

jed commented Nov 28, 2011

with caching:

function newOperator(ctor, args) {
  var l = args.length
    , fn = newOperator[l]
    , body, i

  if (!fn) {
    for (i = 0; i < l; i++) {
      i ? body += "," : body = "return new ctor("
      body += "args[" + i + "]"
    }

    fn = newOperator[l] = Function("ctor", "args", body + ")")
  }

  return fn(ctor, args)
}

@jed
Copy link
Author

jed commented Nov 28, 2011

holy cow, you can put commas in the argument string? does that work universally?

@WebReflection
Copy link

sure it does ;-)

@WebReflection
Copy link

https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function

Names to be used by the function as formal argument names. Each must be a string that corresponds to a valid JavaScript identifier or a list of such strings separated with a comma; for example "x", "theValue", or "a,b".

@WebReflection
Copy link

with cache ...

function newOperator(g, o, l, f) {
  return ((f = newOperator)[l = (o || []).length] || (f[l] = Function(
    "g,o,l",
    "return new g(" +
      Array(l + 1).join(",o[l++]").slice(1)
    + ")"
  )))(g, o, 0);
}

@jed
Copy link
Author

jed commented Nov 28, 2011

bravo, @WebReflection. i'm probably going to go with something like this:

function newOperator(ctor, args) {
  var l = args.length + 1
    , fn = newOperator[l]

  fn || (fn = newOperator[l] = Function(
    "a,b,c",
    "return new a(" +
      Array(l).join(",b[c++]").slice(1) +
    ")"
  ))

  return fn(ctor, args, 0)
}

@WebReflection
Copy link

does not fit into 140 bytes ... shame of us :D

@jed
Copy link
Author

jed commented Nov 28, 2011

actually, it's 129 bytes: https://gist.github.com/1400834

@WebReflection
Copy link

you changed function name, if it's d of course it's smaller ... I would still leave the (a||[]).length to make newOperator(Array) possible, as example

@jed
Copy link
Author

jed commented Nov 28, 2011

good point, fixed it.

@jed
Copy link
Author

jed commented Nov 28, 2011

a non-golfed version:

function newOperator(ctor, args) {
  var length, fn

  args || (args = [])
  length = args.length + 1
  fn = newOperator[length]

  fn || (fn = newOperator[length] = Function(
    "ctor", "args", "i",
    "return new ctor(" +
      Array(length).join(",args[i++]").slice(1) +
    ")"
  ))

  return fn(ctor, args, 0)
}

@WebReflection
Copy link

jed, no need to b=b||[] ... just b||[] because if length is zero Array(1).join("whatever") will be an empty string (join works with at least length 2 between 0 and 1) so the b, even if undefined, will be untouched ;-)

function d(
  a, // constructor function
  b, // arguments
  c  // placeholder for length
){
  return(                         // return the result of
    d[c=b?-~b.length:1] || (      // the cached function or
      d[c] = Function(            // a new function that takes
        "a,b,c",                  // a constructor, arguments, and an index
        "return new a(" +         // and returns a new instance of the constructor
          Array(c)                // with
            .join(",b[c++]")      // the inline arguments
            .slice(1) +           // (sliced to remove leading comma)
        ")"
      )
    )
  )(a,b,0)                        // called with the constructor and arguments
}

@jed
Copy link
Author

jed commented Nov 29, 2011

in that case, why define the array at all if args has no length? can we use this?

var length = args ? args.length + 1 : 0

[EDIT] so something like this:

function newOperator(ctor, args) {
  var length = args ? -~args.length : 0
    , fn = newOperator[length]

  fn || (fn = newOperator[length] = Function(
    "ctor", "args", "i",
    "return new ctor(" +
      Array(length).join(", args[i++]").slice(1) +
    ")"
  ))

  return fn(ctor, args, 0)
}

@WebReflection
Copy link

the minus tilde does not give us much more than +1, 2 readable chars with no necessary int cast.
I agree the creation of an empty Array per each call without arguments is way too redundant and also bigger than this solution:
d[c=b?b.length+1:1]
I have edited and as result 2 chars less and faster "no arguments" execution ;-)

@jed
Copy link
Author

jed commented Nov 29, 2011

doesn't leaving out the number cast mean that the length property could be used to inject code? i'm thinking where args is {length: "throw 'neener neener'"}.

@WebReflection
Copy link

well, the initial trick does not solve a thing here ... if you want to screw up functions that would work even with native arrays methods ...

@WebReflection
Copy link

uh .. wait, I know what you mean ... so yes, minus tilde then, updated :D

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment