• Advertisement
Sign in to follow this  

C# and generic code

This topic is 4222 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Ive been programming in D for a year or so now, but the lack of developer environment and support(code) made me look for something similar without those problems, hence: C#. I really liked what i saw on first impression, and picked a project to start testing it out. On of the first things i needed was a mathematical vector class. Obviously it would be nice to create one of unspecified type. This appeared to be impossible. After some internet searches, it still seems to be impossible without ugly hacks that affect runtime performance. Please tell me this is a joke. This language seems to have so much potential, how can they seriously consider implementing rules (of which i have yet to see the point) that disallow you to do something so basic? If they really insist on that, why dont all language native arithmetic types implement some sort of 'Arithmetic' interface? Not being able to perform operators on language native templated types seems simply unacceptable to me.

Share this post


Link to post
Share on other sites
Advertisement
To end this with a clear question: this cant be true, can it?

If it is, where then can i find a powerfull, modern and well-supported language?

Share this post


Link to post
Share on other sites
Hrm, it does at first glance appear to be correct.

The basic problem (for those that don't have time to hunt for links) is that C# generic types have no guarantee to have operators defined. Thus T op T is undefined. C# allows type constraints, but that requires the specified type to inherit from an abstract base class (understandably unacceptable), or from an interface with the operators defined. oops operator overloading in C# is done with static functions, and interfaces can't implement static functions.

[Caveat: I am no great guru, and only have a vague understanding of language and compiler internals. The following 2 paragraphs is somewhat supposition]

So, T op T is left undefined. In C++/D this doesn't matter much. The type is replaced in T, and if T happens to have op; great. Otherwise you'll get a compiler error dumped out. In C# this is pretty important; C# doesn't plop in the type. C# compiles to a generic object, and upon instantiation JIT's it into the resolved type. So that's the rule choice. So what should happen if the generic type gets the actual type while running and it doesn't support the operation? Keel over and die?

And that ignores reflection, serialization, and other goodies that C#/.NET provides on types (even generic types) that would likely be quite a bit more difficult, if not impossible to provide with full flavored templates.


Give it some time and practice. Generics/templates are weak in C# (and everything else) compared to C++/D. Other features (everything deriving from Object, the Type object, attributes) alleviate the need for templates/generics quite a bit. I know it took me a while to realise that and learn to code in C# style rather than C# in C++ style.

[edit: Linky]

Share this post


Link to post
Share on other sites
O'Caml is a very nice, modern and powerful language. Whether or not you'd consider it well supported, I can't say. I'm more interested in the languages themselves than their libraries.

But I'll have to admit: O'Caml suffers from the same problem as C#: you won't be able to make one vector-"class" that would be perfectly parameterizable, working with integers and on floats (I already welcome jdh30 to this thread, ready to contradict this).

Share this post


Link to post
Share on other sites
yeah, this was one of the big things that dissapointed me about C# Generics. I mean, I still use them and they're great ... but support for this kind of thing would have been awesome.

Now, just for the sake of discussion, I'm assuming by your little performance/hack comment that you read Eric Gunnerson's take on the issue? He too mentioned the performance issues. Unfortunately, I don't think he ever went back and tested this, but one of the comenters suggested a way to get around the performance issues brought on by the virtual method calls by using interfaces.

If by chance you do get it to work, I would be quite interested in seeing your implementation as I've always wanted to do just what you're suggesting :-)

Share this post


Link to post
Share on other sites
this piece from your linked interview seems to sum it up nicely:
Quote:

Now, in C#, operators are static members. So, an operator can never be a member of an interface, and therefore an interface constraint could never endow you with an operator+. The only way you can endow yourself with an operator+ is by having a class constraint that says you must inherit from, say, Number, and Number has an operator+ of two Numbers. But you could not in the abstract say, "Must have an operator+," and then we polymorphically resolve what that means.

Bill Venners: You did the constraints by type, not by signature.

Anders Hejlsberg: Yes.

Bill Venners: So the type must either extend a class or implement interfaces.

