Adding variables to a class instance at runtime

From Rosetta Code

Jump to: navigation, search

Programming Task
This is a programming task. It lays out a problem which Rosetta Code users are encouraged to solve, using languages they know.

Code examples should be formatted along the lines of one of the existing prototypes.

This demonstrates how to dynamically add variables to a class instance at runtime. This is useful when the methods/variables are based on a data file that isn't available until runtime. Hal Fulton gives an example of creating an OO CSV parser at An Exercise in Metaprogramming with Ruby. This is referred to as "monkeypatching" by Pythonistas and some others. It's possible in most dynamic OO languages such as Python, Ruby, and Smalltalk.

Contents

[edit] Ada

Ada is not a dynamically typed language. Yet it supports mix-in inheritance, run-time inheritance and interfaces. These three allow us to achieve the desired effect, however questionably useful it could be. The example declares an interface of the class (Class). Then a concrete type is created (Base). The object E is an instance of Base. Later, at the run time, a new type Monkey_Patch is created such that it refers to E and implements the class interface per delegation to E. Monkey_Patch has a new integer member Foo and EE is an instance of Monkey_Path. For the user EE appears as E with Foo.

 
with Ada.Text_IO;  use Ada.Text_IO;
 
procedure Dynamic is
   package Abstract_Class is
      type Class is limited interface;
      function Boo (X : Class) return String is abstract;
   end Abstract_Class;
   use Abstract_Class;
 
   package Base_Class is
      type Base is new Class with null record;
      overriding function Boo (X : Base) return String;
   end Base_Class;
 
   package body Base_Class is
      function Boo (X : Base) return String is
      begin
         return "I am Class";
      end Boo;
   end Base_Class;
   use Base_Class;
 
   E : aliased Base;  -- An instance of Base
 
begin
   -- Gone run-time
   declare
      type Monkey_Patch (Root : access Base) is new Class with record
         Foo : Integer := 1;
      end record;
      overriding function Boo (X : Monkey_Patch) return String;
      function Boo (X : Monkey_Patch) return String is
      begin -- Delegation to the base
         return X.Root.Boo;
      end Boo;
      EE : Monkey_Patch (E'Access); -- Extend E
   begin
      Put_Line (EE.Boo & " with" & Integer'Image (EE.Foo));
   end;
end Dynamic;
 

Sample output:

I am Class with 1

[edit] Io

Empty := Object clone
e := Empty clone
e foo := 1 

[edit] JavaScript

e = {}       // generic object
e.foo = 1

[edit] Perl

Works with: Perl version 5.x

package Empty;

# Constructor. Object is hash.
sub new { return bless {}, shift; }

package main;

# Object.
my $o = Empty->new;

# Set runtime variable (key => value).
$o->{'foo'} = 1;

[edit] PHP

class E {};

$e=new E();

$e->foo=1;

[edit] Pop11

In Pop11 instance variables (slots) are specified at class creation time and there is no way to add new slot to an instance after its class was created. However, for most practical purposes one can obtain desired effect in different way. Namely, except for a few low-level routines slots in Pop11 are accessed via getter and setter methods. Getters and setters are like ordinary methods, but are automatically defined and "know" low level details of slot access. Pop11 allows dynamic definition of methods, and one can add new methods which work as "getter" and "setter" but do not store data directly in instance. One possibility is to have one instance variable which contains a hastable (this is essentially what Perl solution is doing). Another possibility (used below) is to create na external hashtable. Adding new slots typically make sense if slot name is only known at runtine, so we create method definition (as a list) at runtime and compile it using the 'pop11_compile' procedure.

lib objectclass;

define :class foo;
enddefine;

define define_named_method(method, class);
    lvars method_str = method >< '';
    lvars class_str = class >< '';
    lvars method_hash_str = 'hash_' >< length(class_str) >< '_'
                              >< class_str >< '_' >< length(method_str)
                              >< '_' >< method_str;
    lvars method_hash = consword(method_hash_str);
    pop11_compile([
        lvars ^method_hash = newassoc([]);
        define :method ^method(self : ^class);
            ^method_hash(self);
        enddefine;
        define :method updaterof ^method(val, self : ^class);
            val -> ^method_hash(self);
        enddefine;
    ]);
enddefine;

define_named_method("met1", "foo");
lvars bar = consfoo();
met1(bar) =>  ;;; default value -- false
"baz" -> met1(bar);
met1(bar) =>  ;;; new value

[edit] Python

class empty(object):
  pass
e = empty()

If the variable name is known at "compile" time:

e.foo = 1

If the variable name is only at runtime:

setattr(e, name, value)

[edit] Ruby

class Empty
end
e = Empty.new
e.instance_variable_set("@foo", 1)
e.instance_eval("class << self; attr_accessor :foo; end")
puts e.foo
Personal tools