Language Support for Generic Programming in Object-Oriented Languages: Design Challenges

. It is generally considered that object-oriented (OO) languages provide weaker support for generic programming (GP) as compared with functional languages such as Haskell or SML. There were several comparative studies which showed this. But many new object-oriented languages have appeared in recent years. Have they improved the support for generic programming? And if not, is there a reason why OO languages yield to functional ones in this respect? In the earlier comparative studies object-oriented languages were usually not treated in any special way. However, the OO features affect language facilities for GP and a style people write generic programs in such languages. In this paper we compare ten modern object-oriented languages and language extensions with respect to their support for generic programming. It has been discovered that every of these languages strictly follows one of the two approaches to constraining type parameters. So the first design challenge we consider is “which approach is better”. It turns out that most of the explored OO languages use the less powerful one. The second thing that has a big impact on the expressive power of a programming language is language support for multiple models. We discuss pros and cons of this feature and its relation to other language facilities for generic programming.


Introduction
Almost all modern programming languages provide language support for generic programming (GP) [2]. Some languages do it better than others. For example, Haskell is generally considered to be one of the best languages for generic programming [3,4], whereas mainstream object-oriented languages such as C# and Java are much less expressive and have many drawbacks. There were several studies that compared language support for generic programming in different languages [3][4][5][6]. However, these studies do not make any difference between object-oriented and functional languages. We argue that OO languages are to be treated separately, because they support the distinctive OO features that pure functional languages do not, such as inheritance, interfaces/traits, subtype polymorphism, etc. These features affect the language design and a way people write generic programs in object-oriented languages. Several new object-oriented languages have appeared in recent years, for instance, Rust, Swift, Kotlin. At the same time, several independent extensions have been developed for the mainstream OO languages [7][8][9][10]. These new languages and extensions have many differences, but all of them tend to improve the support for generic programming. There is a lack of a careful comparison of the approaches and mechanisms for generic programming in modern object-oriented languages. This study is aimed to fill the gap: it gives a survey, analysis, and comparison of the facilities for generic programming that the chosen OO languages provide. We identify the dependencies between major language features, detect incompatible ones, and point the properties that a language design should satisfy to be effective for generic programming.

Main Ideas
Ten modern object-oriented languages and language extensions have been explored in this study with respect to generic programming. We have found out that in the case of OO languages there are exactly two approaches to a design of language constructs for generic programming. We call the first one "constraints-are-types", because under this approach OO constructs such as interfaces or traits, which are usually used as types in object-oriented programs, are also used to constrain type parameters in generic programs. The second approach, "constraints-are-Not-types", restricts OO constructs to be used as types only, and provides separate language constructs for constraining type parameters. Hence the first design challenge arises: is one of this approaches better than another? Or the same expressive power can be achieved using any of them? We answer these questions in Sec. 3. It turns out that the approaches cannot be integrated together, and the second one is more expressive. The second point covered in the paper in detail (in Sec. 4) is language support for multiple models (by "model" we mean a way in which types satisfy constraints). There are several questions related to multiple models: 1. Is it desirable to have multiple models of a constraint? 7 2. How can support for multiple models be provided with the approaches discovered? 3. Why does not Haskell allow multiple models (instances of a type class)? 4. Is there a language design that reflects the support for multiple models better than the existing ones? The short answers are: 1. Yes, it is desirable. 2. It can be naturally provided with the second approach but not with the first one. 3. Because of type inference. 4. Yes, there is. In conclusion, we present a modified version of the well-known table [3,5] showing the levels of language support for the features important for generic programming. Table 1 provides information on all of the object-oriented languages considered, introduces some new features, and demonstrates the relations between the features.

Two Approaches to Constraining Type Parameters
This section provides a survey of language constructs for generic programming in several modern object-oriented programming languages as well as some language extensions. All of the languages we explored adopt one of the two approaches: 1. Interface-like constructs, which are normally used as types in object-oriented programming, are also used to constrain type parameters. By "interface-like constructs" we mean, in particular, C#/Java interfaces, Scala traits, Swift protocols, Rust traits. Fig. 1 shows a corresponding example in C#: IPrintable interface acts as the type of xs in PrintArr, whereas in the function InParens<T> it is used to constrain the type parameter T. 2. For constraining type parameters a separate language construct is provided; such construct cannot be used as a type. We will see some examples in Sec. 3-2. Sec. 3-1 analyses the languages of the first category; Sec. 3-2 is devoted to the second one. In Sec. 3-3 we compare both approaches and answer the question "Which one is better if any?".

