What is a lazy type?

In object-oriented programming, a type describes a set of objects equipped with certain operations. Classes are implementation modules that define types in a given programming language. A class sets up a single structure and behavior that are then common to all the objects directly belonging to the class. Object behavior might be redefined in subclasses but it is established beforehand for a given class.

A lazy type is a type whose structure and behavior dynamically adapt themselves to the available data at the instance level. A lazy type includes a set of attributes and a set of methods, as usual in object-oriented programming. However, lazy type behavior is described by means of alternative method implementations for each method.

Lazy type

A lazy object will only incorporate the attributes it really needs at each moment and its behavior will change accordingly. The invocation of a lazy method calls one of the alternative implementations according to the object current state. The implementation used will depend on the data each method alternative implementation needs. If a given attribute is not available and a method implementation needs that attribute value, then that alternative implementation will not be called. The alternative that fits the current object state will be automatically invoked. Since alternative method implementations share their signatures, the lazy type maintains its external interface and the programmer can transparently use lazy objects as if they were conventional objects.

 

An example in C#

Let us suppose that we want to create a lazy triangle, i.e. a class representing a triangle so that we can automatically compute its area given some of its geometrical properties, even when we do not know which piece of information will be available at each moment.

Lazy types define classes with alternative method implementations so that a suitable method implementation will automatically be invoked on an object depending on the object actual state (and we do not have to worry about the conditional logic needed to choose the proper implementation alternative!).

First of all, we will define a Triangle lazy type using the [Lazy] attribute provided by the lazy typing framework. This attribute is defined in the Lazy.dll class library and can be found in the Lazy namespace:

using Lazy;
...

  [Lazy]
  public class Triangle 
  ...

Next, we declare all the data we might collect about a particular triangle (as instance variables for our Triangle class):


  [Lazy]
  public class Triangle 
  {
    private double sideA;
    private double sideB;
    private double sideC;

    private double heightA;
    private double heightB;
    private double heightC;

    private double angleA;
    private double angleB;
    private double angleC;

    private double area;
  }