Anders Hejlsberg: Yes. And we could have gone further. We did give thought to going further, but it gets very complicated. And it's not clear that the added complexity is worth the small yield that you get. If something you want to do is not directly supported in the constraint system, you can do it with a factory pattern. You could have a Matrix<T>, for example, and in that Matrix you would like to define a dot product method. That of course that means you ultimately need to understand how to multiply two Ts, but you can't say that as a constraint, at least not if T is int, double, or float. But what you could do is have your Matrix take as an argument a Calculator<T>, and in Calculator<T>, have a method called multiply. You go implement that and you pass it to the Matrix.

Bruce Eckel: And Calculator is a parameterized type also.

Anders Hejlsberg: Yes. It is sort of a factory pattern. So there are ways of doing these things. It's maybe not quite as nice as you'd like, but everything comes at a price.

but forgive my noobness: i dont quite understand this 'factory pattern' solution. does it have serious runtime performance implications? if so, i dont even want to bother knowing more about it, but if its something i can stuff away in my vector source file never to be bother again with, im willing to put up with it :).

Share this post


Link to post
Share on other sites
Perhaps something like this?


namespace VecTest
{
class Program
{
static void Main(string[] args)
{
Vector<int> u = Vector<int>.Create( 5, 8 );
Vector<int> v = Vector<int>.Create( 3, 1 );

Vector<int> s = u + v;

Console.WriteLine( "{0} + {1} = {2}", u, v, s );
}
}

abstract class Calculator<T>
{
public abstract T Add(T x, T y);
public abstract T Mul(T x, T y);
}

class IntCalc : Calculator<int>
{
public override int Add(int x, int y)
{
return x + y;
}

public override int Mul(int x, int y)
{
return x * y;
}
}

class FloatCalc : Calculator<float>
{
public override float Add(float x, float y)
{
return x + y;
}

public override float Mul(float x, float y)
{
return x * y;
}
}

struct Vector<T>
{
public static Vector<int> Create(int x, int y)
{
return new Vector<int>(new IntCalc(), x, y);
}

public static Vector<float> Create(float x, float y)
{
return new Vector<float>(new FloatCalc(), x, y);
}

private Vector(Calculator<T> calc, T x, T y)
{
_calc = calc;

X = x;
Y = y;
}

public static Vector<T> operator +(Vector<T> u, Vector<T> v)
{
Calculator<T> c = u._calc;

T sx = c.Add( u.X, v.X );
T sy = c.Add( u.Y, v.Y );

return new Vector<T>( c, sx, sy );
}

public override string ToString()
{
return string.Format( "({0}, {1})", X, Y );
}

private Calculator<T> _calc;

public T X;

public T Y;
}
}



Maybe turning Calculator into a struct might improve performance. Also, is there no way of defining a member of the Vector "metaclass", not Vector<T>? The static factory methods Create(...) do not need T to be specified.

Share this post


Link to post
Share on other sites
Quote:
Original post by SamLowry
O'Caml is a very nice, modern and powerful language. Whether or not you'd consider it well supported, I can't say. I'm more interested in the languages themselves than their libraries.

But I'll have to admit: O'Caml suffers from the same problem as C#: you won't be able to make one vector-"class" that would be perfectly parameterizable, working with integers and on floats (I already welcome jdh30 to this thread, ready to contradict this).

Actually, you can, you just have to use variants.

type number = Int of int | Float of float

Of course, this requires wrapping all your constants with the correct type constructor and it's not very fast--one of the reasons I dislike OCaml.

edit:
Oh, and did I mention that you'd also either have to 1) redefine the built-in operators (+ - * /) to accept either ints or floats, or pattern match everywhere in your code. The former would look something like this:

let (+|) x y = match (x, y) with
Int x, Int y -> Int (x + y)
| Int x, Float y -> Float ((float_of_int x) +. y)
| Float x, Int y -> Float (x +. (float_of_int y))
| Float x, Float y -> Float (x +. y);;

...

And would have to be used like:

Float 10.0 +| Int 10


Did I mention I don't like OCaml?

Share this post


