Что означает предупреждение «Эта конструкция приводит к тому, что код будет менее обобщенным c, чем указано в примечаниях типа», в F # - PullRequest
2 голосов
/ 27 февраля 2020

У меня есть код, генерирующий это предупреждение.

Есть несколько сообщений, связанных с SO, наиболее близким является этот: Эта конструкция делает код менее обобщенным c, чем указано в аннотациях типов

но я не вижу, где это применимо, потому что мой код уже находится в функции

, поэтому рассматриваемый код очень прост:

let exists (key: 'a) =
    r.Exists(string key)

let set (key: 'a) value =
    r.Set((string key), value)

let get (key: 'a) =
    r.Get(string key)

let setDefault (key: 'a) value =
    if not (exists key) then
        set key value

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

, но когда я использую это код с перечислением, я получаю предупреждение в заголовке.

Итак, у меня есть два вопроса:

  • Я не совсем понимаю предупреждение. Кто-нибудь может мне это объяснить?
  • Как мне добиться передачи различных перечислений или строк и сделать из них строковый ключ?

Ответы [ 2 ]

2 голосов
/ 27 февраля 2020

Вы можете воспроизвести это предупреждение в очень простом тестовом примере, убрав часть шума из вашего примера:

let f (x: 'a) =
    string x

Глядя на это, вы можете быть смущены, потому что тип string функция 'T -> string, но это не так просто. Чтобы понять, что происходит, вы должны взглянуть на реализацию функции string в FSharp.Core:

    let inline anyToString nullStr x = 
        match box x with 
        | null -> nullStr
        | :? System.IFormattable as f -> f.ToString(null,System.Globalization.CultureInfo.InvariantCulture)
        | obj ->  obj.ToString()


    [<CompiledName("ToString")>]
    let inline string (value: ^T) = 
         anyToString "" value
         // since we have static optimization conditionals for ints below, we need to special-case Enums.
         // This way we'll print their symbolic value, as opposed to their integral one (Eg., "A", rather than "1")
         when ^T struct = anyToString "" value
         when ^T : float      = (# "" value : float      #).ToString("g",CultureInfo.InvariantCulture)
         when ^T : float32    = (# "" value : float32    #).ToString("g",CultureInfo.InvariantCulture)
         when ^T : int64      = (# "" value : int64      #).ToString("g",CultureInfo.InvariantCulture)
         when ^T : int32      = (# "" value : int32      #).ToString("g",CultureInfo.InvariantCulture)
         when ^T : int16      = (# "" value : int16      #).ToString("g",CultureInfo.InvariantCulture)
         when ^T : nativeint  = (# "" value : nativeint  #).ToString()
         when ^T : sbyte      = (# "" value : sbyte      #).ToString("g",CultureInfo.InvariantCulture)
         when ^T : uint64     = (# "" value : uint64     #).ToString("g",CultureInfo.InvariantCulture)
         when ^T : uint32     = (# "" value : uint32     #).ToString("g",CultureInfo.InvariantCulture)
         when ^T : int16      = (# "" value : int16      #).ToString("g",CultureInfo.InvariantCulture)
         when ^T : unativeint = (# "" value : unativeint #).ToString()
         when ^T : byte       = (# "" value : byte       #).ToString("g",CultureInfo.InvariantCulture)

Это использует статически разрешенные параметры типа и использует явную реализацию для каждого из перечисленных типы, так что на самом деле это не так обобщенно c, как кажется из сигнатуры типа. В вашем случае происходит вывод, что он выводит наиболее совместимый тип, а поскольку ваша функция просто набрана как 'a, она выбирает obj. Итак, потому что вы вызываете string, и ваш входной параметр принудительно относится к одному из типов, которые фактически обрабатывает функция string (что на самом деле в anyToString), и это obj.

Чтобы заставить его работать в вашем реальном сценарии, на самом деле довольно просто: просто сделайте ваши функции встроенными и вообще не указывайте тип параметра:

let inline exists key =
    r.Exists(string key)

Это будет выведите тип для параметра и вызовите правильную версию string, и это будет работать практически со всем, что вы хотите передать, включая ваши перечисления.

2 голосов
/ 27 февраля 2020

Проблема связана с использованием string. Если вы посмотрите на источник для строки , вы увидите, что это inline, что означает, что компилятору нужно определить тип во время компиляции . В вашем случае это означает, что тип generi c должен быть разрешен до вызова string, что, в свою очередь, заставляет компилятор выбрать что-то, что будет работать - в данном случае obj.

Есть два простых способа обойти это - во-первых, вы можете сделать ваши функции встроенными, что позволяет компилятору откладывать это до тех пор, пока не будет использована функция, использующая вашу строку. Это будет работать во многих случаях, но также может потенциально «отключить это» для потребителя вашего API позже, если он сделает то же самое, что и вы. Это будет выглядеть так:

let inline set (key: 'a) value =
    r.Set((string key), value)

Другой вариант - избегать оператора string и использовать тот факт, что все объекты в. NET включают ToString, и вызывать его вместо:

let set (key: 'a) value =
    r.Set(key.ToString()), value)

Любой подход позволит избежать предупреждений и сохранить функции обобщенными c.

...