【问题标题】:Dependent Types in C#: making the output type depend on the input valueC#中的依赖类型:使输出类型依赖于输入值
【发布时间】:2015-09-21 16:08:48
【问题描述】:

我希望能够在 C# 中创建一个方法,其输出类型取决于其参数值;松散地,

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

作为一个例子,我想写一个函数,它接受一个整数并根据参数返回许多可能的类型之一:

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

任何帮助都会很有用。

【问题讨论】:

  • 返回 Object 有什么不满意的地方?
  • 我将您的问题理解为“玩具示例”,此时我无法理解您的目标。在您的问题中,您说“输出类型取决于其参数type”(我自己强调)。但是,在您的玩具示例中,参数类型始终相同,始终是int。在那里,输出类型似乎取决于参数 value。两者都是非常不同的情况,需要不同的答案,所以请说明您在寻找什么。
  • DouglasZare :: 请通过可能的实现进行详细说明。 @O.R.Mapper :: 固定,我的意思只是参数,而不是它的类型。是的,这取决于参数值。
  • 我认为这是不可能的,我也想知道您为什么要这样做。即使有可能,你会如何使用它?调用此方法的程序部分会是什么样子?也许您可以尝试解释更多您想要实现的目标。
  • @MusaAl-hassy 我不敢相信我五个月前错过了这个问题!这是我的一个爱好,我有一个小时的关于这个主题的材料。看我的回答!

标签: c# type-systems dependent-type


【解决方案1】:

C# 最接近您在更好 语言(如 Agda)中使用的酷炫特性是参数多态(泛型)。几乎没有类型推断 - 绝对没有类似于更高种类的类型、类型类或隐式术语、更高等级/指示性类型、存在量化*、类型族、GADT、任何类型的依赖类型,或您愿意提及的任何其他行话,我不希望会有。

一方面,它没有胃口。 C# 是为工业而不是研究设计的,绝大多数 C# 开发人员——一群实用的人,其中许多人在 00 年代逃离了 C++——甚至从未听说过我上面列出的大多数概念。并且设计师没有添加它们的计划:正如 Eric Lippert 喜欢指出的那样,a language feature don't come for free 当您拥有数百万用户时。

另一方面,这很复杂。 C# 以子类型多态为中心,这是一个简单的想法,与您可能想要的许多其他类型系统功能具有惊人的深刻交互。根据我的经验,少数 C# 开发人员可以理解的 Variance 只是其中的一个例子。 (事实上​​,子类型和泛型的一般情况是known to be undecidable。)更多,请考虑更高类型的类型(m 中的Monad m 变体?),或者类型族在其参数时应该如何表现可以进行子类型化。大多数高级类型系统都忽略了子类型,这并非巧合:帐户中的货币数量是有限的,而子类型会花费其中的很大一部分。

也就是说,看看你能推多远很有趣。

// 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!
    }
}

不幸的是,如果没有 First 中的运行时强制转换,它就无法完成。 vecVec&lt;S&lt;N&gt;, T&gt; 的事实不足以向类型检查器证明它是 Cons&lt;N, T&gt;。 (你无法证明它,因为它不是真的;有人可以在不同的程序集中继承Vec。)更一般地说,没有办法折叠任意的Vec,因为编译器不能对自然进行归纳数字。这很烦人,因为即使页面上有信息,类型检查器也太笨了,我们无法获取它。

将依赖类型改造为现有语言是困难,正如 Haskell 人员所发现的那样。当语言是基于子类型(与参数多态性结合起来很复杂)的命令式面向对象语言(通常难以证明定理)时,难度会更大。当没有人真正要求它时,就更难了。

* 自从写了这个答案后,我对这个话题做了更多的思考,并意识到higher-rank types are indeed present and correct in C#。这使您可以使用higher-rank encoding of existential quantification

【讨论】:

    【解决方案2】:

    您需要依赖类型来执行此操作。此功能仅存在于少数非主流语言中,例如 Idris 和 Coq。

    鉴于您已正确标记,我假设您知道 c# 没有该功能,那么您具体问什么/为什么要问?

    【讨论】:

    • 我在学习 C# 之前学习了 Agda --- Coq 的酷表亲--- 而我现在正在学习 C# 并且想知道它是否具有依赖类型的强大概念。随着 OOP 力量的大肆宣传,我想也许一个 C# 的老手可能能够模仿依赖类型......
    【解决方案3】:

    这并不是一个真正的答案 - 正如我在评论中提到的那样,我不认为你所要求的是可能的。但这证明了我认为用户 @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;
      }
    

    【讨论】:

    • 感谢您的回复!有没有一种不那么笨拙的方法来避免所有这种类型的铸造业务?也许如果我声明 enum MyType { Bool , Int, String, Matrix(int n)} 并指出我的函数返回一个 MyType 并且可能需要在这里和那里进行一些管道。等等...Matrix(int n) 部分是否允许? C# 枚举可以有这样的 int 标签吗?此外,我的枚举中也可以包含Compounded(MyType t) 吗?我期待着您的回复! :-)
    猜你喜欢
    • 1970-01-01
    • 2018-06-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-22
    • 1970-01-01
    • 2023-03-21
    相关资源
    最近更新 更多