Link to post
Share on other sites
Quote:
Original post by bytecoder
Quote:
Original post by SamLowry
O'Caml is a very nice, modern and powerful language. Whether or not you'd consider it well supported, I can't say. I'm more interested in the languages themselves than their libraries.

But I'll have to admit: O'Caml suffers from the same problem as C#: you won't be able to make one vector-"class" that would be perfectly parameterizable, working with integers and on floats (I already welcome jdh30 to this thread, ready to contradict this).

Actually, you can, you just have to use variants.

type number = Int of int | Float of float

Of course, this requires wrapping all your constants with the type constructors and it's not very fast--one of the reasons I dislike OCaml.


I knew that someone would give me that solution! Ordinarily it's jdh30 :)
I certainly do miss Haskell's type classes in O'Caml.

Share this post


Link to post
Share on other sites
Quote:
Original post by SamLowry
Quote:
Original post by bytecoder
Quote:
Original post by SamLowry
O'Caml is a very nice, modern and powerful language. Whether or not you'd consider it well supported, I can't say. I'm more interested in the languages themselves than their libraries.

But I'll have to admit: O'Caml suffers from the same problem as C#: you won't be able to make one vector-"class" that would be perfectly parameterizable, working with integers and on floats (I already welcome jdh30 to this thread, ready to contradict this).

Actually, you can, you just have to use variants.

type number = Int of int | Float of float

Of course, this requires wrapping all your constants with the type constructors and it's not very fast--one of the reasons I dislike OCaml.


I knew that someone would give me that solution! Ordinarily it's jdh30 :)
I certainly do miss Haskell's type classes in O'Caml.

*ahem* :) Yeah, I tend to like haskell more than OCaml, too.

edit:
I realize now that that's not particularly relevant :/ Oh well, at least it's interesting.

Share this post


Link to post
Share on other sites
Quote:
Original post by SamLowry
Perhaps something like this?

*** Source Snippet Removed ***

Maybe turning Calculator into a struct might improve performance. Also, is there no way of defining a member of the Vector "metaclass", not Vector<T>? The static factory methods Create(...) do not need T to be specified.


thanks.

the actual usage looks clean enough, but whats under the hood makes baby jezus cry... more memory, less performance...

just using copy-paste and code refactoring is probably the easiest way out of this.

Share this post


Link to post
Share on other sites
Quote:
Original post by Telastyn
Or, y'know using the pre-made D3DX vector/matrix stuff.


unfortunately there is also use for vectors outside of graphical programming.

that said (warning, noob question ahead): isnt it possible to mix all .net languages together, given that they all use the same intermediate code? couldnt i just link in a c++/whatever vector class?

Share this post


Link to post
Share on other sites
Quote:
Original post by Eelco
Quote:
Original post by Telastyn
Or, y'know using the pre-made D3DX vector/matrix stuff.


unfortunately there is also use for vectors outside of graphical programming.

that said (warning, noob question ahead): isnt it possible to mix all .net languages together, given that they all use the same intermediate code? couldnt i just link in a c++/whatever vector class?


It depends on how intermediate code (IC) is generated. If a C# generic is compiled to an IC generic, then it could very well be that both systems (C#'s and IC's) share the same limitations.

One could of course define it's own smarter language which will instantiate classes itself instead of relying on the IC. Hence, if you'd use Vector<int> in your code, it would be compiled to Vector_int, as if generated by copy paste with the correct substitutions. It would be very efficient, but other languages would not be able to use the Vector<T> class, only Vector_int, Vector_float, ... and whatever other classes you had your smart language generate explicitly.

I'm not an expert on how .NET works in the lower levels, so I might be plain wrong about this, but I believe my proposed "solution" would work regardless of what happens below.

Share this post


Link to post
Share on other sites
Quote:
Original post by Eelco
that said (warning, noob question ahead): isnt it possible to mix all .net languages together, given that they all use the same intermediate code? couldnt i just link in a c++/whatever vector class?


Adding to this what I think happens:

The issue is that C++ templates are code substitution templates - they do not get issued to IL in their uninstantiated form - the compiler basically generates a version of the vector class with the types substituted whenever you use the template. As such, while the instantiated versions may be visible to other languages, you can't grab the uninstantiated definition and create your instances at will.

