Search code examples
c#perldynamic.net-4.0autovivification

Autovivification in C#


Trying to wrap my head around perl's Autovivification and based on what it sounds like, It seems to work similar to dynamics in C# as a dynamic object is not assigned a type until runtime or, am I totally off here. If so then is there a comparable idea that I can bridge off of in C# that makes sense?

Edit
Okay so I'm apparently way off. So as second part of the 2 part question, is there anything conceptually comparable in C#? To be clear I'm looking for a concept in C# that is comparable to Autovivification. Doesn't have to be exactly the same but close enough conceptually to make sense. And as I stated eariler I am by no means a perl hacker or python hacker by any stretch of the imagination but, I am familar with c based languages C, C++, C#, java, javascript. I was thinking of C#'s dynamics but, as of right now I'm thinking lazy loading based on the info here if that helps....


Solution

  • I can't speak to C#, but in layman's terms, Perl's autovivification is the process of creating a container object out of an undefined value as soon as it is needed.

    Despite most of Perl being quite dynamic, Perl's dereferencing syntax unambiguously specifies the type of the reference at compile time. This allows the interpreter to know what it needs out of a variable before the variable is ever defined.

    my $var;  # undefined
    
    # to autovivify to an array:
    @$var = 1..5;  # @ here implies ARRAY
    $$var[4] = 5;  # square brackets imply ARRAY
    $#$var;        # $# implies ARRAY (returns the last index number)
    
    # to autovivify to a hash:
    
    %$var = (a => 1);   # % implies HASH
    $$var{asdf} = 5;    # curly braces imply HASH
    

    This list could be longer, but should give you an idea.

    So basically, when you have a line like this:

    my $var;
    $var->[1]{x}[3]{asdf}
    

    Perl looks on the right side of the -> and sees square braces. This means that the invocant $var must be an array reference. Since the invocant is undefined, Perl creates a new array and installs its reference into $var. This same process is then repeated for every subsequent dereferencing.

    So the line above really means:

        (((($var //= [])->[1] //= {})->{x} //= [])->[3] //= {})->{asdf};
    

    which is fairly hideous, and hence autovivification. (//= is the defined-or assignment operator in perl 5.10+)

    Update:

    As per cjm's comment, to put this into general non-perl terms, to achieve autovivification in another language, you need a lazy object that supports indexing via [...] and {...}. When either of these indexing operations are performed, the object replaces itself with either an array or hash. Every time the object is then accessed, if the cell is empty, it should return another lazy object.

    obj = new lazy_obj()
    
    level1 = obj[4]   # sets obj to be an array, returns a new lazy_obj for level1
    
    level2 = level1{asdf}  # sets level1 (and obj[4]) to a hash,
                           # returns a new lazy_obj for level2
    

    So basically you need two things, the ability to create an object that supports indexing with both array and hash subscripts (or the equivalent), and a mechanism such that an object can replace itself in memory with another object (or that can lock itself to one interpretation, and then store the new object internally.

    Something like the following pseudo-code could be a start:

    class autoviv {
       private var content;
    
       method array_subscript (idx) {
           if (!content) {
               content = new Array();
           }
           if (typeof content == Array) {
                if (exists content[idx]) return content[idx];
                return content[idx] = new autoviv();
           } else {
                throw error
           }
       }
    
       method hash_subscript (idx) {
           if (!content) {
               content = new Hash();
           }
           if (typeof content == Hash) {
                if (exists content{idx}) return content{idx};
                return content{idx} = new autoviv();
           } else {
                throw error
           }
       }
       // overload all other access to return undefined, so that the value
       // still looks empty for code like:
       //
       // var auto = new autoviv(); 
       // if (typeof auto[4] == autoviv) {should run}
       // if (auto[4]) {should not run}
    }