Outlook on GP technology-tao shengyi and his Second Life

Source: Internet
Author: User
Tags gety rounds traits

Outlook on GP technology-tao shengyi and his Second Life

By Mo Huafeng

For a long time, we have always used GP (generic programming) as an auxiliary technology to simplify the code structure and improve development efficiency. To some extent, this concept is correct. So far, GP technology is only a compilation technology. It can only play a role during the compilation period. Once the software completes compilation, it becomes executable code and loses the chance to use GP. For most applications, the running polymorphism is particularly important. The existing GP cannot play a role at this level, so that my "GP fan" has to pretend to be "using OOP to build a system and using GP to optimize code ".
However, a discussion at toplanguage group not long ago prompted us to notice the concept of runtime GP. From this, we can see the hope of making GP runtime possible, which makes it possible for GP to exert its great power at runtime, it further brings higher efficiency and more flexible structure for software design and development.
In this new series of articles, I tried to use runtime GP to implement something simple, but a typical case is to detect the capabilities and limitations of runtime GP, at the same time, we can further explore and demonstrate the features of this technology.

Runtime polymorphism

Today's applications focus on interactive forms of operation, requiring the software to respond under user input. In this case, to optimize the overall structure of the software, a large number of component technologies are used to make the software a "deployable" system. The structure form of the interface-implement separation achieves this goal well. Polymorphism plays a key role in this process. Specifically, the "dynamic polymorphism" (also known as "subtyping polymorphism") represented by OOP builds an assembled system that can be controlled at runtime. GP, as a "static polymorphism", uses generalized type systems to greatly simplify the construction of such systems and eliminate repetitive work. There is also a little-known form of polymorphism, called the runtime unbound polymorphism by David vanw.orde and niclai M. josutis of C ++ template. The original "dynamic polymorphism", that is, the OOP polymorphism, is refined to the runtime bound polymorphism; the "static polymorphism", that is, the template, is called the static unbound polymorphism.
However, this title is easy to misunderstand, mainly in the word unbound. Here, unbound and bound refer to whether a symbol is of the same specific type when writing code. From this point of view, since the GP code is designed for generics rather than specific types, GP is unbound. Because the existing GP is a compilation technology, it is static. The dynamic polymorphism of OOP must be written for a specific type, so it is bound. However, because the real type can be determined during the running of the dynamic polymorphism, It is a runtime. As for runtime unbound, in the past, it only appeared in dynamic languages, such as Smalltalk, Python, and Ruby. It is named "duck-typing" polymorphism. For more comprehensive classification and introduction of polymorphism, see here.
Runtime unbound implemented through the Dynamic Language Mechanism has performance and type security problems. However, when we promote the concept technology in GP to runtime, we will find that rungime unbound can have the same efficiency and type security as OOP dynamic polymorphism, but it has more flexibility and richer features. In this regard, I have written an article that roughly describes a way to implement the runtime concept (in the appendix of this article, I also provided the improvement of the runtime Concept implementation ).

Runtime Concept

The implementation of runtime concept is not very complex. Basically, we can use the "virtual table" Technology in OOP and it can be simpler. The really complicated part is how to express this runtime GP at the language level without interfering with the existing static GP and other language features. Here, I will first establish a basic solution, then test it through some cases, and make adjustments after finding the problem.
Considering that the runtime concept itself is also a concept, there is no problem with the definition of static concept:
Concept myconcept <t> {
T & copy (T & LHS, t const & RHs );
Void T: Fun ();
...
}
For specific concept definition and usage rules, refer to the concept proposal of C ++ 0x or this article.
On the other hand, we can use concept_map to bind a concept type to this concept:
Concept_map myconcept <mytype> {}
For more information, see the preceding document.
With concept, we can use them to constrain a template:
Template <myconcept T> void myfun (T const & Val); // function Template

Template <myconcept>
Class X // class template

