F # Как написать функцию, которая принимает список int или список строк

Я возился с F # и пытался написать функцию, которая может принимать int list или string list. Я написал функцию, которая является логически общей, в которой я не могу изменить ничего, кроме типа аргумента, и она будет работать с обоими типами списка. Но я не могу дать общее определение, чтобы принять оба.

Вот моя функция без аннотации типа:

let contains5 xs =
    List.map int xs
    |> List.contains 5

Когда я пытаюсь аннотировать функцию для составления общего списка, я получаю предупреждение FS0064: the construct causes the code to be less generic than indicated by the type annotations. Теоретически мне не нужно комментировать это, чтобы это было общим, но я все равно попробовал.

Я могу скомпилировать это в двух отдельных файлах, один с

let stringtest = contains5 ["1";"2";"3";"4"]

и еще один с

let inttest = contains5 [1;2;3;4;5]

В каждом из этих файлов компиляция завершается успешно. С другой стороны, я могу отправить определение функции и один из тестов интерпретатору, и вывод типа выполняется просто отлично. Если я пытаюсь скомпилировать или отправить интерпретатору определение функции и оба теста, я получаю error FS0001: This expression was expected to have type string, but here has type int.

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

Ответ 1

Вы сталкиваетесь с ограничениями по стоимости при автоматическом обобщении системы логического вывода типов, как указано здесь

В частности,

Case 4: Adding type parameters.

Решение состоит в том, чтобы сделать вашу функцию универсальной, а не просто сделать ее параметры общими.

let inline contains5< ^T when ^T : (static member op_Explicit: ^T -> int) > (xs : ^T list)  =
    List.map int xs
    |> List.contains 5

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

Ответ 2

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

В FSI интерактивный REPL:

> open System;;
> let inline contains5 xs = List.map int xs |> List.contains 5;;
val inline contains5 :
  xs: ^a list -> bool when  ^a : (static member op_Explicit :  ^a -> int)

> [1;2;3] |> contains5;;
val it : bool = false

> ["1";"2";"5"] |> contains5;;
val it : bool = true

Обратите внимание, что подпись содержит5 имеет общий элемент. Там больше о встроенных функциях здесь.

Ответ 3

На этот вопрос уже правильно ответили выше, поэтому я просто хотел бы рассказать о том, почему, на мой взгляд, хорошо, что F # делает это трудным/заставляет нас терять безопасность типов. Лично я не считаю их логически эквивалентными:

let inline contains5 xs = List.map int xs |> List.contains 5

let stringTest = ["5.00"; "five"; "5"; "-5"; "5,"]
let intTest = [1;2;3;4;5]

contains5 stringTest // OUTPUT: System.FormatException: Input string was not in a correct format.
contains5 intTest // OUTPUT: true

При включении компилятор создает две логически разные версии функции. При выполнении на list<int> мы получаем логический результат. Когда выполняется list<string>, мы получаем логический результат или исключение. Мне нравится, что F # подталкивает меня к признанию этого.

let maybeInt i = 
    match Int32.TryParse i with
    | true,successfullyParsedInteger -> Some successfullyParsedInteger
    | _ -> None

let contains5 xs = 
    match box xs with
    | :? list<int> as ixs -> 
        ixs |> List.contains 5 |> Ok
    | :? list<string> as sxs -> 
        let successList = sxs |> List.map maybeInt |> List.choose id
        Ok (successList |> List.contains 5)
    | _ -> 
        Error "Error - this function expects a list<int> or a list<string> but was passed something else."

let stringTest = ["5.00"; "five"; "5"; "-5"; "5,"]
let intTest = [1;2;3;4;5]

let result1 = contains5 stringTest // OUTPUT: Ok true
let result2 = contains5 intTest // OUTPUT: Ok true

заставляет меня спросить if some of the values in the string list cannot be parsed, should I drop out and fail, or should I just try and look for any match on any successful parse results?.

Мой подход выше ужасен. Я бы разделил функцию, которая оперирует строками, и функцию, которая оперирует целыми числами. Я думаю, что ваш вопрос был скорее академическим, чем реальным, поэтому я надеюсь, что здесь я не слишком затронул тему!

Отказ от ответственности: я новичок, не верьте всему, что я говорю.