Fig. 2. The IComparable<T> interface in C#
Drawbacks of F-bounded polymorphism. F-bounded polymorphism [11] allows "recursive" constraints (Fconstraints) on type parameters in the form T : I<T>, where T is a type parameter, I<> is a generic interface. Such kind of constraints solves the binary method problem [12]: Fig. 2 demonstrates a corresponding C# [13] example. The type parameter T in the interface IComparable<T> pretends to be a type that implements this interface. This is indeed the case for the class SortedSet<T> due to the constraint T : IComparable<T>, so the method T.CompareTo(T) is like a binary function for comparing elements of type T. But the semantics of IComparable<T> itself has nothing to do with binary methods. One could easily write some class Foo implementing IComparable<Bar>, and thus the semantics of comparing two Bars would be broken. Another shortcoming of the F-bounded polymorphism is that code with recursive constraints is rather cumbersome and difficult to understand. Yet, as we will see, the F-bounded polymorphism is not the only solution to the binary method problem. More detailed discussion on the pitfalls of the F-bounded polymorphism can be found in [9,14]. Lack of associated types [14,15]. Types that are logically related to some entity are often called associated types of the entity. For instance, types of edges and vertices are associated types of a graph. There is no specific language support for associated types in C# and Java: such types are expressed in generic code in the form of extra type parameters. Lack of constraints propagation [14,15]. Despite the fact that the definition of the class SortedSet<T> in Fig. 2 already contains a constraint on the type parameter T, in the baz<T> function defined below the constraint on T is to be placed as well.
void baz<T>(SortedSet<T> s) where T : IComparable<T> { ... } Although baz<T> takes a value of type SortedSet<T>, so it is clear from the signature of the function that T must be comparable, the code would not compile without an

Fig. 3. The C# interfaces for unification algorithm
Some of the drawbacks mentioned above have been successfully eliminated in the modern object-oriented languages. We briefly examine language facilities for generic programming in several OO languages with the "constraints-are-types" philosophy in the following subsections. But there is a problem common for all languages of this category, the problem of multi-type constraints (constraints on several types). Note that an interface (or a similar language construct) describes properties, an interface of a single type that implements/extends it. This has inevitable consequence: multi-type constraints cannot be expressed naturally. Consider a generic unification algorithm [16]: it takes a set of equations between terms (symbolic expressions), and returns the most general substitution which solves the equations. So the algorithm operates on three kinds of data: terms, equations, substitutions. A signature of the algorithm might be as follows: Subst Unify<Tm, Eqtn, Subst>(IEnumerable<Eqtn>) But a bunch of functions has to be provided to implement the algorithm: Subterms : Tm → Ienumerable<Tm>, Solve : Eqtn → Subst, SubstituteTm : Subst × Tm → Tm, SubstituteEq : Subst × Ienumerable<Eqtn> → IEnumarable<Eqtn>, and some others. All these functions are needed for unification at once, hence it would be convenient to have a single constraint that relates all the type parameters and provides the functions required.
Subst Unify<Tm, Eqtn, Subst> (IEnumerable<Eqtn>) where <single constraint> But in C#/Java the only thing one can do 2 is to define three different interfaces describing a term, equation and substitution, and then separately constrain every type parameter with a respective interface. Fig. 3 shows the interface definitions. To set up a relation between mutually dependent interfaces, three type parameters are used: Tm for terms, Eqtn for equations, and Subst for substitution. Moreover, the parameters are repeatedly constrained with the appropriate interfaces in every interface definition. These constraints are to be stated in a signature of the unification algorithm as well:

Interfaces in Ceylon and Kotlin
In contrast to C#, Ceylon [17] and Kotlin [18] interfaces support default method implementation, so Java 8 [19] interfaces do. This is a useful feature for generic programming. For instance, one can define an interface for equality that provides a default implementation for the inequality operation. Fig. 4

