Actionscript 'Object' labeled as a real datastructure for readability - actionscript-3

So in actionscript 3, instances of the Object class can be used an as associative array:
var doNotHaveSexWith:Object = new Object();
doNotHaveSexWith['mum'] = new Person(...);
doNotHaveSexWith['dad'] = new Person(...);
doNotHaveSexWith['dave'] = new Person(...);
Say I have some class, and one of it's members is a read only 'Object' which contains my collection of people.
I think code readability takes a major hit if I return this 'Object', as how would the programmer know what to do with it?
The only way someone is going to know that it is a collection is if they read the code or the comments...
What's the best way to signal that an Object is a collection, rather than a simple object?
Options:
Create a dynamic class, simply
extending from Object, called
"AssociativeArray" or something, just
so the code becomes more readable...
Use something like the AS3
Datastructures Library, though this
seems like a bit of overkill.
Just append the word Collection to
the end of the variable name?
For example:
var hotPeopleCollection:Object = new Object();
hotPeopleCollection['me'] = new Person(...);
hotPeopleCollection['sandrasully'] = new Person(...);
What do you think?
Update: I've decided to go with a custom class extending Dictionary. This way I can wrap a sensible access function around the searching function: hasOwnProperty and give the class a meaningful name.
I chose Dictionary over Object for two reasons:
Dictionary makes more intuitive sense for a collection
Dictionary appears to perform at O(1) for searching. See this fairly
informal dictionary vs array vs object performance benchmark

Use a dictionary.
http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/utils/Dictionary.html
You can still use a string for your key, or any object for that matter.
var dict:Dictionary = new Dictionary();
var obj:Object = new Object();
var key:Object = new Object();
key.toString = function() { return "key" }
dict[key] = "Letters";
obj["key"] = "Letters";
dict[key] == "Letters"; // true
obj["key"] == "Letters"; // true
obj[key] == "Letters"; // true because key == "key" is true because key.toString == "key"
dict["key"] == "Letters"; // false because "key" === key is false
delete dict[key]; //removes the key
A lot of developer ( myself included ) will tell you: Never use an Object. You are basically blindfolding your compiler. Always either use a built in datatype or make your own. Now obviously you didn't know about dictionaries in this case, but as a general rule, if you think you want to use a plain old Object datatype, think again.
Update:
Another link you might find helpful:
http://www.gskinner.com/blog/archives/2006/07/as3_dictionary.html

I'll be contrary and spout the much-hated dynamic-type stance. Just use an Object. It is an associative array. The names of the variables and the context should make it clear enough. Don't make your code any more complicated than it needs to be. If you think it should be a class, make it a class. It it's a simple map, use an Object.
There's no reason to go overboard.

Related

Can you navigate the contents of a Vector's index via a String?