{
...
};
So far, the runtime concept is the same as the static concept. They are actually used for separation. For static concept applications, we use a specific type in instantiation (special) a template:
X <typea> x1; // instantiate a class template
Mytype obj1;
Myfun (obj1); // compiler derives the type instantiation function template of the obj1 object
Myfun (obj1); // explicit instantiation of function templates
Now, we will allow an unconventional approach to make runtime concept possible:Allows you to use concept to instantiate a template or define an object..
X <myconcept> X2;
Myconcept * obj2 = new myconcept;
Myfun (obj2); // here, the compiler will generate the runtime version of myfun
The meaning here is very clear: For X2, accept any object of the myconcept type. Obj2 is a"Dynamic Object"(Here, the kind of objects introduced by runtime concept that do not know the real type, but which meet a certain concept is called" Dynamic Object ". Objects with clearly known types become "static objects"), which meets the requirements of myconcept. As for the actual type, just follow myconcept.
This situation is very similar to the traditional dynamic multi-state interface. However, they have fundamental differences. Interface is a specific type, and the type must be inherited to implement this interface. Concept is not a type, but a "generic"-an abstract (or set) with certain features, and does not need to be immediately bound to the interface when the type is created. Concept_map can be bound to concept at any time. Therefore, runtime concept is actuallyNon-Intrusive Interface. It is more flexible and convenient than interface, which is an intrusive interface.
In this way, we can obtain a GP system that can work at runtime.
On this basis, some useful features are introduced to facilitate subsequent cases:

  1. The assosiate type of a concept is considered as a concept. A concept pointer/reference (Concept_ID*/Concept_ Id& Indicates a conformityConcept_idDynamic Object, its actual type is unknown), are considered as concept. After a class template is instantiated using concept, it is logically a concept.
  2. Create a dynamic object. To create a dynamic object on the stack, you can use the Syntax:Concept_id<Type_id>Obj_idHereConcept_idConcept name,Type_idIs a specific type name,Obj_idIs the object name. In this way, a matchingConcept_idThe actual type of dynamic object isType_id.
    To create a dynamic object on the stack, you can use the Syntax:Concept_id*Obj_id= NewConcept_idThis can be seen as "concept pointer/reference ".
  3. Concept deduction (compilation period ). For expressionsConcept_id Obj_id=Exp, WhereExpIs an expression, if the expressionExpIs a specific typeObj_idRepresents a static object, its type isExp. If the expressionExpIs concept, thenObj_idIs a dynamic object, its type isExpConcept.
    So how to determineExpIs it a specific type or a concept? You can use this rule: ifExpAs long as one is a dynamic object (the type is concept ),ExpIs concept. Otherwise, if all objects involved are static objects (the type is specific ),ExpThe corresponding type. The same rule applies to concept * or concept &.
  4. Concept conversion. It is similar to performing conversions on the inheritance structure of classes. Refined concept can be implicitly converted to base concept. In turn, it must be performed explicitly and executed using the concept_cast operator. The concept must also be converted using concept_cast.
  5. Concept-based overload can also be executed at runtime to implement generalized dynamic-dispatch operations.


Next, we will start the first case.

Case: upgraded tanks

