How do I use an unevaluated (and/or possibly undefined) variable as a parameter for a function? For example:
function myFun(a:int):void {
a = 5;
}
If you are familiar with Mathematica it would be equivalent to:
f[a_Integer]:=a=5
Attributes[f]={HoldAll};
The core idea being that it is the variable's name itself which I want to pass to the function, not the value which is current associated with the variable's name.
You can pass a string.
private function my_fun(name:String):void {
trace(this[name]);
}
Example of use:
public class Main extends Sprite {
public var a:int = 5;
....
public function Main():void {
my_fun("a");
}
According to these guys: get string representation of a variable name in as3 if it's a classe you can get it's name. If it's a local variable you cannot, the reason is probably related with efficience (the name gets lost on compiling phase)
Another way you could solve the problem is to use closures to change evaluation scopes.
public class A {
public static function myFun(setter:Function):void {
setter(5);
}
}
public class B {
function someOtherFunction() {
var val:Number;
A.myFun(function(v:Number):void { val = v; });
trace(val); // 5
}
}
Instead of passing a value, I'm passing a function that is bound in the calling scope that is evaluated in the callee's scope.
Related
"This" is incapable of finding the appropriate variable "array1" despite it clearly being declared within the function. But if I declare the variables outside of the function it works. How can I have the variables inside the function but keep it working?
package
{
public class main extends MovieClip
{
//If I declared the variables here it would work.
public function main():void
{
var array1:Array = [1,2];
var array2:Array = [3,4];
trace(this["array"+1][1]); //returns reference error 1069
}
}
}
Am I stuck with declaring the variables outside of the function?
And no, multidimensional arrays won't work for what I need it for. Though it looks like it would solve everything within the code snippet provided huh?
My intentions is to pass arrays through a class to be used and change which array bunch I use. If I used multidimensional arrays, it would be inefficient due to the amount of copying that would occur.
For this[] to access properties, those properties must belong to this. In your sample, the properties belong to the function in which they were defined and are inaccessible outside of that scope.
So firstly; yes, for your code to work you will of course need to define properties in the class level scope.
But more importantly I'd look closely at what you're trying to do and determine whether it's a good approach - my bet is that it's not. It seems like you may want to consider an isolated class that deals with all the data you want to store.
Your error is because you mis-scoped the variables.
The "this" keyword means you are trying to target a variable on the specific instance of the class.
You have scoped the variables locally to the function.
You need to move them to the class declaration area for "this" to work.
package
{
public class main extends MovieClip
{
public var array1:Array = [1,2];
public var array2:Array = [3,4];
public function main():void
{
trace(this["array"+1][1]); //returns reference error 1069
}
}
}
// now if you meant to scope them locally to the function then you can not use "this"
// you have to assign them to an object or an array
package
{
public class main extends MovieClip
{
public function main():void
{
var obj:Object = {}
obj['array1'] = new Array( [1,2] )
obj['array2'] = new Array( [3,4] )
trace(obj["array"+1][1]);
}
}
}
This is untested code but it should put you on the right track.
Depending on what you are trying to accomplish exactly, you can also consider passing an array as an argument to a function, something like:
function main():void
{
var array1:Array = [1,2];
var array2:Array = [3,4];
doSomethingWithArray(array2);
}
main();
function doSomethingWithArray(arr:Array):void
{
trace(arr[1]); //Traces the value of array2[1], which = 4
}
Also consider having a var currentArray:Array that you can set as any array you'd like and refer to it as needed, if applicable.
public class main extends MovieClip
{
...
public var currentArray:Array;
function main():void
{
var array1:Array = [1,2];
currentArray = array1;
doSomethingithCurrentArray();
var array2:Array = [3,4];
currentArray = array2;
doSomethingithCurrentArray();
currentArray = null;
}
public function doSomethingithCurrentArray():void {
if(currentArray != null){
trace(currentArray);
}
}
}
I am not sure if I worded the question correctly, however, I have code to detail it. Firstly, I have a class Class1:
public class Class1
{
public var S:Number = 0;
public function Class1()
{
}
}
Which I am using to create an object in my document class:
public class Main extends Sprite
{
public var class1:Class1 = new Class1;
public var class2:Class2 = new Class2;
public function Main():void
{
trace(String(class1.S));
class2.c2f1(true, class1.S);
trace(String(class1.S));
}
}
And I want to update that object's variable S through another object's method c2f1 created from this class:
public class Class2
{
public function Class2()
{
}
public function c2f1(param1:Boolean, f1:Number):void
{
if (param1) f1 = 1;
trace("c2f1")
}
}
The output is as follows:
0
c2f1
0
Where it ideally should be:
0
c2f1
1
This isn't the actual code I am using for any projects, I have specified the problem in hopes that I may more easily see the concepts involved.
Hope someone can help. :)
Your problem is that it's not the variable of class1.S that's incremented but a copy of it - in AS3, simple types are passed by value - that is, Flash creates a copy of the variable and that's what's passed to the called function.
You can pass your object to this function:
...
public function c2f1(param1:Boolean, obj:Class1):void
{
if (param1) obj.S = 1;
trace("c2f1")
}
If you need to pass multiple different object types, you can create an interface instead and pass that. Then your Class1 class can implement that interface and you can call a function through that interface to increment the value.
When you pass your Class1 instance, it gets passed by reference because it's a complex type, so you can modify its member variables.
Alternatively, your function can just return 1 or 0 and then you can assign that to S:
...
public function c2f1(param1:Boolean):int
{
trace("c2f1")
return ( param1 ? 1 : 0 );
}
...
var o1:Class1 = new Class1 ();
var o2:Class2 = new Class2 ();
o1.S = o2.c2f1(bValue);
I've been learning about encapsulation in AS3 and using get/set functions to make variables that are (or appear to be) read only. I can get it to work with instance variables, but not static variables. I found this, which seems to indicate that it's possible to use get/set functions on static properties, but the compiler keeps telling me I have duplicate function declarations. This is essentially what I'm using:
package {
public class Foo {
protected static var bar:int = 0;
public static function get bar():int {return bar;}
}
}
You cannot create a function (including gets or sets) with the same name as a variable, otherwise you would be re-initiating the variable.
package {
public class Foo {
protected static var myVar:int = 0;
public static function get theVar():int {return myVar;}
}
}
A good practice you can adopt is to call _bar the "internal" variable you're going to modify by getter and setter, and call them simply bar.
For example:
package {
public class Foo {
protected static var _bar:int = 0;
public static function get bar():int {
return _bar;
}
}
}
What I am trying to do is kind of odd, but I am wondering if anyone can come up with a clever way to do what I want to do. Basically, I want to re-define a named function at runtime. I can do this with anonymous functions, but I can't figure out a way to do it for named functions. I want to do this so that I can implement a "spy" functionality on an object for a testing framework (a port of Jasmine to Flex).
Take, for instance, this class:
public class TestClass
{
public var anonymous:Function = function():void {
trace("original anonymous");
};
public function named():void {
trace("original named");
}
}
I can easily re-define the anonymous function because it is just a variable. Javascript uses this idiom a lot.
var testClass:TestClass = new TestClass();
testClass.anonymous = function():void { trace("overridden anonymous"); }
BUT, when I do the same thing for named functions, you get a compile-time error:
// Does not compile
testClass.named = function():void { trace("overridden named"); }
I tried to make it a bit more "squishy" but this leads to a runtime failure "Cannot assign to a method named on TestClass".
// Compiles with runtime failure
testClass["named"] = function():void { trace("overridden named"); }
Can anyone more clever than I come up with a way to hack this? Can the bytecode be hijacked? Something?
I want to modify an object, not a
class
But object doesn't contain functions, only non-static variables. I tried to use prototype property and replace method there, but original method still gets called instead of injected one.
About "hack" bytecode, do you mean "hack" already loaded SWF in runtime? I think it's not possible. I'm sure, though, you can parse SWF with something like as3swf, find method in bytecode, replace it and save result in new SWF.
I had an idea bout making a function "cache" . This might work with what you need.
Let's say you have a class "Car" with a method you need to redefine at runtime:
public class Car extends Sprite
{
private var functionCache:Function;
public function Car()
{
super();
}
public function flexibleFunction(functionBody:*=null):void{
if(functionBody is Function){
functionBody.call();
functionCache=functionBody;
} else {
functionCache(functionBody);
}
}
}
Usage:
public class Main extends Sprite
{
private var car:Car;
public function Main()
{
car = new Car();
car.flexibleFunction(function(){trace("redefine test #1")});
car.flexibleFunction();
car.flexibleFunction(function(doParametersWork:String="let's see"){trace("redefine test #2: " + doParametersWork);});
car.flexibleFunction("yes they do");
car.flexibleFunction();
}
}
an easy way to accomplish what you want is to simply pass a new function to the original function and execute it from there:
package
{
//Imports
import flash.display.Sprite;
//Class
public class RedefineFunction extends Sprite
{
//Constructor
public function RedefineFunction()
{
originalFunction();
originalFunction(redefinedFunction);
}
//Original Function
public function originalFunction(redefinition:Function = null):void
{
if (redefinition != null)
redefinition();
else
trace("Original Function Definition");
}
//Redefined Function
private function redefinedFunction():void
{
trace("Redefined Function Definition")
}
}
}
traces:
Original Function Definition
Redefined Function Definition
This is a hard question to do, but I'll try to explain.
I have the Class and the parameters of its contructor as an object. What I need to do is a function that returns an instance of this class, passing this parameters to the constructor.
This is the code:
Some random and unmodifiable class:
public Foo {
public function Foo(a:int, b:String) {
// constructor
}
}
And some function (in some another class):
function bar(params:Object):* {
var baz:Foo = new Foo(params.a, params.b);
return baz;
}
What I need to do is make this function generic, without pass params as parameter to Foo constructor because I can't modify it. Something like:
function bar2(clazz:Class, params:Object):* {
var baz:* = new clazz(/*some magic way to transform params in comma separated parameters*/);
return baz;
}
Anyone can help me?
Thanks a lot.
This is called parameterized factory. First I thought about Function.apply, but it doesn't apply to constructors (he-he). So, people are making factories like this:
function create(what:Class, args:Array):* {
switch (args.length) {
case 0: return new what();
case 1: return new what(args[0]);
case 2: return new what(args[0], args[1]);
...
//PROFIT!
}
throw new Error("Need moar cases!");
}
what about using ByteArrayto copy the object ?
function clone(source:Object):* {
var copier:ByteArray = new ByteArray();
copier.writeObject(source);
copier.position = 0;
return(copier.readObject());
}
newObjectCopy = clone(originalObject);
source
If you have the option of not using a constructor, but adding an initialise() function to each class which can be constructed instead, you could use Function.apply - something like in the example below.
public class ThingCreator
{
public static function createTheThing(c:Class, params:Array):Object
{
var the_thing:Object = new c();
the_thing.initialise.apply(the_thing, params);
return the_thing;
}
}
As alxx pointed out above, Function.apply and AS3 reflection in this case does not seem to work with AS3's constructors.