12

I want to be able to make a method, in C#, whose output type depends on its argument value; loosely,

delegate B(a) DFunc<A,B>(A a);

As an example, I'd like to write a function which takes an integer and returns one of many possible types depending on the argument:

f(1) = int
f(2) = bool
f(3) = string
f(n), where n >= 4 = type of n-by-n matrices

Any help would be useful.

Benjamin Hodgson
  • 42,952
  • 15
  • 108
  • 157
Musa Al-hassy
  • 982
  • 1
  • 7
  • 12
  • 1
    What don't you like about returning an Object? – Douglas Zare Jul 03 '15 at 19:36
  • I understand your question down to the "toy example", at which point I fail to understand what you are aiming at. In your question, you say the "output type depends on its argument **type**" (emphasis by myself). However, in your toy example, the argument type is always the same, it's always an `int`. There, the output type seems to depend on the argument **value**. Both are very different cases and warrant different answers, so please clarify what you are looking for. – O. R. Mapper Jul 03 '15 at 21:57
  • DouglasZare :: please elaborate via a possible implementation. @O.R.Mapper :: Fixed, I meant the argument only, not its type. Yes, it depends on the argument value. – Musa Al-hassy Jul 04 '15 at 02:36
  • 1
    I don't think this is possible, and I'm also wondering why you would want to do this. Even if it was possible, how would you use it? What would the part of your program that calls this method look like? Maybe you could try to explain more what you're trying to achieve. – RenniePet Jul 04 '15 at 04:01
  • Can you describe better what do you need to achieve. What is the problem you have beyond changing the return type according to a value? Can you describe you actual scenario? – Dzyann Jul 04 '15 at 05:02
  • I'm trying to mimic the notion of dependent function types as in the languages Agda and Coq, for example. – Musa Al-hassy Jul 04 '15 at 21:45
  • 3
    @MusaAl-hassy I can't believe I missed this question five months ago! It's a hobby-horse of mine and I have an hour of material on the subject. See my answer! – Benjamin Hodgson Nov 03 '15 at 00:31
  • @DzmitryLahoda Broken link :-( – Musa Al-hassy Nov 03 '17 at 20:58
  • https://github.com/louthy/language-ext may have suitable primitives – Dzmitry Lahoda Nov 04 '17 at 17:28

3 Answers3

18

The closest C# gets to the cool features you're used to in better languages like Agda is parametric polymorphism (generics). There's very little type inference - and absolutely nothing resembling higher-kinded types, type classes or implicit terms, higher-rank/impredicative types, existential quantification*, type families, GADTs, any sort of dependent typing, or any other jargon you care to mention, and I don't expect there ever will be.

For one thing, there's no appetite for it. C# is designed for industry, not research, and the vast majority of C# developers - a practical bunch, many of whom fled C++ in the '00s - have never even heard of most of the concepts I listed above. And the designers have no plans to add them: as Eric Lippert is fond of pointing out, a language feature don't come for free when you have millions of users.

For another, it's complicated. C# centrally features subtype polymorphism, a simple idea with surprisingly profound interactions with many other type system features which you might want. Variance, which is understood by a minority of C# developers in my experience, is but one example of this. (In fact, the general case of subtyping and generics with variance is known to be undecidable.) For more, think about higher-kinded types (is Monad m variant in m?), or how type families should behave when their parameters can be subtyped. It's no coincidence that most advanced type systems leave subtyping out: there's a finite amount of currency in the account, and subtyping spends a large proportion of it.

That said, it's fun to see how far you can push it.

// type-level natural numbers
class Z {}
class S<N> {}

// Vec defined as in Agda; cases turn into subclasses
abstract class Vec<N, T> {}
class Nil<T> : Vec<Z, T> {}
// simulate type indices by varying
// the parameter of the base type
class Cons<N, T> : Vec<S<N>, T>
{
    public T Head { get; private set; }
    public Vec<N, T> Tail { get; private set; }

    public Cons(T head, Vec<N, T> tail)
    {
        this.Head = head;
        this.Tail = tail;
    }
}

// put First in an extension method
// which only works on vectors longer than 1
static class VecMethods
{
    public static T First<N, T>(this Vec<S<N>, T> vec)
    {
        return ((Cons<N, T>)vec).Head;
    }
}

public class Program
{
    public static void Main()
    {
        var vec1 = new Cons<Z, int>(4, new Nil<int>());
        Console.WriteLine(vec1.First());  // 4
        var vec0 = new Nil<int>();
        Console.WriteLine(vec0.First());  // type error!
    }
}

Unfortunately it can't be done without the runtime cast inside First. The fact that vec is a Vec<S<N>, T> is not enough to prove to the type checker that it's a Cons<N, T>. (You can't prove it because it isn't true; someone could subclass Vec in a different assembly.) More generally, there's no way to fold up an arbitrary Vec because the compiler can't do induction over natural numbers. It's galling because even though the information is there on the page, the type-checker is too dumb to allow us to harvest it.

Retrofitting dependent types onto an existing language is hard, as the Haskell guys are discovering. Harder when the language is an imperative object-oriented language (typically hard to prove theorems about) based on subtyping (complicated to combine with parametric polymorphism). Even harder when no one's really asking for it.

* Since writing this answer, I've done some more thinking on this topic and realised that higher-rank types are indeed present and correct in C#. This enables you to use a higher-rank encoding of existential quantification.

Benjamin Hodgson
  • 42,952
  • 15
  • 108
  • 157
4

You'd need dependent types to do this. This feature only exists in a few non-mainstream languages such as Idris and Coq.

Given you've correctly tagged that, I assume you're aware c# doesn't have that feature so what/why specifically are you asking?

Dax Fohl
  • 10,654
  • 6
  • 46
  • 90
  • 3
    I learned Agda ---Coq's cooler cousin--- before learning C# and I'm learning C# now and wondered if it had the powerful notions of dependent types. With all the hype of the power of OOP, I thought perhaps a a C# veteran may be able to mimic dependent types... – Musa Al-hassy Jul 04 '15 at 21:47
2

This isn't really much of an answer - as I mention in a comment I don't think that what you're asking for is possible. But this demonstrates what I think user @Douglas Zare is suggesting.

  public void RunTest()
  {
     for (int n = 1; n <= 4; n++)
     {
        object o = F(n);

        if (o is int)
           Console.WriteLine("Type = integer, value = " + (int)o);
        else if (o is bool)
           Console.WriteLine("Type = bool, value = " + (bool)o);
        else if (o is string)
           Console.WriteLine("Type = string, value = " + (string)o);
        else if (o is float[,])
        {
           Console.WriteLine("Type = matrix");
           float[,] matrix = (float[,])o;
           // Do something with matrix?
        }
     }

     Console.ReadLine();
  }


  private object F(int n)
  {
     if (n == 1)
        return 42;

     if (n == 2)
        return true;

     if (n == 3)
        return "forty two";

     if (n >= 4)
     {
        float[,] matrix = new float[n, n];
        for (int i = 0; i < n; i++)
           for (int j = 0; j < n; j++)
              matrix[i, j] = 42f;

        return matrix;
     }

     return null;
  }
RenniePet
  • 11,420
  • 7
  • 80
  • 106
  • Thank-you for your reply! Is there a less clunky way, to avoid all this type casting business? Perhaps If I declared `enum MyType { Bool , Int, String, Matrix(int n)}` and indicated my function returns a `MyType` and perhaps need a few plumbing here and there. Wait...is that `Matrix(int n)` part allowed? Can C# enums have int tags like this? Moreover, can my enum have `Compounded(MyType t)` in it as well? I look forwards to your reply! :-) – Musa Al-hassy Jul 04 '15 at 21:52