Разрешение перегрузки метода F# не так умно, как C#?

скажи, у меня есть

member this.Test (x: 'a) = printfn "generic"
                           1
member this.Test (x: Object) = printfn "non generic"
                               2

если я назову это на C#

var a = test.Test(3);         // calls generic version
var b = test.Test((object)3); // calls non generic version
var c = test.Test<object>(3); // calls generic version

однако, в F#

let d = test.Test(3);  // calls non generic version
let e = test.Test<int>(3); // calls generic version

поэтому я должен добавить аннотацию типа, чтобы получить правильный перегруженный метод. Это правда? Если да, то почему F# не разрешает автоматически правильно, учитывая, что тип аргумента уже выведен? (в любом случае, каков порядок разрешения перегрузки F#? всегда пользу Object чем его наследуемых классах?)

это немного опасно, если метод имеет обе перегрузки, одна из них принимает аргумент как Object type и другой является общим, и оба возвращают один и тот же тип. (как в этом примере, или Assert.AreEqual в модульном тестировании), так как тогда очень вероятно, что мы получим неправильную перегрузку без уведомления (не будет никакой ошибки компилятора). Разве это не проблема?

обновление:

может кто-нибудь объяснить

  • почему F# разрешает Assert.AreEqual(3, 5) as Assert.AreEqual(Object a, Object b) но не Assert.AreEqual<T>(T a, T b)

  • но f# решает Array.BinarySearch([|2;3|], 2) as BinarySearch<T>(T[]array, T value) а не BinarySearch(Array array, Object value)

1 ответов


разрешение перегрузки метода F# не так умно, как C#?

Я не думаю, что это правда. Перегрузка метода делает вывод типа намного более сложным. F# имеет разумные компромиссы, чтобы сделать перегрузку метода полезной и вывод типа настолько мощным, насколько это должно быть.

при передаче значения функции / метода компилятор F# автоматически передает его соответствующему типу. Это удобно во многих ситуациях, но и сбивает с толку иногда.

в вашем примере 3 это upcasted к obj тип. Оба метода применимы, но проще (не общий) метод выбран.

Раздел 14.4 Разрешение Применения Метода в спецификации достаточно четко указаны правила перегрузки:

1) предпочитают кандидатов, использование которых не сдерживают использование введенная пользователем аннотация универсального типа должна быть равна другому типу.

2) предпочитают кандидатов, которые не используют преобразование ParamArray. Если два кандидаты используют преобразование ParamArray с типами pty1 и pty2, и pty1 реально подводит pty2, предпочитает второе; то есть использует кандидат, который имеет более точный тип.

3) предпочитают кандидатов, которые не имеют ImplicitlyReturnedFormalArgs.

4) предпочитают кандидатов, которые не имеют ImplicitlySuppliedFormalArgs.

5) Если два кандидаты имеют неназванные фактические типы аргументов ty11...ty1n и ty21...ty2n, и каждый ty1i либо

a. возможно, подменяет ty2i, или

b. ty2i-это система.Тип Func и ty1i-это другой делегат типа, затем предпочтение второму кандидату. То есть, предпочтите любого кандидата, который имеет более конкретные фактические типы аргументов, и рассмотрим любую систему.Тип Func должен быть более конкретным, чем любой другой тип делегата.

6) предпочтение кандидатам которые не являются членами расширения кандидаты, которые есть.

7) чтобы выбрать между двумя членами расширения, предпочтите тот, который результаты последнего использования open.

8) предпочитайте кандидатов, которые не являются общими по сравнению с кандидатами, которые generic-то есть предпочитают кандидатов, которые имеют пустые ActualArgTypes.

Я думаю, что ответственность пользователей за создание однозначных перегруженных методов. Вы всегда можете посмотреть на выведенные типы чтобы посмотреть, правильно ли вы их делаете. Например, модифицированная версия вашего без двусмысленности:

type T() =
    member this.Test (x: 'a) = printfn "generic"; 1
    member this.Test (x: System.ValueType) = printfn "non-generic"; 2

let t = T()
let d = t.Test(3)  // calls non-generic version
let e = t.Test(test) // call generic version

обновление:

доходит основную концепцию ковариации. F# не поддерживает ковариантность массивов, списков, функций и т. д. Как правило, хорошо обеспечить безопасность типов (см. ).

, поэтому легко объяснить, почему Array.BinarySearch([|2;3|], 2) разрешить BinarySearch<T>(T[] array, T value). Вот другой пример аргументов функции, где

T.Test((fun () -> 2), 2)

разрешить

T.Test(f: unit -> 'a, v: 'a)

а не

T.Test(f: unit -> obj, v: obj)