Lazy typing |
|
|
Getting started:
|
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.
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 using Lazy; ... [Lazy] public class Triangle ...
Next, we declare all the data we might collect about a particular triangle (as instance variables for our [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 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
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 [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 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 using Lazy; ... Triangle triangle; triangle = (Triangle) LazyFactory.Create(typeof(Triangle));
Once the object is created using 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 exampleThe 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.
We will create a lazy 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 [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 The use of a lazy type... 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. |
|
|
Last update: July 2005 |