Generic method type inference behavior?

Started by
6 comments, last by stonemetal 15 years, 9 months ago
As some of you might know, my current project is a little toy programming language. As part of that, I'm doing some research into generics and the type inference of generic methods in particular. There are three kind of quickie test cases I have, and ran for Java, C#, and C++:

// C# - VS2008

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1 {
    public class a { }
    public class b : a { }
    public class c : a { }
    class Program {
        public static void Method<T>(T lhs, T rhs) {
            Console.WriteLine(typeof(T).Name);
        }
        public static void ListMethod<T>(T lhs, List<T> rhs) {
            Console.WriteLine(typeof(T).Name);
        }
        static void Main(string[] args) {
            c C = new c();
            b B = new b();
            //Method(B, C);             // failure.
            List<a> foo = new List<a>();
            ListMethod(B, foo);
        }
    }
}





// java - 1.6 NB6.1

// a,b,c included in other files
    
public class Main {
    
    
    public static <T> void Method(T lhs, T rhs){
        
        System.out.println(lhs.getClass().getName());
        System.out.println(rhs.getClass().getName());
    }
    
    public static <T> void ListMethod(T lhs, ArrayList<T> rhs){
        
    }
    
    public static <T> void UnaryListMethod(ArrayList<T> rhs){
        
    }

    public static void main(String[] args) {
        // TODO code application logic here
        b B = new b();
        c C = new c();
        
        Method(B,C);
        Method(B,new String());
        
        ListMethod(B,new ArrayList<a>());
        
        UnaryListMethod(new ArrayList<a>());
    }

}




#include <iostream>
#include <vector>

template <class T> void Method(T *lhs, T *rhs){
        T::foo();
}

class a{
public:
        static void foo(){
                std::cout << "A!\n";
        }
};

class b:a{
public:
        static void foo(){
                std::cout << "B!\n";
        }
};

class c:a{
public:
        static void foo(){
                std::cout << "C!\n";
        }
};

template <class T> void ListMethod(std::vector<T> lhs, T rhs){
        T::foo();
}

template <class T> void UnaryListMethod(std::vector<T> rhs){
        T::foo();
}

int main(){
        b B;
        c C;

        //Method(&B,&C);           // failure.

        std::vector<a> listA;

        //ListMethod(listA,C);     // failure.
        UnaryListMethod(listA);
}



And the results are rather unusual. C++ failed both combined inference tests, and passed only the one where the type was pulled from a parameter's template. Using straight types and not pointers for the method or pointer variables for the main produced similar failures. C# failed the B,C inference, but somehow knew that B,List<A> would result in an A. (and seems to follow that behavior for Method if one is a subtype of the other). It too could pull from the generic bindings of its parameters. And Java passed all 3 tests, though you couldn't really do much with T (though my java is poor, so I might be missing something). Are there any similar tests that might expose quirks in the languages' implementations that I might find interesting? Any other languages' implementations (besides Haskell's) that are more interesting? And the real basis for the tests: What do you expect the behavior to be for Method(T,T) when called with two distinct types that share a common base type?
Advertisement
Certainly, for C++, this makes a lot of sense. In C++, the type inference is not dynamic. You would need to do this differently in C++ to make this work. What you'd probably want to do is have multiple template parameters and then internally do a static check to see that they share a common base.

I don't know enough C# to comment. I think Java's results may also make sense, since Java implements generics via erasure.
In the C++ example, Method takes two pointers of the same type and &B and &C are of type B * and C* respectively, so Method(&B, &C) could not work (the same applies to ListMethod). Secondly, foo is not virtual (and a static method cannot be virtual) so even if Method somehow worked as Method<a>, it would output "A!". Thirdly, b and c are not a since you made a their private base class.

PS. ListMethod<A> would slice C since parameters are passed by value, and only a part of the object would be copied.
Quote:Original post by rozz666
In the C++ example, Method takes two pointers of the same type and &B and &C are of type B * and C* respectively, so Method(&B, &C) could not work (the same applies to ListMethod).


Apparently. Though inferring the base type would make the generic method 'work'. C++ just doesn't do that.

Quote:
Secondly, foo is not virtual (and a static method cannot be virtual) so even if Method somehow worked as Method<a>, it would output "A!".


That is the point.

Quote:
Thirdly, b and c are not a since you made a their private base class.

PS. ListMethod<A> would slice C since parameters are passed by value, and only a part of the object would be copied.


Which should have no bearing on the type inference of the templated method, but thanks! My C++ is rusty with disuse.

Quote:Original post by Telastyn
As some of you might know, my current project is a little toy programming language. As part of that, I'm doing some research into generics and the type inference of generic methods in particular.

You might consider looking at OCaml for another example of type inference and its "generics"(polymorphic types).
Turring Machines are better than C++ any day ^_~
Quote:Original post by Telastyn
Quote:
Thirdly, b and c are not a since you made a their private base class.

PS. ListMethod<A> would slice C since parameters are passed by value, and only a part of the object would be copied.


Which should have no bearing on the type inference of the templated method, but thanks! My C++ is rusty with disuse.


Well, it should. b and c have no public base class, so Method(&B, &C) could not deduce a even if it normally did.

Now consider this example:

class A { };class B : public A { };class C : public B { };class D : public B { };C c;D d;Method(&c, &d) // should work as Method<A> or Method<b>?


or this

class A { };class B { };class C : public A, public B { };class D : public A, public B { };Method(&c, &d) // again, A or B?
Quote:Original post by Telastyn

Quote:
Secondly, foo is not virtual (and a static method cannot be virtual) so even if Method somehow worked as Method<a>, it would output "A!".


That is the point.



But this will work:

class A {public:    virtual ~A() { }};class B : public A { };class C : public A { };template <typename T>void foo(T *a, T *b){    std::cout << typeid(*a).name() << std::endl;    std::cout << typeid(*b).name() << std::endl;}int main(){    B b;    C c;    foo<A>(&B, &C);}
Quote:Original post by Telastyn
As some of you might know, my current project is a little toy programming language. As part of that, I'm doing some research into generics and the type inference of generic methods in particular.

There are three kind of quickie test cases I have, and ran for Java, C#, and C++:

And the results are rather unusual.


How is it unusual that type inferencing doesn't work in languages that don't implement type inferencing? And that trying to hack something together that appears to be type inferencing works about as well as the author of the tests understands the languages he is manipulating.

Generics in c++,c# are code generators, anytime you instantiate a generic in either of these languages you get custom code for the type. The place where they generate code is where they differ C++ generates it at compile time based on information provided by you the programmer. In C# you get generic code from the compiler that is transformed in to type specific code by the runtime. In Java it throws the type info away and hopes you know what you are doing. None of them do anything remotely like type inference. Interestingly enough all three have talked of adding the feature.

My only experience with type inferencing has been haskell and ocaml so if others do it better then forgive my ignorance.
type inferencing typically types to a name by that I mean the statement
a+b types to numeric in Haskell and int in ocaml. Introduce a new type for which + is a valid operation then you get a type ambiguity which would require type annotations in haskell and is just disallowed in ocaml.

This topic is closed to new replies.

Advertisement