Зависимые типы в 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

Любая помощь будет полезна.


person Musa Al-hassy    schedule 03.07.2015    source источник
comment
Что вам не нравится в возврате объекта?   -  person Douglas Zare    schedule 03.07.2015
comment
Я понимаю ваш вопрос вплоть до игрушечного примера, и в этот момент я не понимаю, к чему вы стремитесь. В своем вопросе вы говорите, что тип вывода зависит от его аргумента type (выделено мной). Однако в вашем игрушечном примере тип аргумента всегда один и тот же, это всегда int. Там тип вывода, по-видимому, зависит от аргумента value. Оба случая очень разные и требуют разных ответов, поэтому, пожалуйста, уточните, что вы ищете.   -  person O. R. Mapper    schedule 04.07.2015
comment
DouglasZare :: пожалуйста, расскажите о возможной реализации. @ORMapper :: Исправлено, я имел в виду только аргумент, а не его тип. Да, это зависит от значения аргумента.   -  person Musa Al-hassy    schedule 04.07.2015
comment
Я не думаю, что это возможно, и мне также интересно, почему вы хотите это сделать. Даже если бы это было возможно, как бы вы его использовали? Как будет выглядеть часть вашей программы, вызывающая этот метод? Может быть, вы могли бы попытаться объяснить больше, чего вы пытаетесь достичь.   -  person RenniePet    schedule 04.07.2015
comment
Можете ли вы описать лучше, что вам нужно для достижения. В чем проблема, кроме изменения возвращаемого типа в соответствии со значением? Можете ли вы описать ваш реальный сценарий?   -  person Dzyann    schedule 04.07.2015
comment
Я пытаюсь имитировать понятие зависимых типов функций, как, например, в языках Agda и Coq.   -  person Musa Al-hassy    schedule 05.07.2015
comment
@MusaAl-hassy Не могу поверить, что пропустил этот вопрос пять месяцев назад! Это мое хобби, и у меня есть час материала на эту тему. Смотрите мой ответ!   -  person Benjamin Hodgson♦    schedule 03.11.2015
comment
@DzmitryLahoda Ссылка битая :-(   -  person Musa Al-hassy    schedule 03.11.2017
comment
github.com/louthy/language-ext может иметь подходящие примитивы   -  person Dzmitry Lahoda    schedule 04.11.2017


Ответы (3)


Ближе всего C# приближается к классным функциям, к которым вы привыкли в лучших языках, таких как Agda, — это параметрический полиморфизм (дженерики). Здесь очень мало вывода типов — и абсолютно ничего похожего на типы более высокого порядка, классы типов или неявные термины, высокоранговые/непредикативные типы, экзистенциальную квантификацию*, семейства типов, GADT, любой вид зависимой типизации, или любой другой жаргон, который вы захотите упомянуть, и я не ожидаю, что он когда-либо будет.

С одной стороны, нет никакого аппетита к этому. C# предназначен для промышленности, а не для исследований, и подавляющее большинство разработчиков C# — группа практиков, многие из которых ушли из C++ в 2000-х годах — никогда даже не слышали о большинстве концепций, которые я перечислил выше. И дизайнеры не планируют их добавлять: как любит указывать Эрик Липперт, языковая функция не предоставляется бесплатно, когда у вас миллионы пользователей.

Для другого это сложно. В C# центральным элементом является полиморфизм подтипов — простая идея с удивительно глубоким взаимодействием со многими другими функциями системы типов, которые могут вам понадобиться. Вариантность, которую, по моему опыту, понимает меньшинство разработчиков C#, является лишь одним из примеров этого. (На самом деле, общий случай создания подтипов и дженериков с дисперсией — это известно, что они неразрешимы.) Для получения дополнительной информации подумайте о типах более высокого порядка (является ли Monad m вариантом в 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. Тот факт, что vec является Vec<S<N>, T>, недостаточен, чтобы доказать программе проверки типов, что это Cons<N, T>. (Вы не можете доказать это, потому что это неправда; кто-то может создать подкласс Vec в другой сборке.) В более общем смысле нет никакого способа свернуть произвольное Vec, потому что компилятор не может проводить индукцию по натуральным числам. Это раздражает, потому что, несмотря на то, что информация есть на странице, средство проверки типов слишком глупо, чтобы мы могли ее собрать.

Встроить зависимые типы в существующий язык трудно, как выяснили ребята из Haskell. Сложнее, когда язык является императивным объектно-ориентированным языком (о котором обычно трудно доказать теоремы), основанным на подтипах (сложно сочетать с параметрическим полиморфизмом). Еще сложнее, когда никто об этом не просит.

* После написания этого ответа я еще немного подумал по этой теме и понял, что /34623859#34623859">типы более высокого ранга действительно присутствуют и корректны в C#. Это позволяет вам использовать более высокий ранговое кодирование экзистенциальной квантификации.

person Benjamin Hodgson♦    schedule 03.11.2015

Для этого вам понадобятся зависимые типы. Эта функция существует только в нескольких неосновных языках, таких как Idris и Coq.

Учитывая, что вы правильно пометили это, я предполагаю, что вы знаете, что в С# нет этой функции, так что/почему конкретно вы спрашиваете?

person Dax Fohl    schedule 04.07.2015
comment
Я изучил Agda --- более крутой двоюродный брат Coq --- до изучения C#, и сейчас я изучаю C# и задался вопросом, есть ли у него мощные понятия зависимых типов. При всей шумихе вокруг мощи ООП я подумал, что, возможно, ветеран С# сможет имитировать зависимые типы... - person Musa Al-hassy; 05.07.2015

На самом деле это не очень хороший ответ - как я упоминаю в комментарии, я не думаю, что то, о чем вы просите, возможно. Но это демонстрирует то, что, как мне кажется, предлагает пользователь @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;
  }
person RenniePet    schedule 04.07.2015
comment
Спасибо за ваш ответ! Есть ли менее неуклюжий способ избежать всего этого литья типов? Возможно, если бы я объявил enum MyType { Bool , Int, String, Matrix(int n)} и указал, что моя функция возвращает MyType и, возможно, потребуется несколько сантехнических работ здесь и там. Подождите... эта Matrix(int n) часть разрешена? Могут ли перечисления С# иметь такие теги int? Более того, может ли в моем перечислении быть Compounded(MyType t)? Я с нетерпением жду вашего ответа! :-) - person Musa Al-hassy; 05.07.2015