Fig. 5. The use of "self type" in Ceylon interfaces
In addition to default method implementations, the Ceylon language also allows a type parameter to be declared as a self type. An example is shown in Fig. 5. In the definition of the Comparable<Other> interface the declaration of Other explicitly requires Other to be a self type of the interface, i. e. a type that implements this interface. Because of this the reverseCompareTo method can be defined: both the other and this values are of type Other, with the Other implementing Comparable<Other>, so the call other.compareTo(this) is perfectly legal.

Scala Traits
Similarly to advanced interfaces in Java 8, Ceylon, and Kotlin, Scala traits [6,20] support default method implementations. They can also have abstract type members, which, in particular, can be used as associated types [21]. Just as in C#/Java/Ceylon/Kotlin, type parameters (and abstract types) in Scala can be constrained with traits and supertypes (upper bounds): the latter constraints are called subtype constraints. But, moreover, they can be constrained with subtypes (lower bounds), which are called supertype constraints. None of the languages we discussed so far support supertype constraints nor associated types. Another important Scala feature, implicits [20], will be mentioned later in Sec. 4-1 with respect to the Concept design pattern.

Rust Traits
The Rust language [22] is quite different from other object-oriented languages. There is no traditional class construct in Rust, but instead it suggests structs that store the data, and separate method implementations for structs. An example is shown in Fig. 6 3 : two impl Point blocks define method implementations for the Point struct. If a function takes the &self 4 argument (as moveOn), it is treated as a method. There can be any number of implementation blocks, yet they can be defined at any point after the struct declaration (even in a different module). This gives a huge advantage with respect to generic programming: any struct can be retroactively adapted to satisfy constraints. Constraints in Rust are expressed using traits. A trait defines which methods have to be implemented by a type similarly to Scala traits, Java 8 interfaces, and others. Traits can have default method implementations and associated types; besides that, the self type of the trait is directly available and can be used in method definitions. Fig. 7 5 demonstrates an example: the Eqtbl trait defining the equality and inequality operations. Note how support for the self type solves the binary method problem (here equal is a binary method): there is no need in extra type parameter that "pretends" to be a self type, because the self type Self is already available. Method implementations in Rust can be probably thought of similarly to .NET "extension methods". But in contrast to .NET 6 , types in Rust also can retroactively implement traits in impl blocks as shown in Fig. 7: Eqtbl is implemented by i32 and Pair<S, T>. The latter definition also demonstrates a so-called type-conditional implementation: pairs are equality comparable only if their elements are equality comparable. The constraint <S : Eqtbl... is a shorthand, it can be declared in a where section as well. There is no struct inheritance and subtype polymorphism in Rust. Nevertheless, as long as traits can be used not only as constraints but also as types, a dynamic dispatch is provided through a feature called trait objects. Suppose i32 and f64 implement the Printable trait from Fig. 7. Then the following code demonstrates creating and use of a polymorphic collection (the type of the polyVec elements is a reference type): let pr1 = 3; let pr2 = 4.5; let pr3 = -10; let polyVec: Vec<&Printable> = vec![&pr1, &pr2, &pr3]; for v in polyVec { v.print(); }

Swift Protocols
Swift is a more conventional OO language than Rust: it has classes, inheritance, and subtype polymorphism. Classes can be extended with new methods using extensions 3 Some details were omitted for simplicity. To make the code correct, one has to add #[derive(Debug,Copy,Clone)] before the Point definition. 4 The "&" symbol means that an argument is passed by reference. 5 Some details were omitted for simplicity. The following declaration is to be provided to make the code correct: #[derive(Copy, Clone)] before the definition struct Pair<S : Copy, T : Copy>. Yet the type parameters of the impl for pair must be constrained with Copy+Equatable. 6 Similarly to .NET, Kotlin supports extending classes with methods and properties, but interface implementation in extensions is not allowed. that are quite similar to Rust method implementations. Instead of interfaces and traits Swift provides protocols. They cannot be generic but support associated types and same-type constraints, default method implementations through protocol extensions, and explicit access to the self type; due to the mechanism of extensions, types can retroactively adopt protocols. Fig. 8 illustrates some examples: the Equatable protocol extended with a default implementation for notEqual (pay attention to the use of the Self type); the contains<T> generic function with a protocol constraint on the type parameter T; an extension of the type Int that enables its conformance to the Printable protocol; the Container protocol with the associated type ItemTy; the allItemsMatch generic function with the same-type constraint on types of elements of two containers, C1 and C2.

