Numerics and generics

Posted Sat, Dec 22 2007 14:45 by bill

A couple of weeks ago a friend was chatting to me in IM and asked me about a problem he was having with generics constraints.  I told him he was trying to solve the lack of an INumeric interface issue. Then last week I watched Brian Beckman go through the same thing with generics, again unable to use a numeric constraint, instead having to create his own wrapper types.  This issue is not new, it is well known.  But what is still lacking is clear and open discussion from Microsoft on how to address this,  Anyway, here's my point of view :

The numeric types can be grouped into two separate categories, Integer types, and Real types.  The Integer group currently has 8 types in it, the signed types, SByte, Int16, Int32, Int64, and the unsigned types, Byte, UInt16, UInt32, UInt64.   The Real group has 3 types, Decimal, Single and Double.  Although all of these implement IConvertible, there is no other common base other than ValueType or Object.  In particular, none of these types has defined operators or methods such as Add, instead the operators are provided by languages.

One option would be to include an INumeric interface. The problem with this is that it would need to have methods for all the standard operators, and overloads for combining with each of the other of the 11 numeric types. You couldn't have just one overload and cast to a wider type, as you would have no way to know if there was a wider type.  It may also be advantageous to be able to work with Integer types as distinct from Reals for both performance reasons as well as allowing bitwise operators.

So, I believe the best solution is not an interface, but rather a language provided grouping, or set of groupings.  I'm going to use 3 sets, Numeric:Integer, Numeric:Real, and Numeric:All.  With these you would define a constraint such as


Function MoveRight(Of T As Numeric:Integer)(x As T) As T
  return x >> 1
End Function

 

Or

 

Function AnnualIncrease(Of T As Numeric:Real)(salary As T) As T
  return CType(salary * 1.1, T)
End Function

 

This still has difficulties in that constants are hard to express in the given type, although that could be handled with some compiler relaxation. For example in the above sample of AnnualIncrease, if you declared a variable of type T then assigned it the 1.1, the constant would be inferred based on T.

Function AnnualIncrease(Of T As Numeric:Real)(salary As T) As T
  Dim increase As T = 1.1
  return salary * increase
End Function

 

Because the Numerics are all value types, when using generics I believe they cause a new type to be laid out, one per value type, thus allowing the CLR to verify the type and the operations on those types. The operators for numeric types  are in fact provided by the CLR at an intrinsic level. The problem being is they are often type specific for loading of constants, and some of the operators are different for signed versus unsigned operations. The basic operations such as add, divide, multiply, and, or, etc are the same for all the numeric types.  So it seems that perhaps a little help form the CLR combined with a little flexibility from the languages, providing such a constraint wouldn't be that difficult at all.

It certainly would be nice to see it addressed.

Filed under: , , , ,

Comments

# re: Numerics and generics

Saturday, December 22, 2007 7:58 AM by C.J. Anderson

Amen

# re: Numerics and generics

Saturday, December 22, 2007 4:53 PM by silky

Wouldn't duck typing "kind of" solve this?

Or being able to apply interfaces to a given set of existing classes, assuming they have the appropriate methods?

Then you could create the groups yourself, which would be far more powerful, imho.

grouping IntegralTypes

{

 typeof(int),

 typeof(long),

 typeof(char)

}

kind of like an enum.

# re: Numerics and generics

Saturday, December 22, 2007 7:25 PM by bill

Duck typing and interfaces are essentially the same thing, just duck typing may be late bound and is just a by name matching.  Both of these wouldn't work with numeric types.  First, they'd have to have overloads for the other 11 types in each type. But even then the signatures wouldn't match, so there'd be no common interface.  The only way they could is if each type in the current set implemented 120 or so Add methods etc.  So interfaces and duck typing are out.

For example, conisder if there was a method

Add(value As Int32) AS T

For Int16, T would be Int32, for Int32 it would be Int32, and for Int64 it would be Int64.  There's no common *simple* rule, and T is not the type that implemetns the Add method.

# re: Numerics and generics

Saturday, December 22, 2007 8:27 PM by silky

What I'm talking about is something like this.

take the following

class A { void M (int k); }

class B { void M (int k); }

Then I want to "group" these as the same type, for the purposes of calling their "M" method.

Then the new "grouping" type

grouping MAble

{

typeof(A),

typeof(B)

}

The compiler would find all matching signatures and expose those under the "MAble" type.

MAble m = new A();

m.M(10);

So then this "thing" could be adapted to integers and longs. It would find their common operations.

The problem, I suppose, comes when the return type of M is different (as in the case of ints and longs). But maybe there is an ingenious way around that, that I can't see at the moment. I guess, when trying to match signatures, the compiler would need to see if the return types are convertible up to a common type. But you could always make them into a group, as I posted before, maybe something like this:

grouping Integral

{

typeof(int),

typeof(long)

}

class A { Integral M(Integral k); }

class B { Integral M(Integral k); }

Interesting to think about anyway. At least to me :)

Hope what I'm saying makes a little bit of sense. Nice topic. I can't say I've ever wanted this feature for numerics myself, but I can see it being potentially useful if you were allowed to create your own groupings.