Is it possible to do something similar to this in Haxe?
private var _bindingsFiltered:Vector<String>;
_bindingsFiltered = new Vector<String>();
_controller_touched_binding.action = "meta_start";
What I would like to be able to do:
_bindingsFiltered[_controller_touched_binding.action] = "BUTTON_13";
trace(_bindingsFiltered["meta_start"]); //result: "BUTTON_13"
I want to be able to override a specific index too (still accessed via a string), with a new value, rather than keep pushing new content to the end of the vector. I have been using 'openfl.utils.Object' to cheat for now but I am looking for a more reliable approach for the long run.
Is there a way to do this in Haxe?
If not, what are my options?
I would also be interested in a solution for this in AS3, if there is one (avoiding the Array class).
My goal is to find a method that I can use in both languages seamlessly (next-to-none, differences).
Vector's cannot be indexed by string in Haxe. A vector is an array with a fixed size. This is the Haxe manual on that subject.
Instead of vectors, you can use a Map.
class Test {
private var vector:Map<String, String> = new Map<String, String>();
public function new() {
var str = 'haxe';
vector[str] = "is great";
trace(vector[str]);
}
static function main() {
new Test();
}
}
https://try.haxe.org/#F74Ba
I think you could do this using flash.utils.Dictionary:
ActionScript
import flash.utils.Dictionary;
...
var _bindingsFiltered:Dictionary = new Dictionary ();
_bindingsFiltered[_controller_touched_binding.action] = "BUTTON_13";
trace(_bindingsFiltered["meta_start"]); //result: "BUTTON_13"
Haxe
import openfl.utils.Dictionary;
...
var _bindingsFiltered = new Dictionary<String, String> ();
_bindingsFiltered[_controller_touched_binding.action] = "BUTTON_13";
trace(_bindingsFiltered["meta_start"]); //result: "BUTTON_13"
First, do you really want an array / vector / list, or do you really want a hashmap of key / value pairs? How are you using the collection? Why do you want String keys? And related, is this mostly about access symantics (you want to type it this way), or are the runtime reasons you'd want to use strings (serialization / etc)?
From what you've described, it sounds like what you really want is an Object like the ones in AS3/JS/ECMAScript, with square-bracket access symantics -- obj[key]
Yes, you can do that in Haxe. The openfl.utils.Object class is a helper to do exactly this, using Dynamic objects and reflection. It should compile to exactly what you want on all Haxe targets.
In any case, if you'd like to feel like you're not bound to OpenFL, no problem. Copy the openfl/utils/Object.hx file and place it anywhere you like in your project's class path (and update the package statement).
There's nothing particularly OpenFL-ish about that code. It's pure Haxe code with no dependencies. It provides array access with String keys, as well as toString, toLocaleString, propertyIsEnumerable, iterator, isPrototypeOf, and hasOwnProperty functions (which ECMA-folk are used to.)
The transition from AS3/JS to Haxe is a little weird, especially when it comes to dynamic objects, and I've been meaning to blog more about it. ;) Good luck!
ETA: In truth, you probably want to get away from Dynamic/Reflection, and embrace a more type-strict approach. AS3/JS devs don't understand this at first, but it is where the benefits of Haxe come from. If you don't then your Haxe experience is likely to be unplesant.
Short answer: yes, you can.
abstract MyVector<T>(Vector<T>) {
public function new(l:Int) this = new Vector<T>(l);
#:op([]) public function set<K:T>(s:String, v:K) {
switch (s) {
case "FIRST": this[0] = v;
case "SECOND": this[1] = v;
default: return;
}
}
#:op([]) public function get(s:String) {
switch (s) {
case "FIRST": return this[0];
case "SECOND": return this[1];
default: return cast 0;
}
}
}
var mv = new MyVector<String>(2);
mv["SECOND"] = "Second";
trace(mv["SECOND"]); // outputs Second
You can inline get and set methods if you want.

What is the difference between Set,Map,WeakSet,WeakMap in ES6? [duplicate]

