Как сделать F # вывести общий базовый тип?

Рассмотрим это:

module Module1 =
    type A() = class end
    type B() = inherit A()
    type C() = inherit A()

    let f x = if x > 0 then new B() else new C()

Последняя строка дает ошибку о ожидаемом типе B, но вместо этого выбирается тип C. Хорошо, я могу притвориться, что понимаю это: компилятор не знает, какую общую базу следует делать, если их много.

Но угадайте, что? Даже когда я указываю тип функции, он все равно не работает:

    let f x : A = if x > 0 then new B() else new C()

Теперь это дает мне две ошибки: "Ожидаемый, B найден" и "Ожидаемый, C найден". WTF? Почему он не видит, что оба B и C неявно конвертируются в A?

Да, я знаю, что я мог бы использовать upcast, например:

    let f x : A = if x > 0 then upcast new B() else upcast new C()

Но угадайте, что (снова)? upcast работает только при наличии явного объявления типа функции! Другими словами, это:

    let f x = if x > 0 then upcast new B() else upcast new C()

все еще дает ошибку.

WTF?! Мне действительно нужно добавить 50% шума в мою программу, чтобы помочь компилятору? Что со всей этой шумихой о том, что код F # чист и бесшумен?

Как-то кажется, что это не может быть правдой. Так что вопрос: я что-то упустил? Как сделать это как компактным, так и работающим?

Ответ 1

Тип вывода и подтипирование не очень хорошо сочетаются, так как ссылки Carsten обсуждают в некоторой степени. Похоже, вы недовольны подходом F # и предпочтете, чтобы

if b then 
    e1 
else 
    e2

были неявно обработаны более как

if b then (e1 :> 'a) else (e2 :> 'a)

когда компилятор дополнительно выводит 'a как наименьшую верхнюю границу иерархии типов на основе типов, которые в противном случае были бы определены для e1 и e2.

Возможно, это будет технически возможно, и я не могу окончательно говорить о том, почему F # не работает таким образом, но здесь есть предположение: если бы инструкции if вели себя таким образом, то никогда не было бы ошибкой имеют разные типы в ветвях if и else, поскольку они всегда могут быть унифицированы путем неявного повышения их до obj. Однако на практике это почти всегда ошибка программиста - вы почти всегда хотите, чтобы типы были одинаковыми (например, если я возвращаю символ из одной ветки и строку из другой, я, вероятно, должен был возвращать строки из обоих, а не obj). Неявным повышением уровня, вы просто затруднили бы обнаружение этих ошибок.

Кроме того, в F # относительно редко встречается сложная иерархия наследования, за исключением, возможно, при взаимодействии с другим кодом .NET. В результате это на практике очень незначительное ограничение. Если вы ищете синтаксически более короткое решение, чем upcast, вы можете попробовать :> _, который будет работать до тех пор, пока есть что-то, чтобы ограничить тип (либо аннотацию на общий результат, либо конкретный отбор на одном ветвей).

Ответ 2

есть причина для всего этого, но сделать его коротким: F # более сильная, чем С#, поэтому вам нужно сказать, куда бросить (см. здесь):

let f x = if x > 0 then (new B() :> A) else (new C() :> A)

Здесь вы можете найти дополнительную информацию: F # требуется для трансляции

И вот еще один большой обсуждение.