CSC/ECE 517 Fall 2012/ch1 1w5 su: Difference between revisions
(5 intermediate revisions by the same user not shown) | |||
Line 40: | Line 40: | ||
There are two techniques used by reflective systems: | There are two techniques used by reflective systems: | ||
* '''Introspection''', which is the ability of a program to examine its own state. | * '''Introspection''', which is the ability of a program to examine its own state. Observing the behavior is helpful in inspecting modules, measuring the performance and in debugging a program. (applies to languages like Ruby, Java, Smalltalk, but not to C, [http://en.wikipedia.org/wiki/Pascal_(programming_language) Pascal]) | ||
* '''Intercession''', which is the ability of a program to modify its own execution state or alter its own interpretation or meaning. | * '''Intercession''', which is the ability of a program to modify its own execution state or alter its own interpretation or meaning. This permits the change of behavior to adapt to the needs of the environment. (applies to languages like Ruby, Smalltalk, Python, [http://en.wikipedia.org/wiki/Groovy_(programming_language) Groovy]) | ||
Introspection is about discovering the component interfaces and relationships among them whereas intercession is about the possibility of re-configuring the architecture at runtime. | |||
== Introspection == | == Introspection == | ||
Line 103: | Line 105: | ||
* Fewer features are available with the packages. The developer needs to add code for additional features to be availed (and handle the exceptions accordingly). This makes the program harder to maintain and less readable. | * Fewer features are available with the packages. The developer needs to add code for additional features to be availed (and handle the exceptions accordingly). This makes the program harder to maintain and less readable. | ||
* In Java, once a class is loaded, you cannot change its structure anymore. That is, you cannot change the number and types of fields, and you cannot change the number and signatures of methods anymore. If you want to do that, you have to do that at load-time (or compile time). What you can change at runtime is the definition of methods, that's it. | * In Java, once a class is loaded, you cannot change its structure anymore. That is, you cannot change the number and types of fields, and you cannot change the number and signatures of methods anymore. If you want to do that, you have to do that at load-time (or compile time). What you can change at runtime is the definition of methods, that's it. | ||
Following are a few examples of reflection in different languages, providing a clear understanding of the distinction between reflective language feature versus reflective package. | |||
The examples have code snippet(s) with the class declaration, and also code to fetch the class information (the class name, its instance variables, its methods, method arguments and to check if an object is an instance of a class) using reflections. Output for the corresponding lines is displayed in a tabular format. | |||
== Reflective Languages Features == | == Reflective Languages Features == |
Latest revision as of 01:58, 22 September 2012
Reflective Language Features vs Reflective Packages<ref>Updated the page: CSC/ECE_517_Fall_2010/ch1_1c_JF</ref>
Reflection is the ability of a computer program to examine and modify the structure and behavior (specifically the values, meta-data, properties and functions) of an object at runtime.<ref>Reflection on Wikipedia</ref>
The concept of reflection is best understood by reaching back to the study of self-awareness in artificial intelligence: "Here I am walking down the street in the rain. Since I'm starting to get drenched, I should open my umbrella." This thought fragment reveals a self-awareness of behavior and state, one that leads to a change in that selfsame behavior and state. It would be desirable for computations to avail themselves of these reflective capabilities, examining themselves in order to make use of meta-level information in decisions about what to do next. <ref>An Introduction to Reflection-Oriented Programming - J.M. Sobel , Daniel P. Friedman - 1996</ref>
A reflective language is a programming language that has been architected to allow reflection. This means the language was constructed to 'reason' effectively and consequentially about its own inference process. Examples in which reflection is a part of the language itself : Ruby, PHP, Python
Languages that are not reflective can incorporate some of the features of reflection by using reflective packages. Examples of languages which require a package to avail reflection : Java, C#
Brief History of Reflection
Year | Development <ref>Java Reflection - Ken Cooney</ref> |
---|---|
Jun 1976 | Origin of Reflective Programming:
The idea of reflection came to Brian Smith at the Xerox Palo Alto Research Center. Smith was working on Knowledge Representation Language(KRL) and as an exercise to learn the language, he worked on a project to represent KRL in KRL. He reasoned that this would be a good way to learn the language since he had to both use and mention the language. He worked on it long enough to become intrigued with the thought of building a system that was self-descriptive. In the five years since, Smith worked on initial versions of such a language, which he called MANTIQ. |
1982 | Brian Cantwell Smith writes a doctoral dissertation at MIT introducing the notion of computational reflection. 3-LISP is the first official programming language to use reflection. <ref> Brian Cantwell Smith, "Procedural Reflection in Programming Languages", Massachusetts Institute of Technology, Feb 1982 </ref> |
1983 | Smalltalk v1.0 has 75% of the standard Reflection command language. <ref>Infoworld July 27, 1987, News Briefs. P31,32</ref> |
1984 | The Interim 3-LISP Reference Manual is published, co authored by Brian Cantwell Smith. <ref>Brian Cantwell Smith, Jim des Rivières. "Interim 3-LISP Reference Manual". Xerox PARC. 1984</ref> |
1987 | Smalltalk v2.0 adds the rest of the Reflection. [6] |
1987-89 | Perl <ref> The Timeline of Perl and its Culture </ref> |
Oct 1996 | Visual J++ and C# has reflections. Python v1.4 <ref>A. Andersen, A note on reflection in Python 1.5, Distributed Multimedia Research Group Report, MPG-98-05, Lancaster University, UK, March 1998</ref> |
Feb 1997 | Java Reflections (JDK v1.1) <ref>Joe Weber. Special Edition Using Java 1.1, Third Edition. </ref> |
Feb 1998 | Python v1.5 makes it easier for reflective programming. [9] |
Jul 2004 | PHP v5.0 <ref>Reflection in php </ref> |
Techniques used by reflective systems
There are two techniques used by reflective systems:
- Introspection, which is the ability of a program to examine its own state. Observing the behavior is helpful in inspecting modules, measuring the performance and in debugging a program. (applies to languages like Ruby, Java, Smalltalk, but not to C, Pascal)
- Intercession, which is the ability of a program to modify its own execution state or alter its own interpretation or meaning. This permits the change of behavior to adapt to the needs of the environment. (applies to languages like Ruby, Smalltalk, Python, Groovy)
Introspection is about discovering the component interfaces and relationships among them whereas intercession is about the possibility of re-configuring the architecture at runtime.
Introspection
Introspection is what allows the program to "know" information about itself. This can include an object knowing what kind of object it is, and what methods it has.
Example (Ruby):
class Sample #empty class end samp = Sample.new samp.class # outputs the name of the class samp.methods # list the methods that Sample and it's superclass,'Object', has
Intercession
Intercession includes calling methods dynamically, which means that the actual method that is called on an object is decided when the program is running, and not when the program is compiled.
Example (Ruby):
class Sample #defines two methods that print different numbers depending on which method is called def printOne puts 1 end def printTwo puts 2 end end samp = Sample.new #Create an instance of the Sample class option="printTwo" samp.send(option) #printTwo method is called option="printOne" samp.send(option) #printOne method is called
Without reflection, calls to these methods could not be changed at runtime. However, reflection allows the method calls to depend on string values that do not have to be initialized until runtime as seen in the above code.
Some of the more dynamic intercessive features that reflection gives a programming language are possible because of the dynamic nature of the language. For example, in Ruby, a method can be added to an object that has already been instanced.
samp = Sample.new def samp.anothermethod #will create a method on the instance of Sample puts "This is a new method" end
If another Sample object is created, this new object will not have access to the anothermethod method because that method was only added to a specific instance of the Sample class. In Java, methods cannot be created on existing objects (because it is a statically typed language), so this kind of reflection is not possible.
Types of Reflection
Parallel to the concepts of introspection and intercession, there are two types of reflection:
- structural reflection
- behavioral reflection
Behavioral reflection is the ability to intercept an operation such as method invocation and alter the behavior of that operation. If an operation is intercepted, the runtime system calls a method on a meta-object for notifying it of that event. The developers can define their own version of the meta-object so that the meta-object can execute the intercepted operation with customized semantics.
However, behavioral reflection only provides the ability to alter the behavior of operation; it does not provide the ability to alter data structures used by the program statically fixed at compile time. The latter, called structural reflection, allows a program to change, for example, the definition of a class, function or record on demand. <ref> http://www.dcs.bbk.ac.uk/research/techreps/2003/bbkcs-03-02.pdf</ref>
Reflective Languages Features VS Reflective Packages
In a reflective language,
- You can access the information about an object or class from the object or class directly or having intermediate objects.
- Features are in-built in the language which makes the use of reflection easier and more powerful.
In a language with a reflective package,
- Reflection functionality is provided either directly or through intermediary objects.
- The package is not automatically included in the functionality. It must be imported with the program.
- Fewer features are available with the packages. The developer needs to add code for additional features to be availed (and handle the exceptions accordingly). This makes the program harder to maintain and less readable.
- In Java, once a class is loaded, you cannot change its structure anymore. That is, you cannot change the number and types of fields, and you cannot change the number and signatures of methods anymore. If you want to do that, you have to do that at load-time (or compile time). What you can change at runtime is the definition of methods, that's it.
Following are a few examples of reflection in different languages, providing a clear understanding of the distinction between reflective language feature versus reflective package. The examples have code snippet(s) with the class declaration, and also code to fetch the class information (the class name, its instance variables, its methods, method arguments and to check if an object is an instance of a class) using reflections. Output for the corresponding lines is displayed in a tabular format.
Reflective Languages Features
Ruby
The ability to introspect is an inherent feature of Ruby. <ref>Ruby Reflections.</ref>
The Object class (ancestor of every class) provides Object#instance_of? and Object#kind_of? methods for checking the instance's class. The latter returns true when the particular instance the message was sent to is an instance of a descendant of the class in question.
class MyParent def initialize end def foo #do stuff end end class MyChild < MyParent def initialize @var = 0 end def bar(arg1,arg2) #do stuff end end
Script | Output |
---|---|
child = MyChild.new | #<MyChild:0x27d1a78 @var=0> |
child.instance_of? MyChild | true |
child.kind_of? MyParent | true |
child.class | MyChild |
child.methods | [:bar,:foo, :nil?, :==.....#other functions inherited from Object, Kernel and BasicObject] |
child.class.instance_method(:bar).parameters.map(&:last) | [:arg1,:arg2] |
PHP
PHP 5 comes with a complete reflection API that adds the ability to reverse-engineer classes, interfaces, functions, methods and extensions. There is no installation needed to use these functions; they are part of the PHP core. PHP uses ReflectionClass class to report information about a class. <ref>PHP Reflections </ref>
<?php class myparent { public function foo($bar) { // do stuff } } class mychild extends myparent { public $val; private function bar(myparent &$baz) { // do stuff } public function __construct($val) { $this->val = $val; } } $child = new mychild('hello world'); $child->foo('test'); ?>
Script | Output |
---|---|
$reflect = new ReflectionClass('mychild'); if ($reflect->isInstance($child)) { echo "child object is an instance of the class 'mychild'\n"; } echo "\nPrinting all methods in the 'mychild' class:\n"; echo "============================================\n"; foreach( $reflect->getMethods() as $reflectmethod) { echo " { $reflectmethod->getName()}()\n"; echo " ", str_repeat("-", strlen($reflectmethod->getName()) + 2), "\n"; foreach( $reflectmethod->getParameters() as $num => $param) { echo " Param $num: \$", $param->getName() , "\n"; echo " Passed by reference: ", (int)$param->isPassedByReference(), "\n"; echo " Can be null: ", (int)$param->allowsNull(), "\n"; echo " Class type: "; if ($param->getClass()) { echo $param->getClass()->getName(); } else { echo "N/A"; } echo "\n\n"; } } |
child object is an instance of the class 'mychild' Printing all methods in the 'mychild' class: ============================================ bar() ----- Param 0: $baz Passed by reference: 1 Can be null: 0 Class type: myparent __construct() ------------- Param 0: $val Passed by reference: 0 Can be null: 1 Class type: N/A foo() ----- Param 0: $bar Passed by reference: 0 Can be null: 1 Class type: N/A |
Python
Python provides a way to examine the contents of modules (and other objects) using the built-in dir() function. Also, the built-in functions isinstance and hasattr can be used to determine what an object is and what an object does respectively. <ref>Python Reflections</ref>
class Person(object): """Person class.""" def __init__(self, name, age): self.name = name self.age = age def intro(self): """Return an introduction.""" return "Hello, my name is %s and I'm %s." % (self.name, self.age)
Script | Output |
---|---|
bob = Person("Robert", 35) # Create a Person instance | <none> |
joe = Person("Joseph", 17) # Create another | <none> |
joe.sport = "football" # Assign a new attribute to one instance | <none> |
isinstance(bob, Person) | True |
hasattr(joe, 'intro') | True |
dir(Person) # Attributes of the Person class |
['__class__', '__delattr__', '__dict__', '__doc__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__repr__', '__setattr__', '__str__', '__weakref__', 'intro'] |
dir(bob) # Attributes of bob |
['__class__', '__delattr__', '__dict__', '__doc__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__repr__', '__setattr__', '__str__', '__weakref__', 'age', 'intro', 'name'] |
dir(joe) # Note that joe has an additional attribute |
['__class__', '__delattr__', '__dict__', '__doc__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__repr__', '__setattr__', '__str__', '__weakref__', 'age', 'intro', 'name', 'sport'] |
bob.intro() # Calling bob's intro method | "Hello, my name is Robert and I'm 35." |
dir(bob.intro) # Attributes of the intro method |
['__call__', '__class__', '__cmp__', '__delattr__', '__doc__', '__get__', '__getattribute__', '__hash__', '__init__', '__new__', '__reduce__', '__repr__', '__setattr__', '__str__', 'im_class', 'im_func', 'im_self'] |
Perl
Introspection can be achieved using the ref and isa functions in Perl.
package MyParent; sub new { my $class = shift; return bless {}, $class; } package MyClass; use base 'MyParent';
Script | Output |
---|---|
package main; my $par= MyParent->new(); my $child = MyChild->new(); |
|
print "This is the parent.\n" if ref $par eq 'MyParent'; | This is the parent. |
print "child is of kind MyParent.\n" if $child->isa('MyParent'); | child is of kind MyParent. |
Reflective Packages
Java
Package java.lang.reflect provides classes and interfaces for obtaining reflective information about classes and objects allowing programmatic access to information about the fields, methods and constructors of loaded classes, and the use of reflected fields, methods, and constructors to operate on their underlying counterparts, within security restrictions.
public class Base { public Base { } public void Foo(int _arg1,int _arg2) { //Foo Logic } } public class Derived extends Base { int m_value; public Derived { m_value = 0; } public void Bar(int _arg1, String _arg2) { //Bar Logic } }
Code | Output |
---|---|
import java.lang.reflect; public static void main(String[] args) { Derived obj = new Derived(); Boolean instance = Derived.class.isInstance(obj); Boolean isParent = Base.class.isAssignableFrom(obj.getClass); Method[] methods = obj.getMethods(); String className = obj.getClass().getName(); //to get parameter types for Bar //technique 1 for(int i =0 ; i<methods.length;i++) { if(methods[i].getName()=="Bar") Type[] parameterTypes = methods[i].getTypeParameters(); } //Tecnique2 Type[] parameterTypes = obj. getMethod("Bar", int.class, String.class).getTypeParameters(); } |
true true [Foo,Bar] (note : only public methods are returned) Derived [int,String] [int,String] |
C#
In C# to use reflections <ref>C# Reflections </ref>, the System.Reflection namespace must be included. It contains classes and interfaces that provide a managed view of loaded types, methods, and fields, with the ability to dynamically create and invoke types.
public class Base { public Base() { } public void Foo(int a) { int m = a; } } public class Derived : Base { public int m; public Derived() { m = 0; } public void Bar(int b, string c) { m=b; } }
Code | Output |
---|---|
using System.Reflection; Derived obj = new Derived(); Console.WriteLine("Classname - " + obj.GetType().Name); Console.WriteLine("Instance of derived - " + typeof(Derived).IsInstanceOfType(obj)); Console.WriteLine("Does inherit from base ?" + typeof(Base).IsAssignableFrom(obj.GetType())); MethodInfo[] methods = obj.GetType().GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly); for (int i = 0; i < methods.Length; i++) { MethodInfo myMethodInfo = (MethodInfo)methods[i]; Console.WriteLine("\nThe name of the method is {0}.", myMethodInfo.Name); } MethodInfo mInfo = obj.GetType().GetMethod("Bar"); Console.WriteLine("Found method: {0}", mInfo); |
Classname - Derived Instance of derived - true Does inherit from base? true The name of the method is Bar Found method: Bar(int32, System.string) |
Perl
Moose <ref>Moose Reflections</ref> provides a powerful introspection API, that lets you add attributes and methods, apply roles, and much more.
package User; use Moose; my $meta = __PACKAGE__->meta; for my $attr ( $meta->get_all_attributes ) { print $attr->name, "\n"; } for my $method ( $meta->get_all_methods ) { print $method->fully_qualified_name, "\n"; }
Advantages and Disadvantages
Advantages
- Access to Metadata and Ability to Manipulate Classes: You can access the metadata of classes and methods, get the annotations and analyze them. Writing direct code is based on specific class details (like interface, method or field names) known at compile-time. If we need to write code that depends on class details known only at runtime, then reflection comes handy.
- Creating Adaptable and Flexible Solutions: Because of its dynamic nature, reflection is useful for creating adaptable and flexible software that is often not possible with direct code. Theoretically, it is well known that reflection can help add more dynamism and flexibility to frameworks and design patterns.
- Writing Tools that Require Implementation Details: Many of the developer tools need access to internal or implementation details of the code For example, an intelligent text editor can query a class using reflection and get details about its implementation, which can be very helpful. When user types in an object name and a ‘.’ to access a member, the text editor can pop with a list box with list of accessible members (obtained using reflection) that the programmer can select from.
- Signature-Based Polymorphism: Java and C# programmers are familiar with polymorphism based on interface inheritance. Reflection provides an alternative where we can invoke methods having same signature, from different classes not have a common interface (which is required in interface based polymorphism).
Applications
- Class Browsers and Visual Development Environments: A class browser needs to be able to enumerate the members of classes. Visual development environments can benefit from making use of type information available in reflection to aid the developer in writing correct code.
- Debuggers and Test Tools: Debuggers need to be able to examine private members on classes which can be achieved with reflections. Test harnesses can make use of reflection to systematically call a discoverable set APIs defined on a class, to insure a high level of code coverage in a test suite.
Disadvantages
- Violates Object-Oriented Principles: Use of reflection essentially exposes much of implementation details – such as the methods, fields, accessibility and other metadata – of the class used. This violates the objectives of object oriented programming like abstraction and information hiding, where only the relevant higher level details of the class that the programmer needs to know are exposed to the users and low-level implementation details are hidden.
- Security: Reflection allows code to perform some operations that would be illegal in non-reflective code, such as accessing private fields and methods which could potentially be a security concern.
- Performance Overhead: Reflection requires dynamic resolution due to which certain optimizations can not be performed. Consequently, reflective operations have slower performance than their non-reflective counterparts, and should be avoided in sections of code which are called frequently in performance-sensitive applications.
- Static type check: Using reflection lets you bypass the static type checking. This mean type errors will be caught at runtime instead of compile time.
- Exception handling: The static checks done by a compiler in static code have to be done by the programmers using exception handling code when reflections are used. Providing more exception handling code increases implementation effort and also adds significantly to the code length making it less readable.
Conclusion
Reflection allows the creation of more versatile and flexible programs through both introspective and intercessive features. These features include the ability for a program to know about itself, and to act on that information. Reflections can come in handy in implementing tools or technologies that involve the use of implementation details. But like any other language feature, reflection can be misused and should be used only when necessary.
References
<references />
See Also
Even more Examples of Introspection