is it posible to have callable objects on ActionScript? For example:
class Foo extends EventDispatcher
{
Foo() { super(); }
call(world:String):String
{
return "Hello, " + world;
}
}
And later...
var foo:Foo = new Foo();
trace( foo("World!") ); // Will NOT work
Why would you need to do this? (I'm not criticising, just interested!) Functions in AS3 are themselves first-class citizens, and can be passed around as arguments.
e.g.
public function main(foo:Function):void
{
trace(foo("World!")); // Will work, assuming foo = function(str:String):String {...}
}
No, only functions/methods can be called in this way. If the only reason is you want to type fewer characters, then you should shorten the length of the instance names and method names.
One option is to use a closure:
public function Foo():Function {
var bar:String;
return function (world:String):String {
var msg:String;
if (bar) {
msg = bar + ' says "Hello, ' + world + '"';
} else {
msg = "Hello, " + world;
}
bar = world;
return msg;
}
}
...
var foo = Foo();
trace( foo("World!") );
This is a much simplified case of the larger pattern of implementing objects as functions. As such, it's more useful in languages that support FP but not OOP, but does technically give you a callable "object". The syntax may be a little off, but:
public function createFoo(barInit, ...):Function {
var slots = {
greeter: barInit, ...
};
var methods = {
'get': function(name) { return slots[name]; }
'set': function(name, value) { slots[name] = value; }
greet: function(whom) {
var msg = slots.greeter + ' says "Hello, ' + whom + '"'
slots.greeter = whom;
return msg;
},
...
};
return function (method:String):* {
args = Array.splice.call(arguments, 1);
return methods[method].apply(null, args);
}
}
var foo = createFoo('Kermit');
trace(foo('greet', "World"));
trace(foo('greet', "Sailor"));
You probably don't want to do it in AS.
As others had said, you can't have callable objects. However, if for some reason you want to have stateful functions, you can achieve it with help of static class variables and package level functions. For example:
// com/example/foo/Helper.as
package com.example.foo {
public class Helper {
private static var _instance:Foo;
public static var data:String;
public static function get instance():Helper
{
if(!_instance) { _instance = new Helper(); }
return _instance;
}
}
}
// com/example/foo/hello.as
package com.example.foo {
public function hello(world:String):void
{
if(Helper.instance.data)
{
trace("Bye, " + Helper.instance.data);
}
trace("Hello, " + world);
Helper.instance.data = world;
}
}
When used, it will print different things.
hello("World!"); // traces "Hello, World!"
hello("People"); // traces "Bye, World!" and "Hello, People"
note: both the constructor and the method declaration miss the keywords public function to even compile, but I suppose that's not the original code. :)
the answer is: you can't.
my question is: what do you want to accomplish?
Functions are the only callable values. And Functions are primitives in ActionScript, much as ints, or Booleans, so there is no meaningful way to extend them.
If you want it to be an object, do it the Java way, defining an ICallable interface, and actually call a method, or just really use a function. closures provide the most simple and flexible possibility to create stateful functions, if that is what you want.
edit: well, you can do this (as an example):
private var fooInst:Foo = new Foo();
protected var foo:Function = fooInst.call;
and then the following workst as you wish:
<mx:Label text="{foo('Whatever')}"/>
its maybe even a little more flexible, although you lose the benefits of strict typing.
greetz
back2dos
Related
I'd like to make wrapper to implement simple data binding pattern -- while some data have been modified all registered handlers are got notified. I have started with this (for js target):
class Main {
public static function main() {
var target = new Some();
var binding = new Bindable(target);
binding.one = 5;
// binding.two = 0.12; // intentionally unset field
binding.three = []; // wrong type
binding.four = 'str'; // no such field in wrapped class
trace(binding.one, binding.two, binding.three, binding.four, binding.five);
// outputs: 5, null, [], str, null
trace(target.one, target.two, target.three);
// outputs: 5, null, []
}
}
class Some {
public var one:Int;
public var two:Float;
public var three:Bool;
public function new() {}
}
abstract Bindable<TClass>(TClass) {
public inline function new(source) { this = source; }
#:op(a.b) public function setField<T>(name:String, value:T) {
Reflect.setField(this, name, value);
// TODO notify handlers
return value;
}
#:op(a.b) public function getField<T>(name:String):T {
return cast Reflect.field(this, name);
}
}
So I have some frustrating issues: interface of wrapped object doesn't expose to wrapper, so there's no auto completion or strict type checking, some necessary attributes can be easily omitted or even misspelled.
Is it possible to fix my solution or should I better move to the macros?
I almost suggested here to open an issue regarding this problem. Because some time ago, there was a #:followWithAbstracts meta available for abstracts, which could be (or maybe was?) used to forward fields and call #:op(a.b) at the same time. But that's not really necessary, Haxe is powerful enough already.
abstract Binding<TClass>(TClass) {
public function new(source:TClass) { this = source; }
#:op(a.b) public function setField<T>(name:String, value:T) {
Reflect.setField(this, name, value);
// TODO notify handlers
trace("set: $name -> $value");
return value;
}
#:op(a.b) public function getField<T>(name:String):T {
trace("get: $name");
return cast Reflect.field(this, name);
}
}
#:forward
#:multiType
abstract Bindable<TClass>(TClass) {
public function new(source:TClass);
#:to function to(t:TClass) return new Binding(t);
}
We use here multiType abstract to forward fields, but resolved type is actually regular abstract. In effect, you have completion working and #:op(a.b) called at the same time.
You need #:forward meta on your abstract. However, this will not make auto-completion working unless you remove #:op(A.B) because it shadows forwarded fields.
EDIT: it seems that shadowing happened first time I added #:forward to your abstract, afterwards auto-completion worked just fine.
I'm not sure of the best approach for handling scoping of "this" in TypeScript.
Here's an example of a common pattern in the code I am converting over to TypeScript:
class DemonstrateScopingProblems {
private status = "blah";
public run() {
alert(this.status);
}
}
var thisTest = new DemonstrateScopingProblems();
// works as expected, displays "blah":
thisTest.run();
// doesn't work; this is scoped to be the document so this.status is undefined:
$(document).ready(thisTest.run);
Now, I could change the call to...
$(document).ready(thisTest.run.bind(thisTest));
...which does work. But it's kinda horrible. It means that code can all compile and work fine in some circumstances, but if we forget to bind the scope it will break.
I would like a way to do it within the class, so that when using the class we don't need to worry about what "this" is scoped to.
Any suggestions?
Update
Another approach that works is using the fat arrow:
class DemonstrateScopingProblems {
private status = "blah";
public run = () => {
alert(this.status);
}
}
Is that a valid approach?
You have a few options here, each with its own trade-offs. Unfortunately there is no obvious best solution and it will really depend on the application.
Automatic Class Binding
As shown in your question:
class DemonstrateScopingProblems {
private status = "blah";
public run = () => {
alert(this.status);
}
}
Good/bad: This creates an additional closure per method per instance of your class. If this method is usually only used in regular method calls, this is overkill. However, if it's used a lot in callback positions, it's more efficient for the class instance to capture the this context instead of each call site creating a new closure upon invoke.
Good: Impossible for external callers to forget to handle this context
Good: Typesafe in TypeScript
Good: No extra work if the function has parameters
Bad: Derived classes can't call base class methods written this way using super.
Bad: The exact semantics of which methods are "pre-bound" and which aren't create an additional non-typesafe contract between your class and its consumers.
Function.bind
Also as shown:
$(document).ready(thisTest.run.bind(thisTest));
Good/bad: Opposite memory/performance trade-off compared to the first method
Good: No extra work if the function has parameters
Bad: In TypeScript, this currently has no type safety
Bad: Only available in ECMAScript 5, if that matters to you
Bad: You have to type the instance name twice
Fat arrow
In TypeScript (shown here with some dummy parameters for explanatory reasons):
$(document).ready((n, m) => thisTest.run(n, m));
Good/bad: Opposite memory/performance trade-off compared to the first method
Good: In TypeScript, this has 100% type safety
Good: Works in ECMAScript 3
Good: You only have to type the instance name once
Bad: You'll have to type the parameters twice
Bad: Doesn't work with variadic parameters
Another solution that requires some initial setup but pays off with its invincibly light, literally one-word syntax is using Method Decorators to JIT-bind methods through getters.
I've created a repo on GitHub to showcase an implementation of this idea (it's a bit lengthy to fit into an answer with its 40 lines of code, including comments), that you would use as simply as:
class DemonstrateScopingProblems {
private status = "blah";
#bound public run() {
alert(this.status);
}
}
I haven't seen this mentioned anywhere yet, but it works flawlessly. Also, there is no notable downside to this approach: the implementation of this decorator -- including some type-checking for runtime type-safety -- is trivial and straightforward, and comes with essentially zero overhead after the initial method call.
The essential part is defining the following getter on the class prototype, which is executed immediately before the first call:
get: function () {
// Create bound override on object instance. This will hide the original method on the prototype, and instead yield a bound version from the
// instance itself. The original method will no longer be accessible. Inside a getter, 'this' will refer to the instance.
var instance = this;
Object.defineProperty(instance, propKey.toString(), {
value: function () {
// This is effectively a lightweight bind() that skips many (here unnecessary) checks found in native implementations.
return originalMethod.apply(instance, arguments);
}
});
// The first invocation (per instance) will return the bound method from here. Subsequent calls will never reach this point, due to the way
// JavaScript runtimes look up properties on objects; the bound method, defined on the instance, will effectively hide it.
return instance[propKey];
}
Full source
The idea can be also taken one step further, by doing this in a class decorator instead, iterating over methods and defining the above property descriptor for each of them in one pass.
Necromancing.
There's an obvious simple solution that doesn't require arrow-functions (arrow-functions are 30% slower), or JIT-methods through getters.
That solution is to bind the this-context in the constructor.
class DemonstrateScopingProblems
{
constructor()
{
this.run = this.run.bind(this);
}
private status = "blah";
public run() {
alert(this.status);
}
}
You can write an autobind method to automatically bind all functions in the constructor of the class:
class DemonstrateScopingProblems
{
constructor()
{
this.autoBind(this);
}
[...]
}
export function autoBind(self)
{
for (const key of Object.getOwnPropertyNames(self.constructor.prototype))
{
const val = self[key];
if (key !== 'constructor' && typeof val === 'function')
{
// console.log(key);
self[key] = val.bind(self);
} // End if (key !== 'constructor' && typeof val === 'function')
} // Next key
return self;
} // End Function autoBind
Note that if you don't put the autobind-function into the same class as a member function, it's just autoBind(this); and not this.autoBind(this);
And also, the above autoBind function is dumbed down, to show the principle.
If you want this to work reliably, you need to test if the function is a getter/setter of a property as well, because otherwise - boom - if your class contains properties, that is.
Like this:
export function autoBind(self)
{
for (const key of Object.getOwnPropertyNames(self.constructor.prototype))
{
if (key !== 'constructor')
{
// console.log(key);
let desc = Object.getOwnPropertyDescriptor(self.constructor.prototype, key);
if (desc != null)
{
if (!desc.configurable) {
console.log("AUTOBIND-WARNING: Property \"" + key + "\" not configurable ! (" + self.constructor.name + ")");
continue;
}
let g = desc.get != null;
let s = desc.set != null;
if (g || s)
{
var newGetter = null;
var newSetter = null;
if (g)
newGetter = desc.get.bind(self);
if (s)
newSetter = desc.set.bind(self);
if (newGetter != null && newSetter == null) {
Object.defineProperty(self, key, {
get: newGetter,
enumerable: desc.enumerable,
configurable: desc.configurable
});
}
else if (newSetter != null && newGetter == null) {
Object.defineProperty(self, key, {
set: newSetter,
enumerable: desc.enumerable,
configurable: desc.configurable
});
}
else {
Object.defineProperty(self, key, {
get: newGetter,
set: newSetter,
enumerable: desc.enumerable,
configurable: desc.configurable
});
}
continue; // if it's a property, it can't be a function
} // End if (g || s)
} // End if (desc != null)
if (typeof (self[key]) === 'function')
{
let val = self[key];
self[key] = val.bind(self);
} // End if (typeof (self[key]) === 'function')
} // End if (key !== 'constructor')
} // Next key
return self;
} // End Function autoBind
In your code, have you tried just changing the last line as follows?
$(document).ready(() => thisTest.run());
Is it possible to call a function in AS3 using a string value as the function name e.g.
var functionName:String = "getDetails";
var instance1:MyObject = new MyObject();
instance1.functionName(); // I know this is so wrong, but it gets the point accross:)
UPDATE
The answer from #Taskinoor on accessing a function is correct:
instance1[functionName]();
And to access a property we would use:
instance1[propertyName]
instance1[functionName]();
Check this for some details.
You may use function.apply() or function.call() methods instead in the case when you dont know whether object has such method for instance.
var functionName:String = "getDetails";
var instance1:MyObject = new MyObject();
var function:Function = instance1[functionName]
if (function)
function.call(instance1, yourArguments)
I have created the following wrappers for calling a function. You can call it by its name or by the actual function. I tried to make these as error-prone as possible.
The following function converts a function name to the corresponding function given the scope.
public static function parseFunc(func:*, scope:Object):Function {
if (func is String && scope && scope.hasOwnProperty(funcName)) {
func = scope[func] as Function;
}
return func is Function ? func : null;
}
Call
Signature: call(func:*,scope:Object,...args):*
public static function call(func:*, scope:Object, ...args):* {
func = parseFunc(func, scope);
if (func) {
switch (args.length) {
case 0:
return func.call(scope);
case 1:
return func.call(scope, args[0]);
case 2:
return func.call(scope, args[0], args[1]);
case 3:
return func.call(scope, args[0], args[1], args[2]);
// Continue...
}
}
return null;
}
Apply
Signature: apply(func:*,scope:Object,argArray:*=null):*
public static function apply(func:*, scope:Object, argArray:*=null):* {
func = parseFunc(func, scope);
return func != null ? func.apply(scope, argArray) : null;
}
Notes
Call
The switch is needed, because both ...args and arguments.slice(2) are Arrays. You need to call Function.call() with variable arguments.
Apply
The built-in function (apply(thisArg:*, argArray:*):*) uses a non-typed argument for the argArray. I am just piggy-backing off of this.
I'm sort of jumping in headfirst to some Flex/AIR stuff. I have a pretty solid background with AS3, but given the inherent hierarchal complexity of Flex (compared to regular Flash), I'm running into an issue.
Let's assume that you have an app where pretty much everything is event driven (common). Accessing elements in the near vicinity of the event target, or the event target itself, is trivial. I'm trying to find, however, the most practical (read: best, most efficient) way to find children that are far removed from the current context.
I know there are functions like getChildAt() and getChildByName(), but that assumes a parent context; what if the element (Flex) you're looking for is several parents up, in a sibling, and then several children down? We take for granted things like jQuery that do this easily, but obviously we don't have that luxury in AS3.
Are any of the following valid? Is there a better way?
Iterate through parents and parents' parents until you find a stop point, find the sibling, and iterate through children and their children until you find your target;
Keep key objects in a global object store (sic) and reference them as necessary (yech)
Use specific dot notation to reach the target, including elements (like skins and their containers - yech again)
Any thoughts would be appreciated.
Edit:
To clarify, let's take an empty Flex 4 AIR app. We have WindowedApplication as the root, obviously, and let's add two SkinnableContainer children with IDs navContainer and mainContainer, respectively. Both have custom skins. Within mainContainer, we have another SkinnableContainer with a vertical layout and ID mainContent, and as one of its children, it has an object (any will do - a spark BorderContainer, maybe) with the ID animatedBox, for example. Within the navContainer, we have a spark Button, which has a listener bound for MouseEvent.CLICK. Within that function, we are going to want to access animatedBox (nativeWindow.mainContainer.mainContent.animatedBox) and animate it to change, say, it's width.
The goal is to access that distant DisplayObject (animatedBox) in a way that is as unobtrusive and efficient as possible, while still conforming to Flex standards that I clearly have yet to possess. :)
in my implementation it is easy to do (however it's in pure AS3):
in display object which handles the click:
private function onClick(e:MouseEvent):void{
Radio.broadcast(new CustomEvent(id, ..params));
}
in animatedBox:
Radio.addListener(id, new Reciever(uid, animate));
private function animate(e:CustomEvent) {
//needed code and access of CustomEvent props you pass
}
upd:
package lazylib.broadcast
{
/**
* ...
* #author www0z0k
*/
public class Reciever
{
private var id: String;
private var toRun: Function;
/*#param nm - unique listener id - required
*#param fn - event handler function - required*/
public function Reciever(nm:String, fn:Function)
{
id = nm;
toRun = fn;
}
public function onEvent(e:* = null):String {
if (e == null) { return id; }
toRun(e);
return id;
}
public function get ID():String { return id; }
}
}
and
package lazylib.broadcast
{
import flash.events.Event;
import flash.events.EventDispatcher;
/**
* ...
* #author www0z0k
*/
public final class Radio extends EventDispatcher
{
private static var listeners: Object = new Object();
private static var archive: Array = new Array();
private static var forSlowpokes: Object = new Object();
public static function get ForSlowpokes():Object { return forSlowpokes; }
public static function addListener(type: String , listener: Reciever):Boolean {
listeners['anchor'] = null;
if (!listeners[type]) {
var o: Object = new Object();
listeners[type] = o;
}
if (!listeners[type][listener.ID]) {
listeners[type][listener.ID] = listener;
return true;
}else {
return false;
}
}
public static function broadcast(evt: * , singleUse:Boolean = false):void {
var type:String = (evt as Event).type;
if (listeners[type]) {
var returned: Array = new Array();
for (var i: String in listeners[type]) {
if(listeners[type][i]){
var fnRetVal: String = listeners[type][i].onEvent(evt);
returned.push(fnRetVal);
}else{
//trace("no listener for id = " + i + ' , type = ' + type);
}
}
}else {
//trace("nobody's interested in : \"" + type + "\"");
}
if (singleUse) {
forSlowpokes[type] = 'you missed it realtime';
delete listeners[type];
}
}
public static function clearDeadFuncs(namez:Object):void {
for (var a:String in namez) {
if (a != 'anchor') {
killListener(a, namez[a]);
}
}
}
public static function killListener(type: String , id: String):Boolean {
if (!listeners[type]) {
//trace("there are no listeners for event : " + "\"" + type + "\"");
return false;
}else {
if (!listeners[type][id]) {
//trace("there is no \"" + id + "\" listener for event : " + "\"" + type + "\"");
return false;
}else {
listeners[type][id] = null;
//trace("removed listener \"" + id + "\" for event : " + "\"" + type + "\"");
var evt2kill: Number = 0;
for (var str: String in listeners[type]) {
if (listeners[type][str]) {
evt2kill++;
}
}
if (evt2kill == 0) {
delete listeners[type];
//trace("no more listeners for event : " + "\"" + type + "\"");
return true;
}
return true;
}
}
}
}
}
delivered as is ;)
We take for granted things like jQuery that do this easily, but obviously we don't have that luxury in AS3.
well there is this: http://tech.nitoyon.com/blog/2008/01/as3query_alpha.html
I asked myself this question also a lot of times. Still haven't figured out an ultimate solution to the problem. Iterating through parents and parents is definately a way but has to be taken with caution, cause relations might change in your application during runtime. I wrote a simple method a few days ago that lets you iterate through all parents of a given object. Definitely not an elegant solution but it works so far. the SWIZ framework also offers good methods to facilitate the communication between objects via code injection and Event mediation. Maybe worth a look...
A.as :
public class A {
public function getFunction():Function {
return function():void {
if(this is C) {
trace("C");
} else {
trace("not C");
}
}
}
public function func1():void {
var internalFunc:Function = getFunction();
internalFunc();
}
}
B.as :
public class B extends A implements C {
}
In some other class :
var b:B = new B();
B.func1();
Output is :
"Not C"
I was expecting the trace output to be
"C"
Can someone explain why?
An anonymous function, if called directly, is scoped to the global object. If you trace this inside it, you will see [object global] instead of [object B], as you would, if this refered to b.
A common workaround is using a closure:
var self:A = this;
return function():void {
if(self is C) {
trace("C");
} else {
trace("not C");
}
}
Please note however, the instance-members of a class defining an anonymous function are available from within. This works, because they are resolved at compile time.
edit in response to Amarghosh's question:
Yes, this points to the global object, but that doesn't mean, you cannot access the instance members of the declaring class. This little piece of code should explain the details:
package {
import flash.display.Sprite;
public class Test extends Sprite {
private var foo:String = "foo";
public function Test() {
var anonymous:Function = function ():void {
trace(foo);//foo
trace(this.foo);//undefined
};
anonymous();
}
}
}
greetz
back2dos
A few things with the code that I assume are just typos?
The getFunction() method doesn't return anything and will thus cause a compiler error.
Your call code calls func1() as a static method, not as a method on an instance of the B. This will also cause a compiler error. I believe these are typos.
In my tests, using your modified code. The output is C. There must be something else going on with your code. Here are my mods to A:
public function getFunction():Function {
if(this is C) {
trace("C");
} else {
trace("not C");
}
return getFunction;
}
Here is my mod to the runnable code, which I put in creationComplete of an empty MXML Application file:
var b:B = new B();
b.func1();
I assume your "real world" code is more extensive than the sample and there must be something else going on.