Let's assume that we are playing a game with the theme of opening a tank. By the practice of the game, you can earn points by eliminating enemies and upgrade them to a certain number of points. For the sake of simplicity, we only consider upgrading the main gun. The first-level main gun is 90mm, and the second-level main gun is upgraded to 120. There are two types of main guns: one can only launch a armor-piercing bullet, and the other can only launch a high-altitude bullet. As a result, there are two types of tanks: The ones that can play the armor and the ones that can play the role well.
In order to make the code easy to develop and maintain, we consider using a modular method: To develop a tank framework, and then implement different types of tanks and upgrades by replacing different main guns:
// Some basic concept definitions
// Battery warhead Concept
Concept warheads {
Double explode (targettype TT); // return the killing rate when the shell explodes. Different warhead have different rates of killing different types of targets.
}
// Shell concept, of course we are concerned with the warhead, so we use warheads to define an associate type
Concept rounds {
Type Warheads Wh;
...
}
// Main gun concept
Concept cannons {
Type rounds R;
Void T: load (R & R); // load the shell. After loading, the shell will be stored in the barrel and cannot be loaded.
R: Wh T: Fire (); // The attacker returns the warhead. The gun is empty after the launch, and you can load
}
// Type and template Definition
// Tank Template
Template <cannons C>
Class Tank
{
...
Public:
Void load (typename C: R & R ){
M_cannon.load (R );
}
Typename C: R: Wh fire (){
Return m_cannon.fire ();
}
PRIVATE:
C m_cannon;
};
// Main gun Template
Template <rounds RD>
Class cannon
{
Public:
Typedef rd r;
Void load (R & R ){...}
Typename R: Wh fire (){...}
}
Concept_map cannons <cannon>;
// Shell Template
Template
Class round
{
Public:
Typedef W Wh;
Static const int caliber = W: caliber;
W shoot (){...}
...
};
Concept_map rounds <round>;
// A warhead template that separates code frameworks of different types of warhead through traits so that the types can be assembled"
Concept wh_traits {
Return T: exploed (INT, targettype, double, material );
}
Template <wh_traits WHT, int C>
Class Warhead
{
Public:
Const static int caliber = C;
Double explode (targettype TT ){
Return WHT: exploed (C, TT ,...);
}
...
};
Concept_map warheads <warhead>;
// Warhead traits
Struct ke_whtraits
{
Static double exploed (INT caliber, targettype TT, double weight, material m ){...}
};
Concept_map wh_traits <ke_whtraits>;
Struct he_whtraits
{
Static double exploed (INT caliber, targettype TT, double weight, material m ){...}
};
Concept_map wh_traits // Define various types of Warhead
Typedef warhead <ke_whtraits, 90> wh_ke_90;
Typedef warhead <ke_whtraits, 120> wh_ke_120;
Typedef warhead

 

Typedef warhead

 

// Define various shells
Typedef round <wh_ke_90> round_ke_90;
Typedef round <wh_ke_120> round_ke_120;

 

Typedef round <wh_he_90> round_he_90;

 

Typedef round <wh_he_120> round_he_120;

 

// Define various main guns
Typedef cannon <round_ke_90> cannon_ke_90;
Typedef cannon <round_ke_120> cannon_ke_120;

 

Typedef cannon <round_he_90> cannon_he_90;

 

Typedef cannon <round_he_120> cannon_he_120;

 

// Define various tanks
Typedef tank <cannon_ke_90> tank_ke_90;
Typedef tank <cannon_ke_120> tank_ke_120;

 

Typedef tank <cannon_he_90> tank_he_90;

 

Typedef tank <cannon_he_120> tank_he_120;

 

As a result, when we start the game, we can create tank Objects Based on the player level and shoot:
// First-level players drive a tank that launches 90mm high-explosive shells
Tank_he_90 mytank;
Round_he_90 R1;
Mytank. Load (R1 );
Mytank. Fire ();
// Second-level players drive and launch 120mm armor-piercing tanks
Tank_ke_120 yourtank;
Round_ke_120 R2;
Yourtank. Load (R2 );
Yourtank. Fire ();
// In this case, the shells are dangerous and the shells do not match.
Mytank. Load (R2); // Error
So far, these codes only show static GP. Concept only serves as a type parameter constraint. However, in these code, we can clearly see that after using the parameterized type feature of GP, it is easy to componentialize. For a group of types that have similar behavior and structural features, we can extract the difference from the type parameters of the template and separate them into so-called "traits" or "policy ". In addition, different products are formed through a combination of traits and policies. In some complex cases, traits and policy can be further componentized through traits or policy.
Next, we should naturally expand the use of runtime GP.
A player can be upgraded. To make the upgrade more flexible, we will naturally use the composite mode. Now, we can implement the composite mode of GP with the support of runtime concept:
// Tank concept
Concept tanks {
Typename round;
Void T: load (round &);
Round: Wh T: Fire ();
}
Concept_map tanks <tank_ke_90>;
Concept_map tanks <tank_ke_120>;

 

Concept_map tanks <tank_he_90>;

 

Concept_map tanks <tank_he_120>;

 

