
Demonstrate how to dynamically add variables to an object (a class instance) at runtime.
This is useful when the methods/variables of an instance are based on a data file that isn't available until runtime. Hal Fulton gives an example of creating an OO CSV parser atAn Exercise in Metaprogramming with Ruby. This is referred to as "monkeypatching" by Pythonistas and some others.
In ActionScript this can be done using an Object object
varobject:Object=newObject();object.foo="bar";
Or by creating a dynamic class
package{publicdynamicclassFoo{// ...}}
varfoo:Foo=newFoo();foo.bar="zap";
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.
withAda.Text_IO;useAda.Text_IO;procedureDynamicispackageAbstract_ClassistypeClassislimitedinterface;functionBoo(X:Class)returnStringisabstract;endAbstract_Class;useAbstract_Class;packageBase_ClassistypeBaseisnewClasswithnull record;overridingfunctionBoo(X:Base)returnString;endBase_Class;packagebodyBase_ClassisfunctionBoo(X:Base)returnStringisbeginreturn"I am Class";endBoo;endBase_Class;useBase_Class;E:aliasedBase;-- An instance of Basebegin-- Gone run-timedeclaretypeMonkey_Patch(Root:accessBase)isnewClasswithrecordFoo:Integer:=1;end record;overridingfunctionBoo(X:Monkey_Patch)returnString;functionBoo(X:Monkey_Patch)returnStringisbegin-- Delegation to the basereturnX.Root.Boo;endBoo;EE:Monkey_Patch(E'Access);-- Extend EbeginPut_Line(EE.Boo&" with"&Integer'Image(EE.Foo));end;endDynamic;
Sample output:
I am Class with 1
define:myClass[name,surname]myInstance:to:myClass["John""Doe"]printmyInstancemyInstance\age:35printmyInstance
[name:John surname:Doe][name:John surname:Doe age:35]
e:={}e.foo:=1
Although Ballerina is statically typed, it has a data structure called an 'open record' which allows additional fields (of type 'anydata') to be added at runtime. This is possible because records work like a kind of mini-map which maps fields to values. An example is given below.
Note that whilst Ballerina also supports 'classes' they cannot be extended in this way.
importballerina/io;// Person is an 'open' record type which allows fields of 'anydata' type// to be added at runtime.typePersonrecord{stringname;intage;};publicfunctionmain(){// Create an instance of Person with an additional 'town' field.Personp={name:"Fred",age:40,"town":"Boston"// extra field name needs to be in quotes};// Print name and age fields - using standard '.' syntax.io:print(p.name," is ",p.age);// Print the additional field - using a map-like syntax.io:println(" and lives in ",p["town"],".");}
Fred is 40 and lives in Boston.
It's not really intended that you should do this, but if you must you can:
INSTALL@lib$+"CLASSLIB"REM Create a base class with no members:DIMclass{method}PROC_class(class{})REM Instantiate the class:PROC_new(myobject{},class{})REM Add a member at run-time:member$="mymember#"PROCaddmember(myobject{},member$,8)REM Test that the member can be accessed:PROCassign("myobject."+member$,"PI")PRINTEVAL("myobject."+member$)ENDDEFPROCaddmember(RETURNobj{},mem$,size%)LOCALD%,F%,P%DIMD%DIM(obj{})+size%-1,F%LEN(mem$)+8P%=!^obj{}+4WHILE!P%:P%=!P%:ENDWHILE:!P%=F%$$(F%+4)=mem$:F%!(LEN(mem$)+5)=DIM(obj{})!(^obj{}+4)=D%ENDPROCDEFPROCassign(v$,n$)IFEVAL("FNassign("+v$+","+n$+")")ENDPROCDEFFNassign(RETURNn,v):n=v:=0
This solution saves the original members and methods in a variable, using pattern matching. Then, using macro expansion, a new object is created with an additional member variable and also an additional method. Because the new object is assigned to the same variable as the original object, the original object ceases to exist.
( ( struktuur = (aMember=) (aMethod=.!(its.aMember)) )& new$struktuur:?object& out$"Object as originally created:"& lst$object& A value:?(object..aMember)& !object:(=?originalMembersAndMethods)& new $ ( ' ( (anotherMember=) (anotherMethod=.!(its.anotherMember)) ()$originalMembersAndMethods ) ) : ?object& out $ "Object with additional member and method and with 'aMember' already set to some interesting value:"& lst$object& some other value:?(object..anotherMember)& out$"Call both methods and output their return values."& out$("aMember contains:" (object..aMethod)$)& out$("anotherMember contains:" (object..anotherMethod)$)&);Output:
Object as originally created:(object==(aMember=) (aMethod=.!(its.aMember)));Object with additional member and method and with 'aMember' already set to some interesting value:(object== (anotherMember=) (anotherMethod=.!(its.anotherMember)) (aMember=A value) (aMethod=.!(its.aMember)));Call both methods and output their return values.aMember contains: A valueanotherMember contains: some other value
// ----------------------------------------------------------------------------------------------//// Program.cs - DynamicClassVariable//// Mikko Puonti, 2013//// ----------------------------------------------------------------------------------------------usingSystem;usingSystem.Dynamic;namespaceDynamicClassVariable{internalstaticclassProgram{#region Static MembersprivatestaticvoidMain(){// To enable late binding, we must use dynamic keyword// ExpandoObject readily implements IDynamicMetaObjectProvider which allows us to do some dynamic magicdynamicsampleObj=newExpandoObject();// Adding a new propertysampleObj.bar=1;Console.WriteLine("sampleObj.bar = {0}",sampleObj.bar);// We can also add dynamically methods and events to expando object// More information: http://msdn.microsoft.com/en-us/library/system.dynamic.expandoobject.aspx// This sample only show very small part of dynamic language features - there is lot's moreConsole.WriteLine("< Press any key >");Console.ReadKey();}#endregion}}
sampleObj.bar = 1< Press any key >
Adding variables to a class at runtime is not possible in C++ which requires the names of all class variables to be known at compile time. However, we can make it appear as though variables are being added at runtime by using a Map or similar structure.
#include<cstdint>#include<iostream>#include<map>#include<string>classDemonstration{public:Demonstration(){variables={};}std::map<std::string,double>variables;};intmain(){Demonstrationdemo;std::cout<<"Create two variables at runtime:"<<std::endl;for(uint32_ti=1;i<=2;++i){std::cout<<" Variable number "<<i<<":"<<std::endl;std::cout<<" Enter name: "<<std::endl;std::stringname;std::cin>>name;std::cout<<" Enter value: "<<std::endl;doublevalue;std::cin>>value;demo.variables[name]=value;}std::cout<<std::endl;std::cout<<"Two new runtime variables appear to have been created."<<std::endl;for(constauto&[key,value]:demo.variables){std::cout<<"Variable "<<key<<" = "<<value<<std::endl;}}
Create two variables at runtime: Variable number 1: Enter name: Test Enter value: 2.0 Variable number 2: Enter name: Item Enter value: 3.14Two new runtime variables appear to have been created.Variable Item = 3.14Variable Test = 2
# CoffeeScript is dynamic, just like the Javascript it compiles to.# You can dynamically add attributes to objects.# First create an object very simply.e={}e.foo="bar"e.yo=->"baz"console.loge.foo,e.yo()# CS also has class syntax to instantiate objects, the details of which# aren't shown here. The mechanism to add members is the same, though.classEmpty# empty classe=newEmpty()e.foo="bar"e.yo=->"baz"console.loge.foo,e.yo()
This version adds a new slot only to one instance, not to the whole class.
(defunaugment-instance-with-slots(instanceslots)(change-classinstance(make-instance'standard-class:direct-superclasses(list(class-ofinstance)):direct-slotsslots)))
Example:
CL-USER>(let*((instance(make-instance'foo:bar42:baz69))(new-slots'((:namexenu:initargs(:xenu)))))(augment-instance-with-slotsinstancenew-slots)(reinitialize-instanceinstance:xenu666)(describeinstance))#<#<STANDARD-CLASSNIL{1003AEE2C1}>{1003AEE271}>[standard-object]Slotswith:INSTANCEallocation:BAR=42BAZ=69XENU=666
The following REPL transcript (fromLispWorks) shows the definition of a classsome-class with no slots, and the creation of an instance of the class. The first attempt to access the slot namedslot1 signals an error as there is no such slot. Then the class is redefined to have such a slot, and with a default value of 23. Attempting to access the slot in the preëxisting instance now gives the default value, since the slot has been added to the instance. This behavior is specified in§4.3.6 Redefining Classes of theHyperSpec.
CL-USER 57 > (defclass some-class () ())#<STANDARD-CLASS SOME-CLASS 200BF63B>CL-USER 58 > (defparameter *an-instance* (make-instance 'some-class))*AN-INSTANCE*CL-USER 59 > (slot-value *an-instance* 'slot1)Error: The slot SLOT1 is missing from #<SOME-CLASS 21F59E37> (of class #<STANDARD-CLASS SOME-CLASS 200BF63B>), when reading the value. 1 (abort) Return to level 0. 2 Return to top loop level 0.Type :b for backtrace, :c <option number> to proceed, or :? for other optionsCL-USER 60 : 1 > :aCL-USER 61 > (defclass some-class () ((slot1 :initform 23)))#<STANDARD-CLASS SOME-CLASS 200BF63B>CL-USER 62 > (slot-value *an-instance* 'slot1)23
structDynamic(T){privateT[string]vars;@propertyTopDispatch(stringkey)()purenothrow{returnvars[key];}@propertyvoidopDispatch(stringkey,U)(Uvalue)purenothrow{vars[key]=value;}}voidmain(){importstd.variant,std.stdio;// If the type of the attributes is known at compile-time:autod1=Dynamic!double();d1.first=10.5;d1.second=20.2;writeln(d1.first," ",d1.second);// If the type of the attributes is mixed:autod2=Dynamic!Variant();d2.a="Hello";d2.b=11;d2.c=['x':2,'y':4];d2.d=(intx)=>x^^3;writeln(d2.a," ",d2.b," ",d2.c);immutableintx=d2.b.get!int;}
10.5 20.2Hello 11 ['x':2, 'y':4]
If you want Dynamic to be a class the code is similar. If the attribute names aren't known at compile-time, you have to use a more normal syntax:
importstd.stdio,std.variant,std.conv;structDyn{Variant[string]data;aliasdatathis;}voidmain(string[]args){Dynd;constattribute_name=text("attribute_",args.length);d[attribute_name]="something";writeln(d[attribute_name]);}
something
ELENA does not support adding a field at run-time but it can be simulated with the help of a mix-in.
ELENA 6.x:
import extensions;class Extender : BaseExtender{ object foo : prop; constructor(object) { this object := object }}public Program(){ var object := 234; // extending an object with a field object := new Extender(object); object.foo := "bar"; Console.printLine(object,".foo=",object.foo); Console.readChar()}234.foo=bar
Classes and singleton objects have a fixed structure which cannot be changed during runtime. However falcon does have capability to add variables/functions at runtime with Prototype based objects. Below are two of the prototype objects that allow adding variables at runtime. These are arrays and dictionaries (hashes for the perl type out there).
Array:In this example we add a function (which prints out the content of the array) and a new value. While we are not technically adding a "variable", this example is presented to show similar type of functionality.
vect = [ 'alpha', 'beta', 'gamma' ]vect.dump = function () for n in [0: self.len()] > @"$(n): ", self[n] endendvect += 'delta'vect.dump()
Output from the above:
0: alpha1: beta2: gamma3: delta
Dictionary:In this example we will add a variable through the use of an object from a bless'ed dictionary. We create a new variable called 'newVar' at runtime and assign a string to it. Additionally we assign an external, to the object, function (sub_func) to the variable 'sub'.
function sub_func( value ) self['prop'] -= value return self.propenddict = bless( [ 'prop' => 0, 'add' => function ( value ) self.prop += value return self.prop end , 'sub' => sub_func])dict[ 'newVar' ] = "I'm Rich In Data"
FBSL class instances aren't expandable with additional, directly accessible public methods at runtime once the class template is defined in the user code. But FBSL has an extremely powerful feature -- an ExecLine() function -- which permits the user to execute any additional code on the fly either privately (bypassing the main code flow) or publicly (interacting with the main code). ExecLine() can be used for a variety of applications from the fine-tuning of current tasks to designing application plug-ins or completely standalone code debuggers. The following class instance may be stuffed up at runtime with any code from simple variables to executable private methods and properties.
#APPTYPECONSOLECLASSGrowablePRIVATE:DIMinstructionsASSTRING="Sleep(1)":ExecCodeDIMdummyASINTEGER=EXECLINE(instructions,1)PUBLIC:METHODAbsorb(codeASSTRING)instructions=codeGOTOExecCodeENDMETHODMETHODYield()ASVARIANTRETURNresultENDMETHODENDCLASSDIMSpongeASNEWGrowable()Sponge.Absorb("DIM b AS VARIANT = 1234567890: DIM result AS VARIANT = b")PRINTSponge.Yield()Sponge.Absorb("b = ""Hello world!"": result = b")PRINTSponge.Yield()PAUSE
Output:
1234567890Hello world!Press any key to continue...
Works with any ANS Forth
Needs the FMS-SI (single inheritance) library code located here:http://soton.mpeforth.com/flag/fms/index.html
includeFMS-SI.fincludeFMS-SILib.f\ We can add any number of variables at runtime by adding\ objects of any type to an instance at run time. The added\ objects are then accessible via an index number.:classfooobject-listinst-objects\ a dynamically growable object container:minit:inst-objectsinit:;m:madd:( obj -- )inst-objectsadd:;m:mat:( idx -- obj )inst-objectsat:;m;classfoofoo1:mainheap>stringfoo1add:heap>fvarfoo1add:s"Now is the time"0foo1at:!:3.14159e1foo1at:!:0foo1at:p:\ send the print message to indexed object 01foo1at:p:\ send the print message to indexed object 1;main\ => Now is the time 3.14159
' Class ... End Class' Esta característica aún no está implementada en el compilador.
Firstly, Go doesn't have classes or class variables but does have structs (an analogous concept) which consist of fields.
Adding fields to a struct at runtime is not possible as Go is a statically typed, compiled language and therefore the names of all struct fields need to be known at compile time.
However, as in the case of Groovy and Kotlin, we canmake it appear as though fields are being added at runtime by using the built-in map type. For example:
packagemainimport("bufio""fmt""log""os")typeSomeStructstruct{runtimeFieldsmap[string]string}funccheck(errerror){iferr!=nil{log.Fatal(err)}}funcmain(){ss:=SomeStruct{make(map[string]string)}scanner:=bufio.NewScanner(os.Stdin)fmt.Println("Create two fields at runtime: ")fori:=1;i<=2;i++{fmt.Printf(" Field #%d:\n",i)fmt.Print(" Enter name : ")scanner.Scan()name:=scanner.Text()fmt.Print(" Enter value : ")scanner.Scan()value:=scanner.Text()check(scanner.Err())ss.runtimeFields[name]=valuefmt.Println()}for{fmt.Print("Which field do you want to inspect ? ")scanner.Scan()name:=scanner.Text()check(scanner.Err())value,ok:=ss.runtimeFields[name]if!ok{fmt.Println("There is no field of that name, try again")}else{fmt.Printf("Its value is '%s'\n",value)return}}}
Sample input/output:
Create two fields at runtime: Field #1: Enter name : a Enter value : rosetta Field #2: Enter name : b Enter value : 64Which field do you want to inspect ? aIts value is 'rosetta'
AnyGroovy class that implements "Object get(String)" and "void set(String, Object)" will have theapparent capability to add new properties. However, this capability will only work as expected with an appropriate implementation, backed by a Map object or something very much like a Map.
classA{finalx={it+25}privatemap=newHashMap()Objectget(Stringkey){map[key]}voidset(Stringkey,Objectvalue){map[key]=value}}
Test:
defa=newA()a.y=55a.z={println(newDate());Thread.sleep5000}printlna.x(25)printlna.y(0..2).each(a.z)printlna.q
Output:
5055Wed Feb 23 21:33:40 CST 2011Wed Feb 23 21:33:45 CST 2011Wed Feb 23 21:33:50 CST 2011null
Unicon implements object environments with records and supporting procedures for creation, initialization, and methods. To modify an instance you must create a new record then copy, amend, and replace it. Strictly speaking we can't guarantee the replace as there is no way to modify the existing object and we are creating a new instance with extensions. The proceduresconstructor andfieldnames are needed. This example doesn't do error checking. Hereextend takes three arguments, the class instance, a list of new variable names as strings, and an optional list of new values to be assigned. The new instance is returned and the object is replaced by assignment. The caveat here is that if the object was assigned to anything else we will now have two objects floating around with possible side effects. As written this isn't safe from name collisions - aside from local declarations the use of a fixed constructor name uses the global name space. There is a final caveat that needs to be observed - if future implementations of objects change then this could easily stop working.
Note: Unicon can be translated via a command line switch into icon which allows for classes to be shared with Icon code (assuming no other incompatibilities exist).
linkximageproceduremain()c1:=foo(1,2)# instance of foowrite("c1:\n",ximage(c1))c1:=extend(c1,["c","d"],[8,9])# 2 new fieldswrite("new c1:\n",ximage(c1))c1:=extend(c1,["e"],[7])# 1 morewrite("newest c1:\n",ximage(c1))endclassfoo(a,b)# dummy classendprocedureextend(instance,newvars,newvals)#: extend a class instanceeveryput(f:=[],fieldnames(instance))# copy existing fieldnamesc:=["tempconstructor"]|||f# new constructoreveryput(c,!newvars)# append new varst:=constructor!c# new constructorx:=t()# new instanceeveryx[v:=!f]:=instance[v]# same as old instancex.__s:=x# new selfif\newvalstheneveryi:=1tomin(*newvars,*newvals)dox[newvars[i]]:=newvals[i]# add new vars = valuesreturnxend
ximage.icn provides ximage to dump variable contents
Output:
c1:R_foo__state_1 := foo__state() R_foo__state_1.a := 1 R_foo__state_1.b := 2new c1:R_tempconstructor_1 := tempconstructor() R_tempconstructor_1.__s := R_tempconstructor_1 R_tempconstructor_1.__m := R_foo__methods_1 := foo__methods() R_tempconstructor_1.a := 1 R_tempconstructor_1.b := 2 R_tempconstructor_1.c := 8 R_tempconstructor_1.d := 9newest c1:R_tempconstructor_1 := tempconstructor() R_tempconstructor_1.__s := R_tempconstructor_1 R_tempconstructor_1.__m := R_foo__methods_1 := foo__methods() R_tempconstructor_1.a := 1 R_tempconstructor_1.b := 2 R_tempconstructor_1.c := 8 R_tempconstructor_1.d := 9 R_tempconstructor_1.e := 7
All "instance variables" (or slots in Io nomenclature) are created at runtime.
Empty:=Objectclonee:=Emptycloneefoo:=1
If you assign a value to the name which references a property of a class instance, that name within that instance gets that value.
C=:<'exampleclass'NB. this will be our class nameV__C=:0NB. ensure the class existsOBJ1=:conew'exampleclass'NB. create an instance of our classOBJ2=:conew'exampleclass'NB. create another instanceV__OBJ1,V__OBJ2NB. both of our instances exist0W__OBJ1NB. instance does not have a W|valueerrorW__OBJ1=:0NB. here, we add a W to this instanceW__OBJ1NB. this instance now has a W0W__OBJ2NB. our other instance does not|valueerror
Adding variables to an object at runtime is not possible in Java which is a statically typed language requiring the names of all class variables to be known at compile time.
However, we can make it appear as though variables are being added at runtime by using a Map or similar structure.
importjava.util.HashMap;importjava.util.Map;importjava.util.Scanner;publicfinalclassAddVariableToClassInstanceAtRuntime{publicstaticvoidmain(String[]args){Demonstrationdemo=newDemonstration();System.out.println("Create two variables at runtime: ");Scannerscanner=newScanner(System.in);for(inti=1;i<=2;i++){System.out.println(" Variable number "+i+":");System.out.print(" Enter name: ");Stringname=scanner.nextLine();System.out.print(" Enter value: ");Stringvalue=scanner.nextLine();demo.runtimeVariables.put(name,value);System.out.println();}scanner.close();System.out.println("Two new runtime variables appear to have been created.");for(Map.Entry<String,Object>entry:demo.runtimeVariables.entrySet()){System.out.println("Variable "+entry.getKey()+" = "+entry.getValue());}}}finalclassDemonstration{Map<String,Object>runtimeVariables=newHashMap<String,Object>();}
Create two variables at runtime: Variable number 1: Enter name: Test Enter value: 42 Variable number 2: Enter name: Item Enter value: 3.14Two new runtime variables appear to have been created.Variable Item = 3.14Variable Test = 42
This kind of thing is fundamental to JavaScript, as it's a prototype-based language rather than a class-based one.
e={}// generic objecte.foo=1e["bar"]=2// name specified at runtime
jq's "+" operator can be used to add a key/value pair (or to add multiple key-value pairs) to an existing object at runtime, butjq is a functional programming language, and objects themselves cannot be altered. Thus it may be helpful to introduce a variable, since the value of a variable can in effect be updated. For example:
{"a":1} as $a | ($a + {"b":2}) as $a | $aThus the value of $a has undergone the desired transition, that is, its final value is {"a":1, "b":2}.A Javascript-like syntax can also be used to add (or update) a key, for example:
$a|.c = 3# or equivalently:$a|.["c"] = 3
Julia does not allow direct modification of the data member variables of a type, though class methods (just Julia functions) can be added without difficulty. For special situations, such as when parsing an input file, where new data type names may be appropriate, this can be accommodated using a Dict as one of the class variables. For example, consider the below JSON input data for a program processing phone numbers, where the type of phone numbers for the person is unknown until run-time:
{"phoneNumbers":[{"type":"home","number":"212555-1234"},{"type":"office","number":"646555-4567"},{"type":"mobile","number":"123456-7890"}]}Add the data into a class member that is declared as a Dict structure:
mutablestructContactname::Stringphonenumber::Dict{Any,Any}endperson=Contact("Jane Doe",Dict())person.phonenumber["home"]="212 555-1234"
Adding variables to an object at runtime is not possible in Kotlin (at least in the version targeting the JVM) which is a statically typed language and therefore the names of all class variables need to be known at compile time.
However, as in the case of Groovy, we canmake it appear as though variables are being added at runtime by using a Map or similar structure. For example:
// version 1.1.2classSomeClass{valruntimeVariables=mutableMapOf<String,Any>()}funmain(args:Array<String>){valsc=SomeClass()println("Create two variables at runtime: ")for(iin1..2){println(" Variable #$i:")print(" Enter name : ")valname=readLine()!!print(" Enter value : ")valvalue=readLine()!!sc.runtimeVariables.put(name,value)println()}while(true){print("Which variable do you want to inspect ? ")valname=readLine()!!valvalue=sc.runtimeVariables[name]if(value==null){println("There is no variable of that name, try again")}else{println("Its value is '${sc.runtimeVariables[name]}'")return}}}
Sample input/output:
Create two variables at runtime: Variable #1: Enter name : a Enter value : rosetta Variable #2: Enter name : b Enter value : 64Which variable do you want to inspect ? aIts value is 'rosetta'
Latitude is prototype-oriented, so adding slots at runtime is very straightforward and common.
myObject := Object clone.;; Name known at compile-time.myObject foo := "bar".;; Name known at runtime.myObject slot 'foo = "bar".
obj = script("MyClass").new()put obj.foo-- "FOO"-- add new property 'bar'obj.setProp(#bar, "BAR")put obj.bar-- "BAR"Logtalk supports "hot patching" (aka "monkey patching") using a category, which is a first-class entity and a fine grained units of code reuse that can be (virtally) imported by any number of objects but also used for "complementing" an existing object, adding new functionality or patching existing functionality. Complementing cateogries can be enable or disabled globally or on a per object basis.
The following example uses a prototype for simplicity.
% we start by defining an empty object:-object(foo).% ensure that complementing categories are allowed:-set_logtalk_flag(complements, allow).:-end_object.% define a complementing category, adding a new predicate:-category(bar,complements(foo)).:-public(bar/1).bar(1).bar(2).bar(3).:-end_category.
We can test our example by compiling and loading the two entities above and then querying the object:
|?- foo::bar(X).X=1;X=2;X=3true
BUKKITs (the all-purpose container type) can be added to at any point during execution, and theSRS operator permits the creation of identifiers from strings. This program and its output demonstrate both by prompting the user for a name and a value, modifying the object accordingly, and then printing the value of the new variable.
HAI 1.3I HAS A object ITZ A BUKKITI HAS A name, I HAS A valueIM IN YR interface VISIBLE "R U WANTIN 2 (A)DD A VAR OR (P)RINT 1? "! I HAS A option, GIMMEH option option, WTF? OMG "A" VISIBLE "NAME: "!, GIMMEH name VISIBLE "VALUE: "!, GIMMEH value object HAS A SRS name ITZ value, GTFO OMG "P" VISIBLE "NAME: "!, GIMMEH name VISIBLE object'Z SRS name OICIM OUTTA YR interfaceKTHXBYE
Example run:
R U WANTIN 2 (A)DD A VAR OR (P)RINT 1? ANAME: fooVALUE: 42R U WANTIN 2 (A)DD A VAR OR (P)RINT 1? PNAME: foo42
empty={}empty.foo=1
Adding y member to an object with a x member which made by a class alfa (a global function). We can make m as a copy of this new group (which is in a container, in a(3)). We can make a pointer to A(3) and handle the new member.
Module checkit { class alfa { x=5 } \\ a class is a global function which return a group Dim a(5)=alfa() Print a(3).x=5 For a(3) { group anyname { y=10} \\ merge anyname to this (a(3)) this=anyname } Print a(3).y=10 Print Valid(a(2).y)=false \\ make a copy of a(3) to m m=a(3) m.y*=2 Print m.y=20, a(3).y=10 \\ make a pointer to a(3) in n n->a(3) Print n=>y=10 n=>y+=20 Print a(3).y=30 \\ now n points to a(2) n->a(2) Print Valid(n=>y)=false ' y not exist in a(2) Print n is a(2) ' true \\ we don't have types for groups Print valid(@n as m)=false ' n haven't all members of m Print valid(@m as n)=true ' m have all members of n}checkitMathematica doesn't rally have classes, so it doesn't have class variables. However, many rules can be applied to a single tag, so it has some aspects similar to a class. With that definition, adding a class variable is similar to adding a rule:
f[a]=1;f[b]=2;f[a]=3;?f
Output:
Global`ff[a]=3f[b]=2
Here, the two 'variables' can be seen under the single heading 'f'. And of course all of this is done at runtime.
If the name of the variable to add is known at compile time, then this is just standard class construction:
empty={}empty.foo=1
If the name of the variable to add is itself in a variable, then instead of dot syntax, use normal indexing:
empty={}varName="foo"empty[varName]=1
Either method results in a perfectly ordinary class or instance (there is no technical distinction between these in MiniScript), which can be used as usual by subsequent code.
To emulate adding a variable to a class instance, Morfa uses user-defined operators` and<-.
import morfa.base;template <T>public struct Dynamic{ var data: Dict<text, T>;}// convenience to create new Dynamic instancestemplate <T>public property dynamic(): Dynamic<T>{ return Dynamic<T>(new Dict<text,T>());}// introduce replacement operator for . - a quoting ` operatorpublic operator ` { kind = infix, precedence = max, associativity = left, quoting = right }template <T>public func `(d: Dynamic<T>, name: text): DynamicElementAccess<T>{ return DynamicElementAccess<T>(d, name);}// to allow implicit cast from the wrapped instance of T (on access)template <T>public func convert(dea: DynamicElementAccess<T>): T{ return dea.holder.data[dea.name];}// cannot overload assignment - introduce special assignment operatorpublic operator <- { kind = infix, precedence = assign }template <T>public func <-(access: DynamicElementAccess<T>, newEl: T): void{ access.holder.data[access.name] = newEl;}func main(): void{ var test = dynamic<int>; test`a <- 10; test`b <- 20; test`a <- 30; println(test`a, test`b);}// private helper structuretemplate <T>struct DynamicElementAccess{ var holder: Dynamic<T>; var name: text; import morfa.io.format.Formatter; public func format(formatt: text, formatter: Formatter): text { return getFormatFunction(holder.data[name])(formatt, formatter); }}30 20
importjson{.experimental:"dotOperators".}template`.=`(js:JsonNode,field:untyped,value:untyped) = js[astToStr(field)] = %valuetemplate`.`(js:JsonNode,field:untyped): JsonNode= js[astToStr(field)]varobj= newJObject()obj.foo= "bar"echo(obj.foo)obj.key= 3echo(obj.key)
"bar"3
Objective-C doesn't have the ability to add a variable to an instance at runtime. However, since Mac OS X 10.6 and iOS 3.1, it has something that can accomplish a very similar purpose, called "associative references" or "associated objects", which allow you to attach additional objects onto an object without changing its class.
You can put associative references on any object. You can put multiple ones on the same object. They are indexed by a pointer key (typically the address of some dummy variable). You use the functionsobjc_getAssociatedObject() andobjc_setAssociatedObject to get and set them, respectively.
#import <Foundation/Foundation.h>#import <objc/runtime.h>staticvoid*fooKey=&fooKey;// one way to define a unique key is a pointer variable that points to itselfintmain(intargc,constchar*argv[]){@autoreleasepool{ide=[[NSObjectalloc]init];// setobjc_setAssociatedObject(e,fooKey,@1,OBJC_ASSOCIATION_RETAIN);// getNSNumber*associatedObject=objc_getAssociatedObject(e,fooKey);NSLog(@"associatedObject: %@",associatedObject);}return0;}
You can also use a selector as the key, since two selectors with the same content are guaranteed to be equal:
#import <Foundation/Foundation.h>#import <objc/runtime.h>intmain(intargc,constchar*argv[]){@autoreleasepool{ide=[[NSObjectalloc]init];// setobjc_setAssociatedObject(e,@selector(foo),@1,OBJC_ASSOCIATION_RETAIN);// getNSNumber*associatedObject=objc_getAssociatedObject(e,@selector(foo));NSLog(@"associatedObject: %@",associatedObject);}return0;}
Octave is dynamically typed, and can have fields added in two methods:
% Given struct "test"test.b=1;test=setfield(test,"c",3);
ooRexx does not directly expose instance variables to callers. Encapsulated access to instance variables is done via accesser methods for assignment and retrieval. In general, it is not possible to just dynamically add support, but it is possible to construct a class that allows for this to happen.
This example traps unknown method calls, then sets or retrieves the values in an encapsulated directory object.
d=.dynamicvar~newd~foo=123sayd~food2=.dynamicvar2~newd~bar="Fred"sayd~bar--aclassthatallowsdynamicvariables.Sincethisisamixin,this--capabilitycanbeaddedtoanyclassusingmultipleinheritance::classdynamicvarMIXINCLASSobject::methodinitexposevariablesvariables=.directory~new--theUNKNOWNmethodisinvokedforallunknownmessages.Weturnthis--intoeitheranassignmentoraretrievalforthedesireditem::methodunknownexposevariablesusestrictargmessageName,arguments--assignmentmessagesendwith'=',whichtellsuswhattodoifmessageName~right(1)=='='thendovariables[messageName~left(messageName~length-1)]=arguments[1]endelsedoreturnvariables[messageName]end--thisclassisnotadirectsubclassofdynamicvar,butmixesinthe--functionalityusingmultipleinheritance::classdynamicvar2inheritdynamicvar::methodinit--mixininitmethodsarenotautomaticallyinvoked,sowemust--explicitlyinvokethisself~init:.dynamicvar
An object may be written that can dynamically add methods to itself. This example is similar to the above example, but the UNKNOWN method attaches a getter/setter pair of methods for the name triggering the UNKNOWN call. On all subsequent calls, the attribute methods will get called.
d=.dynamicvar~newd~foo=123sayd~foo--aclassthatallowsdynamicvariables.Sincethisisamixin,this--capabilitycanbeaddedtoanyclassusingmultipleinheritance::classdynamicvarMIXINCLASSobject::methodinitexposevariablesvariables=.directory~new--theunknownmethodwillgetinvokedanytimeanunknownmethodis--used.ThisUNKNOWNmethodwilladdattributemethodsforthegiven--namethatwillbeavailableonallsubsequentuses.::methodunknownexposevariablesusestrictargmessageName,arguments--checkforanassignmentorfetch,andgettheproper--methodnameifmessageName~right(1)=='='thendovariableName=messageName~left(messageName~length-1)endelsedovariableName=messageNameend--defineapairofmethodstosetandretrievetheinstancevariable.Theseare--createdattheobjectscopeself~setMethod(variableName,'expose'variableName'; return'variableName)self~setMethod(variableName'=','expose'variableName'; use strict arg value;'variableName'= value')--reinvoketheoriginalmessage.Thiswillnowgotothedynamicallyaddedmethodsforwardto(self)message(messageName)arguments(arguments)
Simple implementation for making runtime members - supports integer, float and string types.
'=================class fleximembers'=================indexbase 0bstring buf, *varlsys dp,enmethod addVar(string name,dat) sys le=len buf if dp+16>le then buf+=nuls 0x100 : le+=0x100 : end if @varl=?buf varl[en]=name varl[en+1]=dat dp+=2*sizeof sys en+=2 'next slotend methodmethod find(string name) as sys sys i for i=0 to <en step 2 if name=varl[i] then return i+1 nextend methodmethod vars(string name) as string sys f=find(name) if f then return varl[f]end methodmethod VarF(string name) as double return vars(name)end methodmethod VarI(string name) as sys return vars(name)end methodmethod vars(string name,dat) bstring varl at buf sys f=find(name) if f then varl[f]=datend methodmethod delete() sys i sys v at buf for i=0 to <en freememory v[i] next freememory ?buf ? buf=0 : en=0 : dp=0end methodend class'TESTfleximembers aa.addVar "p",5a.addVar "q",4.5a.addVar "r","123456"print a.Vars("q")+a.vars("q") 'result 4.54.5print a.Varf("q")+a.varf("q") 'result 9a.deleteIt is not possible to add variables to instances in Oz. Every object has exactly one class and this association cannot be changed after object creation. Classes themselves are immutable.
However, classes are also first-class values and are created at runtime. Many of the tasks that are solved with "monkeypatching" in other languages, can be solved by dynamically creating classes in Oz.
declare %% Creates a new class derived from BaseClass %% with an added feature (==public immutable attribute) fun {AddFeature BaseClass FeatureName FeatureValue} class DerivedClass from BaseClass feat %% "FeatureName" is escaped, otherwise a new variable %% refering to a private feature would be created !FeatureName:FeatureValue end in DerivedClass end class Base feat bar:1 meth init skip end end Derived = {AddFeature Base foo 2} Instance = {New Derived init}in {Show Instance.bar} %% inherited feature {Show Instance.foo} %% feature of "synthesized" classTo add a variable number of features and attributes, you can useClass.new.
Works with FPC (tested with version 3.2.2).
This could be done by playing around with the custom variants a bit.
Let's put the following code in a separate unit:
unitMyObjDef;{$mode objfpc}{$h+}{$interfaces com}interfacefunctionMyObjCreate:Variant;implementationusesVariants,Generics.Collections;varMyObjType:TInvokeableVariantType;typeIMyObj=interfaceprocedureSetVar(constaName:string;constaValue:Variant);functionGetVar(constaName:string):Variant;end;TMyObj=class(TInterfacedObject,IMyObj)strictprivateFMap:specializeTDictionary<string,Variant>;publicconstructorCreate;destructorDestroy;override;procedureSetVar(constaName:string;constaValue:Variant);functionGetVar(constaName:string):Variant;end;TMyData=packedrecordVType:TVarType;Dummy1:array[0..5]ofByte;Dummy2:Pointer;FObj:IMyObj;end;TMyObjType=class(TInvokeableVariantType)procedureClear(varV:TVarData);override;procedureCopy(varaDst:TVarData;constaSrc:TVarData;constIndir:Boolean);override;functionGetProperty(varaDst:TVarData;constaData:TVarData;constaName:string):Boolean;override;functionSetProperty(varV:TVarData;constaName:string;constaData:TVarData):Boolean;override;end;functionMyObjCreate:Variant;beginVarClear(Result);TMyData(Result).VType:=MyObjType.VarType;TMyData(Result).FObj:=TMyObj.Create;end;constructorTMyObj.Create;beginFMap:=specializeTDictionary<string,Variant>.Create;end;destructorTMyObj.Destroy;beginFMap.Free;inherited;end;procedureTMyObj.SetVar(constaName:string;constaValue:Variant);beginFMap.AddOrSetValue(LowerCase(aName),aValue);end;functionTMyObj.GetVar(constaName:string):Variant;beginifnotFMap.TryGetValue(LowerCase(aName),Result)thenResult:=Null;end;procedureTMyObjType.Clear(varV:TVarData);beginTMyData(V).FObj:=nil;V.VType:=varEmpty;end;procedureTMyObjType.Copy(varaDst:TVarData;constaSrc:TVarData;constIndir:Boolean);beginVarClear(Variant(aDst));TMyData(aDst):=TMyData(aSrc);end;functionTMyObjType.GetProperty(varaDst:TVarData;constaData:TVarData;constaName:string):Boolean;beginResult:=True;Variant(aDst):=TMyData(aData).FObj.GetVar(aName);end;functionTMyObjType.SetProperty(varV:TVarData;constaName:string;constaData:TVarData):Boolean;beginResult:=True;TMyData(V).FObj.SetVar(aName,Variant(aData));end;initializationMyObjType:=TMyObjType.Create;finalizationMyObjType.Free;end.
And main program:
programtest;{$mode objfpc}{$h+}usesMyObjDef;varMyObj:Variant;beginMyObj:=MyObjCreate;MyObj.Answer:=42;MyObj.Foo:='Bar';MyObj.When:=TDateTime(34121);WriteLn(MyObj.Answer);WriteLn(MyObj.Foo);//check if variable names are case-insensitive, as it should be in PascalWriteLn(MyObj.wHen);end.
42Bar01.06.1993
Adding variables to an object at runtime is not possible in PascalABC.NET because it is a statically typed language requiring the names of all class variables to be known at compile time.
However, we can make it appear as though variables are being added at runtime by using a Dictionary.
typeMyClass=classprivatedict:=newDictionary<string,object>;publicprocedureAdd(name:string;value:object):=dict[name]:=value;propertyItems[name:string]:objectreaddict[name];default;end;beginvarobj:=newMyClass;obj.Add('Name','PascalABC.NET');obj.Add('Age',16);Println(obj['Name'],obj['Age']);varobj1:=newMyClass;obj1.Add('X',2.3);obj1.Add('Y',3.8);Println(obj1['X'],obj1['Y']);end.
PascalABC.NET 162.3 3.8
packageEmpty;# Constructor. Object is hash.subnew{returnbless{},shift;}packagemain;# Object.my$o=Empty->new;# Set runtime variable (key => value).$o->{'foo'}=1;
Dynamic classes are really just wrappers to per-instance dictionaries, not entirely unlike Go/Kotlin/etc, but with slightly nicer syntax.
Attempting to fetch/store "jelly" on a non-dynamic class would trigger a fatal error, unless said field had been explictly defined.
withoutjavascript_semantics-- no classclasswobblydynamic-- (pre-define a few fields/methods if you like)endclasswobblywobble=new()?wobble.jelly-- 0--?wobble["jelly"] -- for dynamic names use []wobble.jelly="green"?wobble.jelly-- "green"
classE{};$e=newE();$e->foo=1;$e->{"foo"}=1;// using a runtime name$x="foo";$e->$x=1;// using a runtime name in a variable
In general, all instance variables in PicoLisp are dynamically created atruntime.
: (setq MyObject (new '(+MyClass))) # Create some object-> $385605941: (put MyObject 'newvar '(some value)) # Set variable-> (some value): (show MyObject) # Show the object$385605941 (+MyClass) newvar (some value)-> $385605941
Pike does not allow adding variables to existing objects, but we can design a class that allows us to add variables.
classCSV{mappingvariables=([]);mixed`->(stringname){returnvariables[name];}void`->=(stringname,mixedvalue){variables[name]=value;}array_indices(){returnindices(variables);}}objectcsv=CSV();csv->greeting="hello world";csv->count=1;csv->lang="Pike";indices(csv);Result:({/* 3 elements */"lang","count","greeting"})
classmyclassendlocalmc=newmyclass()mc.somevar=21print(mc.somevar*2)
42
In Pop11 instance variables (slots) are specified at class creationtime and there is no way to add new slot to an instance after itsclass was created. However, for most practical purposes one canobtain desired effect in different way. Namely, except for a fewlow-level routines slots in Pop11 are accessed via getter andsetter methods. Getters and setters are like ordinary methods,but are automatically defined and "know" low level details ofslot access. Pop11 allows dynamic definition of methods, andone can add new methods which work as "getter" and "setter" butdo not store data directly in instance. One possibility isto have one instance variable which contains a hastable (thisis essentially what Perl solution is doing). Another possibility(used below) is to create na external hashtable. Adding new slotstypically make sense if slot name is only known at runtine, sowe create method definition (as a list) at runtime and compileit 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 valuePowerShell allows extending arbitrary object instances at runtime with theAdd-Member cmdlet. The following example adds a propertyTitle to an integer:
$x=42`|Add-Member-PassThru`NoteProperty`Title`"The answer to the question about life, the universe and everything"
Now that property can be accessed:
PS> $x.TitleThe answer to the question about life, the universe and everything
or reflected:
PS> $x | Get-Member TypeName: System.Int32Name MemberType Definition---- ---------- ----------CompareTo Method int CompareTo(System.Object value), ...Equals Method bool Equals(System.Object obj), bool...GetHashCode Method int GetHashCode()GetType Method type GetType()GetTypeCode Method System.TypeCode GetTypeCode()ToString Method string ToString(), string ToString(s...Title NoteProperty System.String Title=The answer to th...
While trying to access the same property in another instance will fail:
PS> $y = 42PS> $y.Title
(which simply causes no output).
classempty(object):passe=empty()
If the variable (attribute) name is known at "compile" time (hard-coded):
e.foo=1
If the variable name is determined at runtime:
setattr(e,name,value)
Note: Somewhat counter-intuitively one cannot simply usee = object(); e.foo = 1 because the Python baseobject (the ultimate ancestor to all new-style classes) will raise attribute exceptions. However, any normal derivatives ofobject can be "monkey patched" at will.
Because functions are first class objects in Python one can not only add variables to instances. One can add or replace functionality to an instance. Doing so is tricky if one wishes to refer back to other instance attributes since there's no "magic" binding back to "self." One trick is to dynamically define the function to be added, nested within the function that applies the patch like so:
classempty(object):def__init__(this):this.foo="whatever"defpatch_empty(obj):deffn(self=obj):printself.fooobj.print_output=fne=empty()patch_empty(e)e.print_output()# >>> whatever
(formerly Perl 6)
You can add variables/methods to a class at runtime by composing in a role. The role only affects that instance, though it is inheritable. An object created from an existing object will inherit any roles composed in with values set to those at the time the role was created. If you want to keep changed values in the new object, clone it instead.
classBar { }# an empty classmy$object =Bar.new;# new instancerolea_role {# role to add a variable: foo,has$.fooisrw =2;# with an initial value of 2}$objectdoesa_role;# compose in the rolesay$object.foo;# prints: 2$object.foo =5;# change the variablesay$object.foo;# prints: 5my$ohno =Bar.new;# new Bar object#say $ohno.foo; # runtime error, base Bar class doesn't have the variable foomy$this =$object.new;# instantiate a new Bar derived from $objectsay$this.foo;# prints: 2 - original role valuemy$that =$object.clone;# instantiate a new Bar derived from $object copying any variablessay$that.foo;# 5 - value from the cloned object
That's what's going on underneath, but often people just mix in an anonymous role directly using thebut operator. Here we'll mix an attribute into a normal integer.
my$lue =42butrole {has$.answer ="Life, the Universe, and Everything" }say$lue;# 42say$lue.answer;# Life, the Universe, and Everything
On the other hand, mixins are frowned upon when it is possible to compose roles directly into classes (as with Smalltalk traits), so that you get method collision detection at compile time. If you want to change a class at run time, you can also use monkey patching:
useMONKEY-TYPING;augmentclassInt {methodanswer {"Life, the Universe, and Everything" }}say42.answer;# Life, the Universe, and Everything
This practice, though allowed, is considered to be Evil Action at a Distance.
Rebol[Title:"Add Variables to Class at Runtime"URL:http://rosettacode.org/wiki/Adding_variables_to_a_class_instance_at_runtime]; As I understand it, a REBOL object can only ever have whatever; properties it was born with. However, this is somewhat offset by the; fact that every instance can serve as a prototype for a new object; that also has the new parameter you want to add.; Here I create an empty instance of the base object (x), then add the; new instance variable while creating a new object prototyped from; x. I assign the new object to x, et voila', a dynamically added; variable.x:makeobject![]; Empty object.x:makex[newvar:"forty-two"; New property.]print"Empty object modifed with 'newvar' property:"probex; A slightly more interesting example:starfighter:makeobject![model:"unknown"pilot:none]x-wing:makestarfighter[model:"Incom T-65 X-wing"]squadron:reduce[makex-wing[pilot:"Luke Skywalker"]makex-wing[pilot:"Wedge Antilles"]makestarfighter[model:"Slayn & Korpil B-wing"pilot:"General Salm"]]; Adding new property here.squadron/1:makesquadron/1[deathstar-removal-expert:yes]print[crlf"Fighter squadron:"]foreachpilotsquadron[probepilot]
Empty object modifed with 'newvar' property:make object! [ newvar: "forty-two"]Fighter squadron:make object! [ model: "Incom T-65 X-wing" pilot: "Luke Skywalker" deathstar-removal-expert: #(true)]make object! [ model: "Incom T-65 X-wing" pilot: "Wedge Antilles"]make object! [ model: "Slayn & Korpil B-wing" pilot: "General Salm"]
It is possible to extend object in Rebol 3!
>>o:object[a:1]puto'b2o==makeobject![a:1b:2]
person:makeobject![name:noneage:none]people:reduce[makeperson[name:"fred"age:20]makeperson[name:"paul"age:21]]people/1:makepeople/1[skill:"fishing"]foreachpersonpeople[printreduce[person/age"year old"person/name"is good at"any[selectperson'skill"nothing"]]]
We can add an attribute (or a group of attributes) to the object state using addattribute() function
o1 = new pointaddattribute(o1,"x")addattribute(o1,"y")addattribute(o1,"z")see o1 {x=10 y=20 z=30}class pointx: 10.000000y: 20.000000z: 30.000000
classEmptyende=Empty.newclass<<eattr_accessor:fooende.foo=1putse.foo# output: "1"f=Empty.newf.foo=1# raises NoMethodError
"class << e" uses thesingleton class of "e", which is an automatic subclass of Empty that has only this single instance. Therefore we added the "foo" accessor only to "e", not to other instances of Empty.Another way of adding a method to a singleton is:
yes_no="Yes"defyes_no.notreplace(self=="Yes"?"No":"Yes")end#Demo:pyes_no.not# => "No"pyes_no.not# => "Yes"p"aaa".not# => undefined method `not' for "aaa":String (NoMethodError)
Since version 2.10 Scala supports dynamic types. Dynamic types have to implement traitDynamic and implement methodsselectDynamic andupdateDynamic.
importlanguage.dynamicsimportscala.collection.mutable.HashMapclassAextendsDynamic{privatevalmap=newHashMap[String,Any]defselectDynamic(name:String):Any={returnmap(name)}defupdateDynamic(name:String)(value:Any)={map(name)=value}}
Sample output in the REPL:
scala>vala=newAa:A=A@7b20f29dscala>a.foo=42a.foo:Any=42scala>a.foores10:Any=42
classEmpty{};vare=Empty();# create a new class instancee{:foo}=42;# add variable 'foo'saye{:foo};# print the value of 'foo'
Slate objects are prototypes:
define: #Empty -> Cloneable clone.define: #e -> Empty clone.e addSlotNamed: #foo valued: 1.
the following addSlot function creates an anonymus class with the additional slot, defines accessor methods and clones a new instance from the given object which becomes the old one.This preserves object identity. (by the way: if we remember and reuse these temp classes, we get the core of Google's fast JavaScript interpreter implementation ;-)
(should work with all Smalltalks, though)
|addSlot p|addSlot:= [:obj:slotName||anonCls newObj|anonCls:=objclasssubclass:(objclassname,'+')asSymbolinstanceVariableNames:slotNameclassVariableNames:''poolDictionaries:''category:nilinEnvironment:nil.anonClscompile:('%1 ^ %1'bindWith:slotName).anonClscompile:('%1:v %1 := v'bindWith:slotName).newObj:=anonClscloneFrom:obj.objbecome:newObj. ].
create a 2D Point object, add a z slot, change and retrieve the z-value, finally inspect it (and see the slots).
p:=Pointx:10y:20.addSlotvalue:pvalue:'z'.pz:30.pz.pz:40.pinspect
The above used a block to perform this operation in privacy. In a real world application, the addSlot code would be added as an extension to the Object class, as in.
!ObjectmethodsFor:'adding slots'!addSlot:slotName|anonCls newObj|anonCls:=selfclasssubclass:(selfclassname,'+')asSymbolinstanceVariableNames:slotNameclassVariableNames:''poolDictionaries:''category:nilinEnvironment:nil.anonClscompile:('%1 ^ %1'bindWith:slotName).anonClscompile:('%1:v %1 := v'bindWith:slotName).newObj:=anonClscloneFrom:self.selfbecome:newObj.
then, again create a 2D Point object, add a z slot, change and retrieve the z-value, finally inspect it (and see the slots).
p:=Pointx:10y:20.paddSlot:'z'."instance specific added slot"pz:30.pz.pz:40.pinspect."shows 3 slots""Point class is unaffected:"p2:=Pointx:20y:30.p2z:40.->error;Pointdoesnotimplementz:p2inspect."shows 2 slots""but we can create another instance of the enhanced point (even though its an anon class)"p3:=pclassnew.p3x:1y:2.p3z:4.p3inspect."shows 3 slots"
We can use the same associated object mechanism as in Objective-C:
importFoundationletfooKey=UnsafeMutablePointer<UInt8>.alloc(1)classMyClass{}lete=MyClass()// setobjc_setAssociatedObject(e,fooKey,1,.OBJC_ASSOCIATION_RETAIN)// getifletassociatedObject=objc_getAssociatedObject(e,fooKey){print("associated object:\(associatedObject)")}else{print("no associated object")}
or
The code below uses the fact that each object is implemented as a namespace, to add atime variable to an instance ofsummation:
%packagerequireTclOO%oo::classcreatesummation{constructor{}{variablev0}methodaddx{variablevincrv$x}methodvalue{{varv}}{variable$varreturn[set$var]}destructor{variablevputs"Ended with value $v"}}::summation%sets[summationnew]%# Do the monkey patch!%set[infoobjectnamespace$s]::timenownow%# Prove it's really part of the object...%$svaluetimenow%
An alternative approach is to expose the (normally hidden)varname method on the object so that you can get a handle for an arbitrary variable in the object.
%oo::classcreatesummation{constructor{}{variablev0}methodaddx{variablevincrv$x}methodvalue{{varv}}{variable$varreturn[set$var]}destructor{variablevputs"Ended with value $v"}}::summation%sets[summationnew]%sets2[summationnew]%oo::objdefine$sexportvarname%# Do the monkey patch...%set[$svarnametime]"now"%$svaluetimenow%# Show that it is only in one object...%$s2valuetimecan'tread"time":nosuchvariable
Although Wren is dynamically typed, it is not possible to add new variables (or fields as we prefer to call them) to a class at run time. We therefore follow the example of some of the other languages here and use a map field instead.
import"io"forStdin,StdoutclassBirds{constructnew(userFields){_userFields=userFields}userFields{_userFields}}varuserFields={}System.print("Enter three fields to add to the Birds class:")for(iin0..2){System.write("\n name : ")Stdout.flush()varname=Stdin.readLine()System.write(" value: ")Stdout.flush()varvalue=Num.fromString(Stdin.readLine())userFields[name]=value}varbirds=Birds.new(userFields)System.print("\nYour fields are:\n")for(kvinbirds.userFields){System.print("%(kv.key) =%(kv.value)")}
Sample session:
Enter three fields to add to the Birds class: name : finch value: 6 name : magpie value: 7 name : wren value: 8Your fields are: finch = 6 magpie = 7 wren = 8
set Object = {}Object.Hello = "World";log(Object.Hello);World
Once created, class structure is fixed. However, using reflection, you can blow apart the class structure, add what ever and recompile the class (at run time). The REPL does this to store intermediate user results (defined classes, functions, variables, etc). It is ugly, slow and left as an exercise to the reader who cares.