There is already some questions about map and weak maps, like this: What's the difference between ES6 Map and WeakMap? but I would like to ask in which situation should I favor the use of these data structures? Or what should I take in consideration when I favor one over the others?
Examples of the data structures from:https://github.com/lukehoban/es6features
// Sets
var s = new Set();
s.add("hello").add("goodbye").add("hello");
s.size === 2;
s.has("hello") === true;
// Maps
var m = new Map();
m.set("hello", 42);
m.set(s, 34);
m.get(s) == 34;
// Weak Maps
var wm = new WeakMap();
wm.set(s, { extra: 42 });
wm.size === undefined
// Weak Sets
var ws = new WeakSet();
ws.add({ data: 42 });
// Because the added object has no other references, it will not be held in the set
Bonus. Which of the above data structures will produce the same/similar result of doing: let hash = object.create(null); hash[index] = something;
This is covered in §23.3 of the specification:
If an object that is being used as the key of a WeakMap key/value pair is only reachable by following a chain of references that start within that WeakMap, then that key/value pair is inaccessible and is automatically removed from the WeakMap.
So the entries in a weak map, if their keys aren't referenced by anything else, will be reclaimed by garbage collection at some point.
In contrast, a Map holds a strong reference to its keys, preventing them from being garbage-collected if the map is the only thing referencing them.
MDN puts it like this:
The key in a WeakMap is held weakly. What this means is that, if there are no other strong references to the key, then the entire entry will be removed from the WeakMap by the garbage collector.
And WeakSet does the same.
...in which situation should I favor the use of this data structures?
Any situation where you don't want the fact you have a map/set using a key to prevent that key from being garbage-collected. Here are some examples:
Having instance-specific information which is truly private to the instance, which looks like this: (Note: This example is from 2015, well before private fields were an option. Here in 2021, I'd use private fields for this.)
let Thing = (() => {
var privateData = new WeakMap();
class Thing {
constructor() {
privateData[this] = {
foo: "some value"
};
}
doSomething() {
console.log(privateData[this].foo);
}
}
return Thing;
})();
There's no way for code outside that scoping function to access the data in privateData. That data is keyed by the instance itself. You wouldn't do that without a WeakMap because it would be a memory leak, your Thing instances would never be cleaned up. But WeakMap only holds weak references, and so if your code using a Thing instance is done with it and releases its reference to the instance, the WeakMap doesn't prevent the instance from being garbage-collected; instead, the entry keyed by the instance is removed from the map.
Holding information for objects you don't control. Suppose you get an object from some API and you need to remember some additional information about that object. You could add properties to the object itself (if it's not sealed), but adding properties to objets outside of your control is just asking for trouble. Instead, you can use a WeakMap keyed by the object to store your extra information.
One use case for WeakSet is tracking or branding: Suppose that before "using" an object, you need to know whether that object has ever been "used" in the past, but without storing that as a flag on the object (perhaps because if it's a flag on the object, other code can see it [though you could use a private field to prevent that]; or because it's not your object [so private fields wouldn't help]). For instance, this might be some kind of single-use access token. A WeakSet is a simple way to do that without forcing the object to stay in memory.
Which of the above data structures will produce the same/similar result of doing: let hash = Object.create(null); hash[index] = something;
That would be nearest to Map, because the string index (the property name) will be held by a strong reference in the object (it and its associated property will not be reclaimed if nothing else references it).

Initializing unknown type in Actionscript 3.0

To keep my program running smoothly, I have to create objects only when I need them. In my case, I want to see if an object of an unspecified class is null, and if not, then call tell it to initialize. (I think "initialize" is the correct term, correct me if I am wrong)
if (someObject == null) {
someObject = new someObject.class()
}
Obviously, this code does not work. The idea seems right, but Actionscript does not agree with me. How can this be achieved?
Edit - Perhaps this will help to simplify (or complicate) the answer. I know that someObject extends my class SuperClass. Can I use a property from SuperClass to instantiate someObject?
If you know what type someObject is supposed to be, it's easy:
var someObject:SomeClass;
if(someObject==null)
{
someObject = new SomeClass(); // creates an instance of the class
}
If you don't, you're making it hard for yourself. You'll have to rethink your design I guess.
I'm going to make an attempt at getting on the right track toward a useful answer.
Firstly, looking at your example:
if (someObject == null) {
someObject = new someObject.class()
}
I'm not sure what you're trying to convey here. If someObject is null, you cannot extract any information about anything from it. Even if you type someObject e.g:
var someObject:Sprite;
You cannot access information about the fact that the property someObject is expecting a Sprite*
*You can use describeType() to access the type if someObject is a member of a class, but lets not go there.
If you want to be able to:
Check if an instance of a specific class exists, and if not;
Create an instance of the specified class.
You can write something like:
var existing:Object = { };
function findOrMake(type:Class):*
{
var key:String = type.toString();
if(!existing.hasOwnProperty(key))
{
// We need to make a new instance of this class.
existing[key] = new type();
}
return existing[key];
}
Which can be used like:
var someObject:Sprite = findOrMake(Sprite); // Makes a new Sprite.
someObject = findOrMake(Sprite); // References the above Sprite.
Don't name the instance with the exact name of the class you're instantiating.
If you do this:
var someobject;
if (someobject == null)
{
someobject = new someObject()
}
it works.

Typed AS3 JSON Encoder and Decoder?

I need to encode and Decode AS3 Objects in a typed manner. http://code.google.com/p/as3corelib/ only supports untyped encoding and decoding.
http://code.google.com/p/ason/ supports some kind of typed objects but is not very robust, e.g. it fails on Date Objects. Any Recommendations ?
To make it clear: It MUST be JSON and it MUST be strong typed and robust.
JSON is built in in AS3. The preferred method to transmit data over the wire is AMF, which does provide you typed objects.
If you have to use JSON, then I guess that you might have to do with some sort of custom protocol to be able encode/decode with types.
You would actually need a reflection utility that read beans in JSON format and then produce your object. It really depends on how deep you want to go.
as3Commons has a reflect package that could help. They also have a JSONTypeProvider, which is not exactly what you need but can put you in the right tract.
You could modify any of the IOC frameworks to produce the context by parsing JSON instead of the regular XML most of them use.
You could modify ASON and add a custom type parser. You would have to send a variable in your JSON object containing the type of the object. And use that in with flash.utils.getDefinitionByName.
Another approach would be to just parse the objects with a regular JSON parser and then if it has a defined type create an instance of that objet, and initialize the properties.
Something like this, to get you started:
var beanInfo:Object = JSON.decode( jsonString );
beanInfo = _parseBean( beanInfo );
private function _parseBean(beanInfo:Object):Object{
if ( beanInfo.hasOwnProperty("_type") ) {
var clazz:Class = getDefinitionByName( beanInfo._type ) as Class;
beanInfo.__clazz = clazz;
var instance:Object = new clazz;
for( var prop:String in beanInfo ) {
if( instance.hasOwnProperty(prop) ) target[prop] = _getPropertyFrom(beanInfo[prop]);
}
}
}
private function _getPropertyFrom(property:String):* {
var xml:XML = describeType( beanInfo.__clazz );
//find the type of the current property.
var type:String = xml...
//if is a simple object then do something like
switch( type ) {
case "number":
return parseFloat(property ) as Number;
break;
case "int":
case "uint":
return parseInt( property );
break;
case "string":
return property as String;
break;
...
default
//As it is it does not suppor complex objects.
//You would use reflection. But then you could save the whole switch...
break;
}
}
Flash has its own serialization system.
var serializer:ByteArray = new ByteArray();
serializer.writeObject(new Sprite());
serializer.position = 0;
var data:String = serializer.readUTFBytes(serializer.bytesAvailable);
trace(data); //Will show you the binary jibberish
You can use registerClassAlias to add support for custom classes.
JSON doens't really define a means to convey type information. It's just strings and ints and arrays and so on. So basically you need some sort of "pickle" for AS3 that's based on JSON. I would suggest you look into Flex/Flash remoting solutions and see how they package objects to be transmitted for RPC; you might be able to modify that solution to use JSON. I'm actually doubtful you'll find a library like this. Does it have to be JSON? I'm pretty sure there are XML based libraries that do this.
JSON is not implemented in the flash virtual machine, and therefore there is no typed object "JSON" as there is "Xml." So basically you can decode JSON just fine, but the type you're going to get is Object. You can them access data using the key in the object as an associative array.
http://blog.alien109.com/2009/02/11/php5-json-as3corelib-a-beautiful-thing/
JSON lib/utils official from adobe:
http://code.google.com/p/as3corelib/source/browse/#svn%2Ftrunk%2Fsrc%2Fcom%2Fadobe%2Fserialization%2Fjson
As good as it gets. :)
There are two operations you need to consider: 1) serializing an object of a particular type into JSON and 2) deserializing a JSON string into an object of a particular type.
The serialization part is easy - just use the built-in JSON.stringify(). Deserializing a JSON string into an object of a particular type in ActionScript is where it gets interesting (and where the answer to your question is). You need to write your own deserialization function for the classe(s) you will need to deserialize. In that function, you need to provide a reviver function to JSON.parse(), which allows you to customize how the JSON gets deserialized.
For example:
public static function deserializeComplexObject(json:String):ComplexObject
{
if (null == json || "null" == json)
{
return null;
}
var complexObject:ComplexObject = new ComplexObject();
var parsedObject:Object = JSON.parse(
json,
function (key:String, value:Object):*
{
switch (key)
{
case “keyForNumber”:
return value;
case “keyForComplexObject2”:
return deserializeComplexObject2(JSON.stringify(value));
case “keyForComplexObject3”:
return deserializeComplexObject3(JSON.stringify(value));
case “keyForString”:
return value;
case “keyForBoolean”:
return value;
default:
return value;
}
}
);
complexObject.keyForNumber = parsedObject.keyForNumber;
complexObject.keyForComplexObject2 = parsedObject.keyForComplexObject2;
// continue setting fields
// …
return complexObject;
}
Each case statement corresponds to a top-level key in the JSON string. You don't actually need separate case statements for every key - you can use the default case to handle all keys that map to values that are one of the simple types (Object, Array, String, Number, Boolean, null) by returning the value as-is.
I have now forked the json part of http://code.google.com/p/as3corelib/ and added typed object support...