// Tank constructor Template
Template <tanks T>
T * createtank (whtype type, int level) {// whtype is an enumeration indicating the shell Type
Switch (level)
{
Case 1:
If (type = wht_ke)
ReturnNew tank_ke_90;
Else
ReturnNew tank_he_90;
Case 2:
If (type = wht_ke)
ReturnNew tank_ke_120;
Else
ReturnNew tank_he_120;
Default:
Throw error ("No such tank .");
}
}
// Players
Class player
{
Public:
Void Update (){
M_ptank = createtank (m_tanktype, ++ m_level);
}
...
PRIVATE:
Int m_level;
Whtype m_tanktype;
Tanks * m_ptank;
};
In class player, a concept is used instead of a type to define an object. According to the concept deduction rules mentioned above, m_ptank points to a dynamic object or static object, depending on whether the expression type assigned to it is concept or a specific type. In the update () function, we can see that m_ptank uses the expressionCreatetank (m_tanktype, ++ m_level)Assign values. The return type of this function determines the m_ptank type. Createtank () is a template. The return type is the template parameter and the type conforms to the concept tanks. The key lies in the codeReturn new tanks <...>Statement. As mentioned above, this form creates a dynamic object conforming to the tanks using the type in <...>. Therefore, createtank () returns a dynamic object. M_ptank also points to a dynamic object. During running, players can be upgraded when certain conditions are met. The Update () member function will recreate the corresponding tank object based on the player level and assign the value to m_ptank.
Here, we actually use the tanks concept description to act as a public interface of the type. Its features are very similar to those of the abstract base classes with dynamic polymorphism. However, the difference is that, as shown in the code, concept can be defined at any time as an interface and bound to the same type. It does not need to be defined before the type definition, just like the abstract base class. Therefore, this non-intrusive interface is more flexible and free than the abstract base class.
However, things are not finished yet. After further deepening the tank case, we will also find that runtime GP has more interesting and important features.
The tank was fired to attack the target. Damage to the target is directly related to the player's survival and score. Therefore, we must calculate the damage to the target after shooting. So I wrote such a group of functions:
Double lethalityevaluate (target & T, double hitrate, wh_ke_90 & Wh ){...}
Double lethalityevaluate (target & T, double hitrate, wh_he_90 & Wh ){...}
Double lethalityevaluate (target & T, double hitrate, wh_ke_120 & Wh ){...}
Double lethalityevaluate (target & T, double hitrate, wh_he_120 & Wh ){...}
Target is the target, hitrate is the hit rate, which is calculated based on the location and shooting parameters of the tank and target (if you want to be more realistic, can add wind direction, wind power, temperature, humidity, altitude and other factors); Wh is the Shell launched. The function returns the killing rate. In this way, we can evaluate it after shooting:
Double L = lethalityevaluate (house, HR, mytank. Fire ());
Now, the game requires some extensions and an upgrade to allow tanks to be upgraded to Level 3. In the third pole, the caliber of the main gun has risen to the beginning, but the function can be upgraded to launch a armor-piercing bullet and a high-altitude missile. In this way, we need a "dual-purpose" main gun class. However, you do not need to directly create such a class. You only need to use the "dual-purpose" ammunition to instantiate the cannon template:
Concept warheads120: Warheads {// 120mm gun warhead Concept
Double lethalityevaluate (target & T, double hitrate, T & Wh );
}
Concept rounds120: rounds {}
Concept_map warheads120 <wh_ke_120>; // The 120mm armor plate belongs to warheads120.
Concept_map warheads120 <wh_he_120>; // The height of 120mm belongs to warheads120.
Concept_map rounds120 <round_ke_120>; // all shells containing the warheads120 warhead belong to the rounds120

