Java Generics usage

Bug #1209713 reported by Srinivas Hasti on 2009-03-01
6
This bug affects 1 person
Affects Status Importance Assigned to Milestone
JQuantLib
Medium
Unassigned

Bug Description

Suggested readings:
http://java.sun.com/j2se/1.5/pdf/generics-tutorial.pdf
http://gafter.blogspot.com/2006/12/super-type-tokens.html
http://gafter.blogspot.com/2007/05/limitation-of-super-type-tokens.html
http://gafter.blogspot.com/2006/11/reified-generics-for-java.html
http://www.angelikalanger.com/GenericsFAQ/FAQSections/TechnicalDetails.html

In jquantlib, we are using Super type token approach to mimic
some of the C++ templates usage quantlib. While i think its a good idea to
use the technique it can introduce a non-type safe programming
model resulting in runtime errors which can be annoying to the endusers.

Lets take an example:
 We have binomial vanilla engine follows

  public abstract class BinomialVanillaEngine<T extends BinomialTree>
     extends VanillaOptionEngine{
  }

Though class implementation is concrete we made the class abstract so that we can
force a extension and there by we can use super type token approach to determine
the BinomialTree type and create the instance of the tree class internally.

For end users to create JarrowRudd BVE, we expect them to create an extension class
with empty body as shown below
      new BinomialVanillaEngine<JarrowRudd>(){ };
Using super type token approach we create the instance of JarrowRudd under the covers to drive the
engine. Lets go over the drawbacks of the approach:

a) We are forcing end users to create a concrete implementation which isn't desirable
when engine needs to be created dynamically using class by name approach by runtimes. While you
can argue that its just two parenthesis they need to write, but we shouldn't force
tools to write code.

b) Now lets say user trying out the api the first time and he does
       new BinomialVanillaEngine(){}
   which is prefectly legal thing to do. Above is just a compile warning. When user tries to
   run the code, it will fail miserably with no useful message to a non-advanced programmer. You might
   say we can document and assume users are going to read. Come on, how many times does anyone
   reads 100% of the doc and do we think we will be able to organize docs in such a way that
   users of the api will know the tricks, good luck.

I beleive in "out of box experience". If it gives me an impresssion that its too complicated and doesn't
allow language semantics, most likely i won't use it

Now lets see how we can address above..
 a) We can create a concrete extensions for each type, for example
    public class JarrowRuddBinomialVanillaEngine extends
  BinomialVanillaEngine<JarrowRudd> { }

 This approach doesn't change any existing api, so advanced users can still continue to use original approach.

 Other approach is make BinomialVanillaEngine class concrete and add the constructor to take Tree enum type as an argument.

  b) Don't expect users of the api to specify generic type as it is perfectly legal thing not to. When generic type
     is not specified, we should have default type to go with.

Comments are welcome.

Here is another reading i strongly recommend http://www.mindview.net/WebLog/log-0061

=============
Relationships
=============
child of http://bugs.launchpad.net/bugs/jquantlib-394

Richard Gomes (frgomes) wrote :
Download full text (3.2 KiB)

My comments [more or less] 'inline':

a) We are forcing end users to create a concrete implementation which isn't desirable
when engine needs to be created dynamically using class by name approach by runtimes. While you
can argue that its just two parenthesis they need to write, but we shouldn't force
tools to write code.

-->

The reason for using...

PricingEngine engine = new BinomialVanillaEngine<Jarrod>(...) {};

... instead of ...

Tree tree = new Jarrod(...);
PricingEngine engine = new BinomialVanillaEngine(tree, ...);

... is that the 'responsibility' for creating a Tree is moved from end user code to library code. An end user only needs to tell which algorithm he/she desires and the library code will do all the magic and calculate all the correct parameters a Tree of such algorithm needs to work properly.

IMHO this is a very good design and object model.
On the other hand, the use of templates in C++ and the tricky counterpart Super Type Token in Java may lead to certain problems:

1. It's difficult to create wrappers when you have templates;

2. Super Type Tokens is an 'advanced' technique which could be easily replaced by something trivial like an enumeration, as Srini mentioned.

======

b) Now lets say user trying out the api the first time and he does

      new BinomialVanillaEngine() {}

   which is perfectly legal thing to do. Above is just a compile warning. ....

-->

I've improved error messages and documentation of BinomialVanillaEngine. You just need to hover the mouse over it to obtain the information you need, including an example of usage.

I'd love to see everything being verified at compile time too!
Oh well... this is how Generics works, for good or for bad.

=====

Now lets see how we can address above..
 a) We can create a concrete extensions for each type, for example
    public class JarrowRuddBinomialVanillaEngine extends
     BinomialVanillaEngine<JarrowRudd> { }

-->

This is not convenient for MonteCarlo module. In MC there are classes with 3 (or maybe even 4?) generic parameters. In MC, the following approaches are acceptable:

1. Use Super Type Tokens

MC mc = new MC<MyNumberGenerator,MySampleStrategy,MyDistribution>(...) {};

2. Class as parameters

MC mc = new MC(MyNumberGenerator.class, MySampleStrategy.class, MyDistribution.class, ...);

3. Enumerations

MC mc = new MC(enum1.MyNumberGenerator, enum2.MySampleStrategy, enum3.MyDistribution, ...);

