Как заставить 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# нужно для приведения
И вот еще одна отличная дискуссия по этому вопросу.