Creating a "true" HashMap implementation with Object Equality in ActionScript 3

I've been spending some of my spare time working a set of collections for ActionScript 3 but I've hit a pretty serious roadblock thanks for the way ActionScript 3 handles equality checks inside Dictionary Objects.
When you compare a key in a dictionary, ActionScript uses the === operator to perform the comparison, this has a bit of a nasty side effect whereby only references to the same instance will resolve true and not objects of equality. Here's what I mean:
const jonny1 : Person = new Person("jonny", 26);
const jonny2 : Person = new Person("jonny", 26);
const table : Dictionary = new Dictionary();
table[jonny1] = "That's me";
trace(table[jonny1]) // traces: "That's me"
trace(table[jonny2]) // traces: undefined.
The way I am attempting to combat this is to provide an Equalizer interface which looks like this:
public interface Equalizer
{
function equals(object : Object) : Boolean;
}
This allows to to perform an instanceOf-esq. check whenever I need to perform an equality operation inside my collections (falling back on the === operator when the object doesn't implement Equalizer); however, this doesn't get around the fact that my underlying datastructure (the Dictionary Object) has no knowledge of this.
The way I am currently working around the issue is by iterating through all the keys in the dictionary and performing the equality check whenever I perform a containsKey() or get() operation - however, this pretty much defeats the entire point of a hashmap (cheap lookup operations).
If I am unable to continue using a Dictionary instance as the backing for map, how would I go about creating the hashes for unique object instances passed in as keys so I can still maintain equality?
How about you compute a hash code for your objects when you insert them, and then look them up by the hash code in your backing dictionary? The hashcode should compare === just fine. Of course, that would require you to have a Hashable interface for your object types instead of your Equalizer interface, so it isn't much less work than you are already doing, but you do get the cheap lookups.
How about rather doing this:
public interface Hashable {
function hash():String;
}
personally, I ask myself, why you want to do this ... hashing objects to obtain keys makes little sense if they are mutable ...
also, you might consider using a different approach, as for example this factory:
package {
public class Person {
/**
* don't use this!
* #private
*/
public function Person(name:String, age:int) {
if (!instantiationAllowed)
throw new Error("use Person.getPerson instead of constructor");
//...
}
private static var instantiationAllowed:Boolean = false;
private static var map:Object = {};
private static function create(name:String, age:int):Person {
instantiationAllowed = true;
var ret:Person = new Person(name, age);
instantiationAllowed = false;
}
public static function getPerson(name:String, age:int):Person {
var ageMap:Array = map[name];
if (ageMap == null) {
map[name] = ageMap = [];
return ageMap[age] = Person.create(name, age);
}
if (ageMap.hasOwnProperty(age))
return ageMap[age];
return ageMap[age] = Person.create(name, age);
}
}
}
it ensures, there's only one person with a given name and age (if that makes any sense) ...
Old thread I know, but still worth posting.
const jonny1 : Person = new Person("jonny", 26); const jonny2 : Person = new Person("jonny", 26);
is creating two completely different objects that will not compare using ==, guess I don't see why it's any more of a road block because of as3
The problem with AS3/JavaScript/EcmaScript is not that they create two different, equivalent objects.
The problem is that they cannot equate those two equivalent objects--only identity works, since there is no equals or hashCode methods that can be overriden with class-specific comparison logic.
For Map implementations such as dynamic Object or Dictionary, this means that you have to either use Strings or references as keys: you cannot recover objects from a map using different but equivalent objects.
To work around that problem, people either resort to strict toString implementations (for Object maps) which is undesirable, or to instance control for Dictionaries, as in #back2dos example, which introduces different problems (Also, note that #back2dos solution does not really guarantee unique Person instances since there is a time window during which asynchronous threads will be allowed to instantiate new Persons).
#A.Levy's solution is good except that in general, hashCodes are not strictly required to issue unique values (they are meant to map entries to buckets allowing for fast lookups, wherein fine-grained differentiation is done through equals method).
You need both a hashCode and an equals method, e.g.
public interface IEquable
{
function equals(object : Object) : Boolean;
function hash():String;
}
In any programming language,
const jonny1 : Person = new Person("jonny", 26);
const jonny2 : Person = new Person("jonny", 26);
is creating two completely different objects that will not compare using ==, guess I don't see why it's any more of a road block because of as3