The C# (managed) generics on the other hand are a bit weirder - there is support in the IL to define the types without an actual instantiation, so a generic type defined in C# and compiled into a class library can be instantiated in vb with whatever type desired. C++ is a bit less strongly typed - every program redefines the whole template and because of binary compatibility can call into a library that also defined the type. In the managed world you can't do this - a type is associated to the assembly that declares it, so even if you re-declare an identical type you can't assign instances from another assembly to it.

Share this post


Link to post
Share on other sites
Quote:
Original post by SamLowryBut I'll have to admit: O'Caml suffers from the same problem as C#: you won't be able to make one vector-"class" that would be perfectly parameterizable, working with integers and on floats (I already welcome jdh30 to this thread, ready to contradict this).


How's this:


module Make(Elt : sig
type t
val zero : t
val one : t
val ( ~: ) : t -> t
val ( +: ) : t -> t -> t
val ( -: ) : t -> t -> t
val ( *: ) : t -> t -> t
val ( /: ) : t -> t -> t
val sqrt : t -> t
end) = struct
type t = Elt.t list

open Elt
open List

let ( ~| ) = map ( ~: )
let ( +| ) = map2 ( +: )
let ( -| ) = map2 ( -: )
let ( *| ) s = map (( *: ) s)
let ( *.| ) = fold_left2 (fun t a b -> t +: a *: b) zero
let length2 r = r *.| r
let length r = sqrt(length2 r)
end;;


Cheers,
Jon.

Share this post


Link to post
Share on other sites
Quote:
Original post by bytecoder
Actually, you can, you just have to use variants.

type number = Int of int | Float of float



You're implementing dynamic typing, which is slow and unnecessary.

Quote:

Of course, this requires wrapping all your constants with the correct type constructor and it's not very fast--one of the reasons I dislike OCaml.


Of course it isn't fast, you're writing lots of pointless code that gets executed.

Quote:

edit:
Oh, and did I mention that you'd also either have to 1) redefine the built-in operators (+ - * /) to accept either ints or floats, or pattern match everywhere in your code.


Not true. Just parameterise your vector over the type of its elements.

Quote:

The former would look something like this:

let (+|) x y = match (x, y) with
Int x, Int y -> Int (x + y)
| Int x, Float y -> Float ((float_of_int x) +. y)
| Float x, Int y -> Float (x +. (float_of_int y))
| Float x, Float y -> Float (x +. y);;

...

And would have to be used like:

Float 10.0 +| Int 10


Did I mention I don't like OCaml?


Sheesh.

Cheers,
Jon.

Share this post


Link to post
Share on other sites
Quote:
Original post by jdh30
Quote:
Original post by SamLowryBut I'll have to admit: O'Caml suffers from the same problem as C#: you won't be able to make one vector-"class" that would be perfectly parameterizable, working with integers and on floats (I already welcome jdh30 to this thread, ready to contradict this).


How's this:


module Make(Elt : sig
type t
val zero : t
val one : t
val ( ~: ) : t -> t
val ( +: ) : t -> t -> t
val ( -: ) : t -> t -> t
val ( *: ) : t -> t -> t
val ( /: ) : t -> t -> t
val sqrt : t -> t
end) = struct
type t = Elt.t list

open Elt
open List

let ( ~| ) = map ( ~: )
let ( +| ) = map2 ( +: )
let ( -| ) = map2 ( -: )
let ( *| ) s = map (( *: ) s)
let ( *.| ) = fold_left2 (fun t a b -> t +: a *: b) zero
let length2 r = r *.| r
let length r = sqrt(length2 r)
end;;


Cheers,
Jon.


That would indeed be a module of parameterizable vectors. I did think of modules, but for some reason I saw problems where there actually weren't any.