We would also define the corresponding getters and setters methods for all instance variables (or the equivalent properties in C#).

Now, depending on the data we might have about a particular triangle, we could compute its area using different algorithms, so we will define an Area method and some alternative implementations for such method:

1. If we know the actual area, the method implementation is trivial:


  [Lazy]
  public class Triangle
  ...

    public virtual double Area () {
      return area;
    }

Please, take into account that the method must be declared virtual so that the lazy typing framework will be able to replace its default implementation when needed.

2. We could also compute the area of the triangle if we knew the length of its base and its height. This leads us to three alternative implementations for the Area method (depending on which side is considered to be the base):


  [Lazy]
  public class Triangle
  ...
    
    [AlternativeImplementation("Area")]
    public double AreaFromHeightA () {
      return sideA*heightA/2;
    }

    [AlternativeImplementation("Area")]
    public double AreaFromHeightB () {
      return sideB*heightB/2;
    }

    [AlternativeImplementation("Area")]
    public double AreaFromHeightC () {
      return sideC*heightC/2;
    }

Here, we have declared three public methods using the [AlternativeImplementation] attribute provided by the lazy typing framework. These methods represent implementation alternatives to the Area method. Please, take into account that their signatures match the Area method signature.

We could have declared the above alternative implementations as protected methods. In fact, that's what we should do not to modify the public interface of the lazy class whenever a new alternative method implementation is defined.

3. If we know the length of one side of the triangle and its angles (two are enough, since the third can be computed taking into account that ºA+ºB+ºC=180º), we can devise three more ways to compute the area of a given triangle:


  [Lazy]
  public class Triangle
  ...

    [AlternativeImplementation("Area")]
    public double AreaFromSideA () {

      double h = sideA*Math.Sin(angleB)*Math.Sin(angleC)/Math.Sin(angleA);

      return sideA*h/2;
    }

    [AlternativeImplementation("Area")]
    public double AreaFromSideB () {

      double h = sideB*Math.Sin(angleA)*Math.Sin(angleC)/Math.Sin(angleB);

      return sideB*h/2;
    }

    [AlternativeImplementation("Area")]
    public double AreaFromSideC () {

      double h = sideC*Math.Sin(angleA)*Math.Sin(angleB)/Math.Sin(angleC);

      return sideC*h/2;
    }

4. If we know the length of two sides of the triangle and the angle they form, we have three more alternative implementations to compute the area of the triangle:


  [Lazy]
  public class Triangle
  ...

    [AlternativeImplementation("Area")]
    public double AreaFrom2SidesA () {

      return sideB*sideC*Math.Sin(angleA)/2;
    }

    [AlternativeImplementation("Area")]
    public double AreaFrom2SidesB () {

      return sideA*sideC*Math.Sin(angleB)/2;
    }

    [AlternativeImplementation("Area")]
    public double AreaFrom2SidesC () {

      return sideA*sideB*Math.Sin(angleC)/2;
    }

5. Finally, we can also compute the area of a triangle given the lengths of its sides:


  [Lazy]
  public class Triangle
  ...

    [AlternativeImplementation("Area")]
    public double AreaFrom3Sides () {

      double T = (sideA+sideB+sideC)/2;

      return Math.Sqrt(T*(T-sideA)*(T-sideB)*(T-sideC));
    }

In short, we have devised 11 ways to compute the area of a given triangle. Since we labelled our class with the [Lazy] attribute, we don't have to worry about which algorithm to employ in each possible situation. The lazy typing framework will take care of that. We just have to create a lazy object using the reflective lazy object factory bundled with the lazy typing framework as follows:

using Lazy;
...

    Triangle triangle; 

    triangle = (Triangle) LazyFactory.Create(typeof(Triangle));

Once the object is created using LazyFactory.Create(), the proper method implementation will automatically be invoked every time we call the Area method on the triangle object. Please, check the accompanying NUnit tests file to see how different object states transparently lead to different implementations for the Area method. The full source code of the lazy Triangle class is also available here.

The previous example shows how lazy types work but does not reflect how lazy types might be used in practice, so we introduce another example in the following section.

 

Another example

The demand of a known good can be approximated as a function of its price in the market. The higher the price, the lower the product demanded quantity. The lower the price, the higher the product demanded quantity. The figure below shows how the product and its demanded quantity are related in the market.

Price vs. Demanded quantity

We will create a lazy Demand class to lazily-model the demand of a given product in different situations:

using Lazy;
...

  [Lazy]
  public class Demand 
  ...

First, we analyze the data we might collect about the demand of a given product:

A1. We might know the product market actual size:


  [Lazy]
  public class Demand 
  ...
    // Known market
    private double price;
    private long   quantity;

A2. We might be able to estimate the demand elasticity (basically, a quantity related to the slope of the curve shown in the figure above that describes how a relative variation in price would affect the market size and vice versa):


  [Lazy]
  public class Demand 
  ...
    // Demand elasticity (deltaQ/deltaP)
    private double elasticity;

A3. We might also suppose that the demand is linear, so that the market for the product has a maximum price and the product has a cap price beyond which no one would buy our product:


  [Lazy]
  public class Demand 
  ...
    // Linear demand
    private double maxPrice;
    private long   maxMarket;
    private double slope;		// -maxPrice/maxMarket

Once we know which data items we might collect about the demand for a particular product, we must define alternative implementations for the public methods that constitute the public interface of our Demand class. Apart from the usual getters and setters (which can be implemented as C# properties), we are also interested in adding the following functionality to our Demand class:


  [Lazy]
  public class Demand 
  ...

    // Given a market size, 
    // estimate the product price needed to create such a market

    public virtual double Price (long quantity);

    // Given a target price,
    // estimate the market size for that price

    public virtual long Quantity (double price);

    // Given a market size,
    // estimate the demand elasticity

    public double Elasticity (long quantity);

We can provide different implementations for the above methods depending on the available information:

M1. To estimate the "ideal" price for a given market size:


  [Lazy]
  public class Demand 
  ...

    public virtual double Price (long quantity) {
      return 0;
    }

    [AlternativeImplementation("Price")]
    protected double PriceFromKnownMarket (long quantity) {
      return price;
    }

    [AlternativeImplementation("Price")]
    protected double PriceFromLinearDemand (long quantity) {
      return price = maxPrice + quantity*slope;
    }

    [AlternativeImplementation("Price")]
    protected double PriceFromElasticity (long quantity) {
      double deltaQ = (quantity-this.quantity)/(double)(this.quantity);
      return price*(1-deltaQ/elasticity);
    }

M2. To estimate the market size for a given price:


  [Lazy]
  public class Demand 
  ...

    public virtual long Quantity (double price) {		
      return 0;
    }

    [AlternativeImplementation("Quantity")]
    protected long QuantityFromKnownMarket (double price) {

      if (price<=this.price) {
         return this.quantity;
      } else {
         return 0;
      }
    }

    [AlternativeImplementation("Quantity")]
    protected long QuantityFromLinearDemand (double price) {

      if (price<maxPrice) {
         return (long) (maxMarket*(1-price/maxPrice));
      } else 
         return 0;
    }

    [AlternativeImplementation("Quantity")]
    protected long QuantityFromElasticity (double price) {

      double deltaP = (price-this.price)/(this.price);
      long   demand = (long) (quantity*(1-deltaP*elasticity));

      if (demand<0)
         demand = 0;

      return demand;
    }

M3. To estimate the demand elasticity:


  [Lazy]
  public class Demand 
  ...

    public double Elasticity (long quantity) {
      return elasticity;
    }

    [AlternativeImplementation("Elasticity")]
    protected double ElasticityFromLinearDemand (long quantity) {
      
      double deltaQ = (1.1*quantity)/quantity;
      double deltaP = PriceFromLinearDemand((long)(1.1*quantity))
                    / PriceFromLinearDemand(quantity);

      return deltaQ/deltaP;
    }

Finally, in order to create lazy demand objects, we resort to the lazy object factory provided with the lazy typing framework:

using Lazy;
...

    Demand demand; 

    demand = (Demand) LazyFactory.Create(typeof(Demand));

Now we have a truly flexible Demand class that we can easily incorporate into more powerful economic analysis applications.

The use of a lazy type...

  • simplifies our implementation (no longer we will have to worry about when to use each algorithm to estimate key demand parameters),
  • reduces potential maintenance headaches (the selection of the most suitable strategy is automatically performed), and
  • nicely fits a complex problem where the existence varying amounts of data, maybe from different sources, would make our design much more complex if we did not have lazy types at our disposal.

    The full source code of the lazy economic analysis case study is available here. Some NUnit test cases for this case study are also provided here.

  •