简体   繁体   English

C#中的依赖类型:使输出类型取决于输入值

[英]Dependent Types in C#: making the output type depend on the input value

I want to be able to make a method, in C#, whose output type depends on its argument value; 我希望能够在C#中创建一个方法,其输出类型取决于它的参数值; 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. 任何帮助都会有用。

The closest C# gets to the cool features you're used to in better languages like Agda is parametric polymorphism (generics). 最接近的C#可以获得您在更好的语言中习惯的酷炫功能,例如Agda是参数多态(泛型)。 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. 几乎没有类型推断 - 绝对没有类似高级类型,类型类或隐式术语, 更高级别/不可预测类型,存在量化 *,类型族,GADT,任何类型的依赖类型,或您关心的任何其他术语提到,我不指望会有。

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. C#是专为工业而非研究而设计的,绝大多数C#开发人员 - 其中许多人在00年代逃离了C ++ - 从未听说过我上面列出的大部分概念。 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. 设计师没有计划添加它们:正如Eric Lippert喜欢指出的那样,当你拥有数百万用户时, 语言功能并不是免费的。

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. C#集中具有子类型多态性,这是一个简单的想法,与您可能需要的许多其他类型系统功能进行了令人惊讶的深度交互。 Variance, which is understood by a minority of C# developers in my experience, is but one example of this. 根据我的经验,少数C#开发人员可以理解的差异只是其中的一个例子。 (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. (事实上,分型和方差仿制药的一般情况下, 已知的不可判定 。)更多,想想高kinded类型(是Monad m变体m ?),或类型的家庭应该如何表现时,他们的参数可以子类型。 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. 也就是说,看到你可以推动它有多远,这很有趣。 I have a talk entitled Fun With Generics ( slides here , video now available! ) which is designed to subliminally introduce the idea of dependent types to an unsuspecting audience of C# users. 我有一个名为Fun With Generics的演讲( 这里 幻灯片视频现在可用! ),旨在向C#用户毫无戒心的观众提供从属类型的概念。 After complaining at length about correct programs which the type-checker rejects, and doing silly things with upper type bounds, I show how you can abuse generics to simulate the very simplest examples of dependent types. 在详细抱怨类型检查器拒绝的正确程序,并使用上限类型做傻事后,我展示了如何滥用泛型来模拟依赖类型的最简单的例子。 Here's the absurd conclusion of the talk: 这是谈话的荒谬结论:

// 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 . 不幸的是,如果没有在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> . vecVec<S<N>, T>的事实不足以向类型检查器证明它是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. (你无法证明它,因为它不是真的;有人可以在不同的程序集中Vec 。)更一般地说,没有办法折叠任意Vec因为编译器不能对自然数进行归纳。 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. 正如Haskell人发现的那样,将依赖类型改造为现有语言很难 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# . *自从写完这个答案以来,我已经对这个主题做了更多的思考,并意识到更高级别的类型确实存在并且在C#中是正确的 This enables you to use a higher-rank encoding of existential quantification . 这使您可以使用更高级别的存在量化编码

You'd need dependent types to do this. 您需要依赖类型才能执行此操作。 This feature only exists in a few non-mainstream languages such as Idris and Coq. 此功能仅存在于一些非主流语言中,例如Idris和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? 鉴于你已经正确标记了这一点,我假设你知道c#没有那个功能那么具体是什么/为什么要问?

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. 但这证明了我认为用户@Douglas Zare的建议。

  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;
  }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM