Numerics and generics

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




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.

4 Comments so far

  1.   C.J. Anderson on December 22nd, 2007          


  2.   silky on December 22nd, 2007          

    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

    kind of like an enum.

  3.   bill on December 22nd, 2007          

    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.

  4.   silky on December 22nd, 2007          

    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

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

    MAble m = new A();

    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

    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.