Note:
In particular, I'm against the use of enumerations because they restrict how the object model can be extended.

IMHO, we should keep the Super Type Token and add constructors which accept Classes as arguments. This change would satisfy 'advanced' users (typically C++ programmers translating their existing code blindly) and Java programmers used to pass Class as arguments without hurting any previous pre-conception.

====

  b) Don't expect users of the api to specify generic type as it is perfectly legal thing not to. When generic type
     is not specified, we should have default type to go with.

-->

Yes, you are correct. It would solve the problem you mentioned in (a).
This is not what the original C++ code does, but we are not running C++ a...

Read more...

Srinivas Hasti (shasti) wrote :

>>>>>>Richard wrote:
>>>IMHO, we should keep the Super Type Token and add constructors which accept >>>Classes as arguments.

I don't like adding constructor with .class types as arguments which i think is almost same as requiring users to pass in instance you mentioned in #a. I feel api looks ugly when end user has to pass in .class types as arguments

Richard Gomes (frgomes) wrote :

When calling

PricingEngine engine = new BinomialVanillaEngine(Jarrod.class, ...);

you are not passing an instance of Jarrod, but only telling which algorithm (a class) you'd like to use. It's not developer's responsibility to create and instance of Jarrod and figure out what are the correct parameter which make sense, etc, etc... but it's his/her responsibility to choose which algorithm he/she desires. This responsibility will be always there, whatever syntax we choose.

Richard

Srinivas Hasti (shasti) wrote :

I understand, i am saying i don't like using Class<T> parameters to tell which algorithm to use as it makes extending the library difficult. Yes user doesn't have to worry about creating a new instance, but severely restricts capability to plugin custom implementations. I prefer instance over Class because if someone wants to create a custom type, they don't have to modify engine. For example, if i want to test with a CustomJarrod that extends Jarrod which needs more initialization parameters than that are used when instance
is created by library, then user need to modify engine as well which is not desired. Class type also introduces more non-type safe pitfalls.

Richard Gomes (frgomes) wrote :

The scenario you described is not offered by QuantLib at the moment, AFAIK.
I would not worry about this scenario until a user requests it.

Srinivas Hasti (shasti) wrote :

Why to wait for a user request if we know somethings make the api better and easy to extend. If we take the Class<T> args approach we are going to divert from quantlib api anyways.

Srinivas Hasti (shasti) wrote :

I am not suggesting to divert from c++ quantlib, but i am suggesting we should also think about providing flexibility and cleaner api

Richard Gomes (frgomes) wrote :
Download full text (3.1 KiB)

Let's suppose a developer is willing to extend JarrowRudd, for instance.
It may be necessary to add more arguments to a constructor or get rid of an unneeded argument. This scenario is not totally new; we already have this scenario happening at the moment: have a look at @Unused annotation and where it is used. I created this annotation to mark those arguments which appear in a method (or constructor) signature and which are not used inside that scope. It happens in some classes of BinomialTree hierarchy. What I mean to say is:

If we opt to [continue to] let the callee (BinomialVanillaEngine) decide how a BinomialTree is instantiated, the callee will have to make some assumptions and will have to have some 'insider' knowledge how a BinomialTree is instantiated. In this scenario, we have to write our extended class conforming to previous constructor signatures, probably keeping arguments we dont need and marking them as @Unused only for better documentation.

On the other hand, suppose that our extended class needs to add more arguments to constructors. Now we have a problem :( because there's no immediate way to integrate this new class to the existing ones without having to modify all constructor signatures of an entire hierarchy. This is where your suggestion is helpful, because we could solve this problem simply allowing and instance (besides .class) to be passed to BinomialVanillaEngine as argument.

What I said in the previous note I've written is that it's not needed at the moment because QuantLib does not provide it [at the moment] and I'm talking about the specific case of BinomialVanillaEngine. But I agree that it can be desirable anyway in order class hierarchies and eventually the one we are discussing now.

So, let's go ahead and suppose a very flexible thing:

1. We accept C++ style calls using the concept of Super Type Token in order to retrieve parametric generic arguments in calls like this:

// calling using Generics and using an anonymous class
PricingEngine engine = new BinomialVanillaEngine<JarrowRudd>(...) {};

2. We accept 'well behaved' Java style calls passing Classes as arguments:

PricingEngine engine = new BinomialVanillaEngine(JarrowRudd.class, ...);

3. We accept a user-suplied thing:

BinomialTree tree = new MyModifiedJarrodRudd(myCustomArg1,...,myCustomArgN);
PricingEngine engine = new BinomialVanillaEngine(tree, ...);

I dont see any problem on having 3 flavors for doing the same thing (or almost the same thing). The more flexibility the better.

What I said (and I insist!) is that BinomialVanillaEngine (in particular!) does not need case (3) *at the moment*.

IMHO, the library needs to be coherent.
So, I think we could eventually create an issue in Mantis intended to promote a complete code review, in the entire library, aiming to standardize how we provide such flexibility. For doing this, first we need to complete the translation of v0.8.1, in order to perform once and properly a code review which is complete.

I vote for opening an issue on this topic, providing the directions needed to be remembered at the time of implementation. Or maybe adding these directions to this issue.

Thanks. Very good discussi...

Read more...

To post a comment you must log in.
This report contains Public information  Edit
Everyone can see this information.

Other bug subscribers