Languages with "Constraints-are-Not-Types" Philosophy
Most of the languages in this category were to some extent inspired by the design of Haskell type classes [22]. For defining constraints these languages suggest new language constructs, which are usually second-class citizens 7 . These constructs have no self types and cannot be used as types, they describe requirements on type parameters in an external way; therefore, retroactive satisfaction of constraints (retroactive modeling) is automatically provided. Besides retroactive modeling, an integral advantage of such kind of constructs is that multi-type constraints can be easily and naturally expressed using them; yet there is no semantic ambiguity which arises when the same construct, such as C # interface, is used both as a type and constraint, as in the example below: void Sort<T>(ICollection<T>) where T : IComparable<T> Here ICollection<T> and IComparable<T> are generic interfaces, but the former one is used as a type whereas the latter one is used as a constraint.
interface EQ { boolean eq(This that); 7 Second-class citizens cannot be assigned to variables, passed as arguments, returned from functions.

JavaGI Generalized Interfaces
JavaGI [7] generalized interfaces represent a kind of confluence of both "constraintsare-types" and "constraints-are-not-types" philosophies. Interfaces such as PrettyPrintable defined below are called single-parameter interfaces. They describe interfaces of a single type and can be used both as types and constraints.
interface PrettyPrintable { String prettyPrint(); } Such interfaces have explicit access to the self type named This; an example is shown in Fig. 9, where the self type is used in the interface EQ. There is no direct support for default method implementations in JavaGI, but abstract implementation definitions can be used for this purpose 8 . For example, the notEq method of EQ ( Fig. 9) is implemented in such a way. Generalized interfaces can be implemented retroactively in implementation blocks. They do not support associated types but can be generic; moreover, implementations can be generic as well, and the support for type-conditional interface implementation is provided: Besides single-parameter interfaces, there are multi-headed generalized interfaces that adopt several features from Haskell type classes [24] and describe interfaces of several types. There is no self type in a multi-headed interface; therefore, it cannot be used as a type, it is designed to be used as a constraint only. An example of multiheaded interface is shown in Fig. 9: the UNIFY interface contains all the functions required by the unification algorithm considered earlier; the requirements on three 8 The design of JavaGI we discuss here goes back to 2011 when default method implementations were not supported in Java. With Java 8 this task could probably be solved in a more elegant way.
types (term, equation, substitution) are defined at once in a single interface. Note how succinct is this definition as compared with the one in Fig. 3.

Language G and C++ concepts
Concept as an explicit language construct for defining constraints on type parameters was initially introduced in 2003 [25]. Several designs have been developed since that time [26][27][28]; in the large, the expressive power of concepts is rather close the Haskell type classes [4]. Concepts were designed to solve the problems of unconstrained C++ templates [14,29]; they were expected to be included in C++0x standard, but this did not happen. A new version of concepts, Concepts Lite (C++1z) [30], is under way now. The language G declared as "a language for generic programming" [8] also provides concepts that are very similar to the C++0x concepts. G is a subset of C++ extended with several constructs for generic programming. For "C++ concepts" we use the G syntax in this paper. Similarly to a type class, a concept defines a set of requirements on one or more type parameters. It can contain function signatures that may be accompanied with default implementations, associated types, nested concept-requirements on associated types, and same-type constraints. A concept can refine one or more concepts, it means that the refining concept includes all the requirements from the refined concepts. Refinement is very similar to multiple interface inheritance in C# or protocol inheritance in Swift. Due to the concept refinement, a so-called concept-based overloading is supported: one can define several versions of an algorithm/class that have different constraints, and then at compile time the most specialized version is chosen for the given instance. The C++ advance algorithm for iterators is a classic example of concept-based overloading application. It is said that a type (or a set of types) satisfies a concept if an appropriate model of the concept is defined for this type (types). Model definitions are independent from type definitions, so the modeling relation is established retroactively; models can be generic and type-conditional. Fig. 10 illustrates some examples: the InputIterator<Iter> concept with the associated type of elements value; the Monoid<T> concept and its model for the type int; the accumulate<Iter> generic function with two constraints, on the type of the iterator and on the associated type of this iterator. Note how identity_elt is called in accumulate: in contrast to the languages from the previous section, identity_elt is available in the body of accumulate at the top-level; this may lead to some inconvenience even if the autocomplete feature is supported in IDE.

C# with concepts
In the C# cpt project [9] (C# with concepts) concept mechanism integrates with subtyping: type parameters and associated types can be constrained with supertypes (as in basic C#) and also with subtypes (as in Scala). In contrast to all of the languages we discussed earlier, C# cpt allows multiple models of a concept in the same scope. . This property is called "constraints-compatibility" in [9], but we will refer to it as "models-consistency". One more interesting thing about C# cpt : concept-requirements can be named. In the Contains<T> function (Fig. 11) the name cEq is given to the requirement on T; this name is used later in the body of Contains<T> to access the Equal function of the concept. It is also worth mention that the interface IEnumerable<T> is used as a type along with the concept CEquatable[T] being used as a constraint; thus, the role of interfaces is not ambiguous any more, interfaces and concepts are independently used for different purposes.

Constraints in Genus
Like G concepts and Haskell type classes, constraints in Genus [10] (an extension for Java) are used as constraints only. Fig. 12

Which Philosophy Is Better If Any?
It is time to find out which approach is better. Taking into consideration what we explored in Sec. 3-1 and Sec. 3-2, we draw a conclusion that there are only two language features important for generic programming that cannot be incorporated in a language together: 1. the use of a construct both as a type and constraint; 2. natural support for multi-type constraints. Languages with "constraints-are-types" philosophy support the first feature but not the second, languages with "constraints-are-Not-types" philosophy vice versa 10 . Can we determine one feature that is more important? It was shown in the study [31] that in practice interfaces that are used as constraints (such as IComparable<T> in C# or Comparable<X> in Java) are almost never used as types: authors had checked about 14 millions lines of Java code and found only one such example, which could be even rewritten and eliminated. According to [31], the same observation also holds for the code in Ceylon. It is hard to imagine any useful "constraint-and-type" example besides the IPrintable interface from Fig. 1. In those rare cases when this could happen, it is possible to provide a lightweight language mechanism for automatic generation of one construct from another. For example, single-parameter Genus constraints with some restrictions could be translated to Java interfaces, with the other direction being easier. At the same time, multi-type constraints, which can be so naturally expressed under the "constraints-are-Not-types" approach, have rather awkward and cumbersome representation in the "constraints-are-types" approach as we have seen in Sec. 3-1. Language support for multiple models is also a problem in the latter approach: it is considered in detail in the next section. All other language facilities we discussed could be supported under any approach. Therefore, we claim that with respect to generic programming the "constraints-are-Not-types" approach is preferable. An additional benefit is that it eliminates the ambiguity in semantics of the interface-like constructs currently used for different purposes in OO languages.

Single Model versus Multiple Models
For simplicity, in this part of the paper we call "constraint" any language construct that is used to describe constraints, while a way in which types satisfy the constraints we call "model". We have seen in the previous section that most of the languages allow having only one, unique model of a constraint for the given set of types; only C# cpt [9] and Genus [10] support multiple models 11 . And indeed this makes sense for the languages with "constraints-are-types" philosophy, because it is not clear what to do with types that could implement interfaces (or any other similar constructs) in several ways. But how does this affect generic programming?
It turns out that sometimes it is desirable to have multiple models of a constraint for the same set of types. The example of string sets with case-sensitive and caseinsensitive equality comparisons we saw earlier is only one of such examples; another one is the use of different orderings on numbers, yet different graph implementations, and so on. Thus, in respect of generic programming, the absence of multiple models is rather a problem than a benefit. Without extending the language the problem of multiple models can be solved in two ways, and both of them have serious drawbacks.
1. Using the Adapter pattern. If one wants the type Foo to implement IComparable<Foo> in a different way, an adapter of Foo, the Foo1 that implements IComparable<Foo1> can be created. This adapter then can be used instead of Foo whenever the Foo1-style comparison is required. An obvious shortcoming of this approach is the need to repeatedly wrap and unwrap Foo values; in addition, a code becomes cumbersome. 2. Using the Concept design pattern [20], which is considered in Sec. 4-1.
As we have discovered in Sec. 3-3, languages with the "constraints-are-types" philosophy are in the large less expressive than the ones with the "constraints-are-Not-types" philosophy. But may languages such as C# cpt and Genus, which are in the "constraints-are-Not-types" category and support multiple models at the language level, be considered as the best languages for generic programming? Or we can imagine a language with a better design? We discuss this question in Sec. 4-3. And one more question: if language support for multiple models is a good idea, then why does not Haskell [24] allow multiple instances of a type class? This issue is considered in Sec. 4-2.

Fig. 13. The use of the Concept design pattern in C#
The Concept design pattern is suitable for programming languages with the "constraints-are-types" philosophy. It eliminates two problems: 1. Firts, it enables retroactive modeling of constraints, which is not supported in languages such as C#, Java, Ceylon, Kotlin, or Scala. 2. Second, it allows defining multiple models of a constraint for the same set of types.
The idea of the Concept pattern is as follows: instead of constraining type parameters, generic functions and classes take extra arguments that provide a required functionality -"concepts". Fig. 13 shows an example: in the case of the Concept pattern the F-constraint T : IComparable<T> is replaced with an extra argument of the type IComparer<T>. The IComparer<T> interface represents a concept of comparing: it describes interface of an object that can compare values of type T. As long as one can define several classes implementing the same interface, different "models" of the IComparer<T> "concept" can be passed into Sort<T> and SortedSet<T>. This pattern is widely used in generic libraries of mainstream object-oriented languages such as C# and Java; it is also used in Scala. Due to implicits [6,20], the use of the Concept pattern in Scala is a bit easier: in most cases an appropriate "model" can be found by a compiler implicitly, so there is no need to explicitly pass it at a call site 12 . Nevertheless, the pattern has two substantial drawbacks. First of all, it brings run-time overhead, because every object of a generic class with constraints has at least one extra field for the "concept", while constrained generic functions take at least one extra argument. The second drawback, which we call modelsinconsistency, is less obvious but may lead to very subtle errors. Suppose we have s1 of type HashSet<String> and s2 of the same type, provided that s1 uses case-sensitive equality comparison, s2the case-insensitive one. Thus, s1 and s2 use different, inconsistent models of comparison. Now consider the following function: Unexpectedly, the result of GetUnion(s1, s2) could differ from the result of GetUnion(s2, s1). Despite the fact that s1 and s2 have the same type, they use different comparers, so the result depends on which comparer was chosen to build the union.
Recall that in C# cpt and Genus models are part of types; therefore, a similar situation causes the static type error. But in the case of the Concept pattern models-consistency cannot be checked at compile time.

Instance Uniqueness in Haskell
Type classes in Haskell [23] provide the support for ad hoc polymorphism (function overloading). Like concepts and constraints, they define functions available for some types. For instance, a type class for equality comparison is defined in Haskell as follows: class Eq a where (==) :: a -> a -> Bool (/=) :: a -> a -> Bool x /= y = not (x == y) It contains a function signature for the equality operator ==, and provides a default implementation for the inequality operator /=. Instances (models) of this type class can be retroactively defined for types. For example, an instance for Int, a typeconditional instance for lists, and so on.
instance Eq Int where … --(==) implementation instance Eq a => Eq [a] where … --(==) implementation As long as type classes support ad hoc polymorphism, they are "globally transparent". If a function is a part of some type class, every time the name of this function is used, a compiler knows that an instance of the corresponding type class must be provided. Multiple instances of a type class for the same set of types are not allowed in Haskell, and there is a strong reason for that: type inference. Consider the following function definition: instance for one Eq [a], but not for another one. Therefore, at the point of the useBar definition a compiler has no idea whether there is an error of missed Baz [a] instance or not, because it knows nothing about the instance that might be used in a call to useBar. This information is available only at the point of the actual call, not the function definition. Note that even with the OverlappingInstances extension for Haskell, multiple models in a sense we discuss in the paper are not supported. This extension indeed allows having in a scope several instances that match the constraints deduced for code. But   13 [a] is a type of generic list, it is a notation for Data.List a.
there must be only one, the most specialised instance among them that compiler can select unambiguously (according to some rules) at the point of the code definition. Again, not at the call siteat the point of definition. Thus, a user of the code still cannot choose between instances, an instance is already selected by a compiler. Thus, Haskell sacrifices language support for multiple models for the sake of type inference. It is a strong argument for Haskell users, but in the case of the most object-oriented programming languages, which usually do not permit omitting type annotations of function arguments as well as constraints on type parameters, there is no need to prohibit multiple models in OO languages.

Parameters versus Predicates
So far we have found out that languages with "constraints-are-Not-types" philosophy may potentially provide better support for generic programming compared to other languages, especially if they also allow multiple models definition. We have seen only two languages with such properties, C# cpt [9] and Genus [10], and there is an essential shortcoming in the design of both of them: constraints on type parameters are declared in "predicate-style" rather than "parameter-style". For example, consider the following Genus definition [10]: Nevertheless, we believe that in the case of multiple models the "predicate-style" syntax of constraints is misleading and makes it more difficult to write and call generic code. We suggest that the design of constraints has to be maintained in the "parameter-style". One example of such design is provided by the extension for the OCaml languagemodular implicits [32]; it is briefly discussed in Sec. 4-3-1. A sketch of the "parameter-style" design of constraints for object-oriented languages is presented in Sec. 4-3-2.

Modular Implicits in OCaml
In the "modular implicits" extension for the OCaml language [32] module types are used to describe constraints, modules represent models, with generic functions explicitly taking module-parameters. Fig. 14 Fig. 15 shows some examples of generic code in the style of concept-parameters, which we call Cp# -C# with concept-Parameters. Concepts are the same as in C# cpt , whereas constraints on type parameters are not predicates any more, they are explicitly stated as parameters in the angle brackets after the " | " sign. In the ICollection<T> interface the Remove method is obviously generic: it takes the concept-parameter eq for comparing values of type T. Note that concept-parameters can even be non-generic as in the MaxInt function. If default models are supported, it must be possible to infer concept-arguments just in the same way as in C# cpt or Genus, so that in common cases instances of generic functions and classes can be written in a usual way, without the need to specify the models required:

Concept Parameters for C#
var ints = new ISet<int>(...); var has5 = Contains(ints, 5); var maxv = MaxInt(ints); var minv = MaxInt<|IntOrdDesc>(ints); ISet<String> s1 = ...; ISet<String|StringEqCaseIS> s2 = ...; s1 = s2; // Static ERROR, s1 and s2 have different types C# cpt and Genus can easily be redesigned to follow the "concept-parameters" style presented here. With this style, the syntax of such languages would perfectly fit the semantics. On the other hand, the "concept-predicates" style used misleads a programmer and masks the fact that constraints can be satisfied non-uniquely.  Table 1 provides a summary on comparison of the languages: each row corresponds to one property important for generic programming, each column shows levels of support of the properties in one language. Black circle • indicates full support of a property, ◐partial support, ○ means that a property is not supported at the language level, ✴ means that a property is emulated using the Concept pattern, and the "−" sign indicates that a property is not applicable to a language. The "ModImpl" column corresponds to the Ocaml modular implicits. All the properties that appear in rows of Table 1 were discussed in Sec. 3 and Sec. 4. Related properties are grouped within horizontal lines; some of them are mutually exclusive. For example, as we saw earlier, the use of constraints as types and natural language support for multi-type constraints are mutually exclusive features. The major features analysed in the paper are highlighted in bold. Table 1. The levels of support for generic programming on OO languages a Constraints have no self types, therefore, any function member of a constraint can be treated as static function. b G supports lexically-scoped models but not really multiple models. c If multiple models are not supported, the notion of model-dependent types does not make sense. d C++0x concepts, in contrast to G concepts, provide full support for concept-based overloading.