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?
.
Мой подход выше ужасен. Я бы разделил функцию, которая оперирует строками, и функцию, которая оперирует целыми числами. Я думаю, что ваш вопрос был скорее академическим, чем реальным, поэтому я надеюсь, что здесь я не слишком затронул тему!
Отказ от ответственности: я новичок, не верьте всему, что я говорю.