Class field properties: Pass accessor identifier combination to the data inside Array, Vector etc. (Standard library DS)

Hi there!

I would like to know if there is a simple way to pass an accessor identifier combination to the data inside often used data structures from the standard library (Arrays, Vectors, Lists etc.). Just like one would write the following (but which is not accepted by the compiler):

var names (default, null ) : Vector< ((default, null ) : Name ) >;

As you can see I’m trying to protect the Name object inside the Vector from being written to, but still would like to grant read access…


(however I ony manage to protect either one of them:)

var names   (default, null ) : Vector<Name>; // only protects Vector itself
var my_name (default, null ) : Name;         // only protects a single Name object


So, is it that I need to create/code my own modified class of the DS I’d like to use?
I feel like this would be a great feature otherwise…

Thank you! :slight_smile:


Edit: Other ideas
(Partly, I can also rely on access control to protect the fields inside Name, but still I’d be able to overwrite the object itself like names[31] = new Name(); which should be prevented.)
:arrow_forward: Here’s a full sample with the amazing try.haxe!!

A fairly elegant way to do this would be to create a read-only “view” of the type using an abstract, by only exposing fields that don’t mutate any state. haxe.ds.ReadOnlyArray is an example of this:

1 Like

Ok, took me a while to study this response and to understand every word in it. The idea with only exposing “read” fields using an abstract must be an elegant way indeed it seems to me now…

I recoded my sample!!… my code’s not that elegant for now anyhow… :woozy_face: maybe with more experience… I struggle a bit to expose a public read-only DS what is a standard DS inside the class itself (without reallocating the DS), but it does what it should do right now! :+1: :smiley:

(I never understood this abstract but now I can grasp it’s really cool together with @:forward. I really only understand the concepts and Haxe features once I really have used/needed them. But then it’s really an upgrade of knowledge.)

So, this “view” concept I know that from SQL and maybe ENTT. But these days I think it’s really a fundamental thing in general to control read/write accesses. Often public is doing fine still.


edit: Wait! I’m getting closer to achieving more elegant code maybe :smiley:

var names : Vector<Name>;
public function get_names() {
        return cast( names, ReadOnlyVector<Dynamic> );
    }

Something like this!

1 Like

Regarding the edit, you could remove the explicit cast using Abstract’s implicit casting: Implicit Casts - Haxe - The Cross-platform Toolkit

ReadOnlyArray supports this already as you can see here.

So, that’s amazing. It seems this can really solve the issue. It’s not the one-liner/one word feature I required in the beginning, but needs only several lines of code, but then also they have much more power.

Also works with other DS!

import haxe.ds.ObjectMap;

class Main {
    static function main() {
        var b = new Blackbox();
        trace( b.read_map() );
    }
}

class Blackbox {
    var _map : ObjectMap<{},String>;
    public function read_map() : ReadOnlyObjectMap<{},String> {
        return _map;
    }
    public function new() {
        _map = new ObjectMap();
        _map.set( new SomeObjA(), "Hello!" );
        _map.set( new SomeObjA(), "Ciao bella!" );
        _map.set( new SomeObjB(), "We have that in store." );
    }
}

class SomeObjA {public function new() {}}
class SomeObjB {public function new() {}}

@:forward(get, exists, keys, iterator, keyValueIterator, copy, toString) // state mutating fields `set`, `remove` and `clear` omitted
abstract ReadOnlyObjectMap< K:{}, V >( ObjectMap<K,V> ) from ObjectMap<K,V> to ObjectMap<K,V> {}

A couple tweaks I’d make:

  • Omit to ObjectMap.
  • Ditch the Blackbox class, or make it a typedef. In practice of course you’ll be using classes, but in this example it isn’t contributing anything.
  • Optionally, use the fancy array syntax to define the map.
import haxe.ds.ObjectMap;

class Main {
    static function main() {
        var b:ReadOnlyObjectMap<{}, String> = [
            new SomeObjA() => "Hello!",
            new SomeObjA() => "Ciao bella!",
            new SomeObjB() => "We have that in store."
        ];
        trace( b );
    }
}

// typedef Blackbox = ReadOnlyObjectMap<{}, String>; // Optional.

class SomeObjA {public function new() {}}
class SomeObjB {public function new() {}}

@:forward(get, exists, keys, iterator, keyValueIterator, copy, toString)
abstract ReadOnlyObjectMap< K:{}, V >( ObjectMap<K,V> ) from ObjectMap<K,V> {}

Also note that copy() will return type ObjectMap<K, V>. That might not be a problem, but if it is, the solution is to declare a different return type.

@:forward(get, exists, keys, iterator, keyValueIterator, toString) // Omit `copy`.
abstract ReadOnlyObjectMap< K:{}, V >( ObjectMap<K,V> ) from ObjectMap<K,V> {
    public function copy():ReadOnlyObjectMap<K, V> {
        return this.copy();
    }
}
1 Like