One little annoyance would be vector literals, but I guess that with the necessary helper functions it could be made as simple as "vector [5; 6; 3]" (I really don't want to have to type [Elt.one +: Elt.one +: Elt.one +: Elt.one +: Elt.one; Elt.one +: Elt.one +: Elt.one +: Elt.one +: Elt.one +: Elt.one; Elt.one +: Elt.one +: Elt.one])

Another annoyance would be the sort-of dynamic typing on the size of the vector: there's no compile-time check you are adding a 2D-vector with another 2D-vector. Of course, you could just define separate modules, but that would lead to duplication, and I don't like duplication. A solution I see is to have a module per kind of vector, with a hidden "type t = V2D of Elt.t list", a factory function "Vector2D.create x y = V2D [x;y]" and functions which retrieve the Elt.t list and pass it to a general VectorN module, but that would still require a lot of rather useless code. I'm currently not creative enough to find a truly elegant solution.

Share this post


Link to post
Share on other sites
Quote:
Original post by SamLowry
That would indeed be a module of parameterizable vectors. I did think of modules, but for some reason I saw problems where there actually weren't any.

One little annoyance would be vector literals, but I guess that with the necessary helper functions it could be made as simple as "vector [5; 6; 3]" (I really don't want to have to type [Elt.one +: Elt.one +: Elt.one +: Elt.one +: Elt.one; Elt.one +: Elt.one +: Elt.one +: Elt.one +: Elt.one +: Elt.one; Elt.one +: Elt.one +: Elt.one])


No, the literals are already as simple as you can get:


[1; 2; 3]


Quote:

Another annoyance would be the sort-of dynamic typing on the size of the vector: there's no compile-time check you are adding a 2D-vector with another 2D-vector. Of course, you could just define separate modules, but that would lead to duplication, and I don't like duplication. A solution I see is to have a module per kind of vector, with a hidden "type t = V2D of Elt.t list", a factory function "Vector2D.create x y = V2D [x;y]" and functions which retrieve the Elt.t list and pass it to a general VectorN module, but that would still require a lot of rather useless code. I'm currently not creative enough to find a truly elegant solution.


Yes. I'd just write a compiler to generate the optimised, specialised OCaml code...

Cheers,
Jon.

Share this post


Link to post
Share on other sites
Quote:
Original post by jdh30
No, the literals are already as simple as you can get:

[1; 2; 3]


I had change in mind: if I had vector<float> and wanted to change it to vector<double>. If I wanted to do something similar in O'Caml, I'd have to change all literals, such as [0; 0; 0] to [0.; 0.; 0.]. In order to prevent this, I'd think you'd need some helper functions:

module type Number =
sig
type t
val from_int : int -> t
(* ... *)
end;;

module Integer =
struct
type t = int
let from_int x = x
end;;

module Float =
struct
type t = float
let from_int = float_of_int
end;;

module Vector (Elt : Number) =
struct
let create coords =
List.map (Elt.from_int) coords
end;;

let v = Vector.create [0;0;0]

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
Quote:
Original post by SamLowry
I had change in mind: if I had vector<float> and wanted to change it to vector<double>. If I wanted to do something similar in O'Caml, I'd have to change all literals, such as [0; 0; 0] to [0.; 0.; 0.]. In order to prevent this, I'd think you'd need some helper functions:

module type Number =
sig
type t
val from_int : int -> t
(* ... *)
end;;

module Integer =
struct
type t = int
let from_int x = x
end;;

module Float =
struct
type t = float
let from_int = float_of_int
end;;

module Vector (Elt : Number) =
struct
let create coords =
List.map (Elt.from_int) coords
end;;

let v = Vector.create [0;0;0]


Yes. I'm not sure this is likely to be a problem in practice. If it is, write a camlp4 macro to implement literals however you wish. For example, I use [:1;2;3:] to denote the balanced binary tree representing the set {1, 2, 3}.

Cheers,
Jon.

Share this post


Link to post
Share on other sites
Quote:
Original post by jdh30
Quote:
Original post by bytecoder
Actually, you can, you just have to use variants.

type number = Int of int | Float of float



You're implementing dynamic typing, which is slow and unnecessary.

Yup. I'd forgotten that it's possible to do that in OCaml. My mistake :)


Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement