Как заставить 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# чистый и бесшумный?

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

2 ответа

Решение

Вывод типа и подтип не очень хорошо сочетаются друг с другом, так как ссылки Карстена в некоторой степени обсуждаются. Похоже, вы недовольны подходом 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, вы можете попробовать :> _, который будет работать до тех пор, пока есть что-то, что ограничивает тип (либо аннотация на общий результат, либо конкретное приведение к одной из ветвей).

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

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

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

И вот еще одна отличная дискуссия по этому вопросу.

Другие вопросы по тегам