Concept_map rounds120 <round_he_120>;
Typedef canon <round120> cannon120; // use rounds120 to instantiate the cannon template.
After a bunch of dazzling concept and concept_map, The rounds120 is called "dual-purpose" ammunition. As a concept, it is combined with two types of map, which is actually the two types of interfaces. When the cannon template is instantiated using rounds120, a "dual-purpose main gun" is created (using the main gun of rounds120 ammunition ). If you use this cannon120 to instantiate the tank template, you can get the Level 3 tank (the tank with the cannon120 main gun is level 3 ):
Typedef tank <cannon120> tankl3;
Therefore, we can use different 120mm ammunition to fill the main gun and launch corresponding shells:
Tankl3 tank_l3;
Round_ke_120 ke_round; // create a armor piercing bullet
Round_he_120 he_round; // create a high-end worker
Tank_l3.load (ke_round); // refresh armor piercing
Tank_l3.fire (); // launch a armor piercing bullet
Tank_l3.load (he_round); // The loading height.
Tank_l3.fire (); // launch a high definition
Now, we can shift our attention from eliminating the enemy to the parameter type of tankl3: load () and the return type of tankl3: Fire. In level 1 and level 2 tanks (such as tank_ke_90), the parameter type of the load () member is round_ke_90 and other specific types, and the return type of fire () is also the same. However, tankl3 is instantiated using cannon120, while cannon120 is instantiated using the rounds120 concept. According to the tank <> template definition, the parameter type of the load () member is actually an associate type of the template parameter. This associate type is actually rounds120. This means that the signature after the load () instance is: void load (rounds120 & R) (currently, concept is allowed as the type parameter ). Any data that meets the rounds120 type can be passed as real parameters to the load () member. Similarly, the return type of the fire () member is from the Associate type on round120, which is also a concept. Therefore, the signature after fire () is instantiated is: warheads120 fire ().
The following is a member of the fire. If the returned type is a concept, it returns a dynamic object. At runtime, it may return the instance of wh_ke_120 or the instance of wh_he_120, depending on the shell type Loaded by the load () function at runtime. When we use the lethalityevaluate () function to evaluate the killing situation of the shell, there will be a subtle situation:
Double X = lethalityevaluate (histank, HR, tank_l3.fire ());
At this time, which lethalityevaluate () Should the compiler choose ()? Since tank_l3.fire () returns a dynamic object, the specific type is unknown during compilation. In fact, in an authentic static language, such calls cannot be compiled at all. Of course, the compiler can obtain the type information through runtime reflect, and then match the correct function in the reload of lethalityevaluate. However, this dynamic language approach may cause performance problems, so it is disdain for static languages.
However, in the context of runtime concept, we can make this call static, legal, and efficient. Note that I added an associate function in the definition of concept warhead120: Double lethalityevaluate (target & T, double hitrate, T & Wh );. Runtime concept faithfully constructs the associate function in the concept definition into a function finger table (I call it ctable. (For details, see the appendix of this article and the appendix of this Article ). Therefore, the pointer of the lethalityevaluate () function corresponding to the actual type of the dynamic object returned by tank_l3.fire () is honestly lying in the corresponding ctable. Therefore, we can directly obtain the pointer to the ctable from the dynamic object, find the corresponding lethalityevaluate () function pointer, and then call it directly. For example:
Tank_l3.load (ke_round );
Double X = lethalityevaluate (histank, HR, tank_l3.fire ());
Behind these codes, ke_round loads the code into the main gun through load () and then changes it into a dynamic object. The compiler attaches a pointer to the ctable to it, and then returns a reference to the dynamic object when calling fire. At this point, the compiler finds that the corresponding warhead120 concept of this dynamic object has defined an associate function named lethalityevaluate (), and the signature is consistent with the current call. Therefore, you can directly find the function pointer corresponding to lethalityevaluate () in the ctable, and call it with no scrubs. Because a concept associate function must be a function version that matches the actual type. For example, for wh_he_120, its associate function lethalityevaluate () is version: Double lethalityevaluate (target & T, double hitrate, wh_he_120 & Wh ){...}. Other versions of lethalityevaluate () cannot meet the constraints imposed by concept warhead120 on type wh_he_120.
This feature enables runtime concept as an interface, which is more flexible than dynamic multi-state abstract interfaces. Abstract interfaces only express the members of the class and the behavior of the class. They cannot express the interaction between types and other types, or between types. Concept also describes member functions and related free functions (including operators), so that the relationship between types can be directly obtained through interfaces, without indirect dynamic means such as reflect.
This plays a vital role in handling built-in types (such as int and float), preset types (types in some libraries), third-party types, and Other types that are not easy to or cannot be modified. In Oop, we cannot output an "integer", maybe short, long, or even unsinged Longlong. To this end, we either convert them into a "most basic type" (C/C ++ void *, or C # object *), and then use the rtti information for type conversion, do it again; either use the variant type packaging (as in COM), and then define a full set of computing libraries for them. However, the runtime concept not only allows the output of a dynamic object such as an integer, but also attaches related operations to a dynamic object, so that it can be processed without the help of rtti or auxiliary types, it is like processing objects of specific types.
However, here I have only examined a concept (currently called a mona1 concept) for a type, and it does not involve two or more types of concept (currently called a multi-element concept, or N-yuan concept ). In actual development, most operations involve multiple objects, such as adding two numbers and converting one type to another. At this time, we will face the multi-element concept. However, the characteristics of multiple runtime concept are unclear and further research and analysis are required.

Summary

This article shows the dynamic characteristics of GP after the introduction of runtime concept. The following points are summarized:

  1. Static GP and runtime GP are completely unified in form. They can be seen as different manifestations of the same abstract mechanism. Therefore, when constructing code entities such as types and functions, we do not need to consider whether they will be used as static or runtime in the future. The control of static and runtime depends entirely on how these code entities are used. This reduces the early design of software projects and the proactive pressure on library design.
  2. As a non-intrusive interface, runtime concept can be used flexibly. You do not need to define the interface accurately at the beginning of code writing. You can directly write the function type and build the software structure step by step. Define the interface (concept) as needed and bind it to the type at any time. Interface customization can be a step-by-step process, and the adverse effects of earlier interface design deficiencies are weakened accordingly.
  3. Runtime concept is more free than dynamic multi-state abstract interfaces. Concept can describe the features of member functions, free functions, Type features, and so on. After runtime, related free functions become part of interfaces. Furthermore, behavior characteristics of types in the overall software code environment are further reduced. It also provides more information and means for dynamic object access.
  4. Concept not only implements the type description, but also further describes the relationship between types. This greatly improved the abstract system. Especially in runtime, this broader type of description can play two roles: one is to further constrain the behavior of dynamic objects; the other is, it provides more information for external operations and usage types, and eliminates or reduces the abstract penalties for type matching. More features in this area are unclear and further research is needed.

In conclusion, we can see that runtime GP is more flexible and richer than dynamic polymorphism without compromising performance. Basically, the GP with the concept as the core provides a more basic abstract system (for more information, see the section on the role of concept in type division in this article ). In other words, the type description and constraints of Concept reflect the nature of Type abstraction, and on this basis, two specific usage methods, static and runtime, are further derived. This is also called Tao shengyi and his second life. :)

Appendix runtime Concept implementation solution 2

In the appendix of this article, I have provided a possible solution for implementing the runtime concept. Here, I have made some further improvements to this solution to make it more streamlined and efficient.
Suppose we have a concept:
Concept shape
{
Void T: load (XML );
Void T: Draw (device );
Void move (T &);
}
In addition, there is a concept representing the circle:
Concept cycles:
Copyconstructable,
Assignable,
Swappable,
Shape
{
T: T (Double, double, double );
Double T: getx ();
Double T: Gety ();
Double T: getr ();
Void T: setx (double );
Void T: sety (double );
Void T: setr (double );
}
There is a type of cycle:
Class cycle
{
Public:
Cycle (Double X, Double Y, Double R );
Cycle (cycle const & C );
Cycle & operator = (cycle const & C );
Void swap (cycle const & C );
Void load (XML init );
Void draw (device Dev );
Double getx ();
Double Gety ();
Double getr ();
Void setx (Double X );
Void sety (Double Y );
Void setr (Double R );
PRIVATE:
...
};
We map the cycle type to concept cycles:
Concept_map cycles {}
When we create an object, we will get the following structure:


The concept table (concept list) is no longer put together with objects, but the type information of the same type is put together. Ctable is also placed together. In ctable, each corresponding concept item has a pointer to the concept table (you can also point to the type information header) to locate the concept list and execute concept cast. You only need to append a pointer to the corresponding concept to the dynamic object or the reference/pointer of the dynamic object. Compared with the previous solution, the memory usage is higher.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.