let rec nth (list, i) =
match list, i with
| x::xs, 0 -> x
| x::xs, i -> nth(xs, i-1)
| [], _ -> ()
Эта функция действительно будет иметь (нежелательную) подпись, которую вы упомянули:
val nth : unit list * int -> unit
Почему?Посмотрите на правую сторону этих трех ваших правил.Если бы не ()
, вы не могли бы сказать, какой конкретный тип значения возвращает ваша функция.Но как только вы добавите последнее правило, F # увидит выражение ()
(которое имеет тип unit
) и из этого может вывести тип возврата вашей функции;который больше не является общим.Поскольку любая функция может иметь только один фиксированный тип возвращаемого значения, она затем выводит, что x
, xs
также включают тип unit
, что приводит к подписи выше.
Как уже отмечалось в kvb, вы хотитеиногда возвращать значение, и при приведении пустого списка ввода вы не хотите ничего возвращать ... значит, возвращаемое значение должно быть 'a option
(также может быть записано как option<'a>
btw.)
let rec nth (list, i) =
match list, i with
| x::xs, 0 -> Some(x)
| x::xs, i -> nth(xs, i-1) // <-- nth already returns an 'a option,
| [], _ -> None // no need to "wrap" it once more
Теперь указанная подпись выглядит правильно:
val nth : 'a list * int -> 'a option
Относительно вашего второго вопроса , я признаю, что не могу ответить на него полностью, потому что я 'Я все еще новичок F #.Однако один совет: если вы возьмете вышеуказанную функцию в правильной форме (универсальная версия, возвращающая 'a option
), вы на самом деле не сможете не проверить все возможные возвращаемые значения:
Почему?Потому что, если вы хотите получить фактическое возвращаемое значение (названное x
в только что показанном коде), вы должны «извлечь» его, используя блок match
:
let result = nth (someList, someIndex)
match result with
| Some(x) -> ...
| None -> ...
И так как ваши правилавсегда должно быть исчерпывающим (чтобы компилятор не жаловался), вам автоматически придётся добавить правило, которое проверяет возможность None
.
Компилятор на самом деле заставит вам также учитывать, чтодолжно произойти в ситуации «ошибки»;Вы не можете забыть это.Вы можете только выбрать, как с этим справиться!
(Конечно, как только вы занимаетесь программированием на .NET и сталкиваетесь с типами, которые могут быть null
, все может выглядеть немного иначе,поскольку null
не является родным понятием F #.)
Дальнейшее предложение по улучшению: Если вы измените способ, которым ваша nth
функция принимает свои аргументы, выполучить возможность частично применить его, что означает, например, что вы можете использовать его с оператором конвейерной обработки:
let rec nth i list = // <-- swap order of arguments, don't pass them in
... // as a tuple but as two separate arguments
Теперь вы можете сделать это:
someList
|> nth someIndex
Или вот это:
let third = nth 2
someList |> third
Если, с другой стороны, ваша функция принимает только кортеж, это не сработает.Так что подумайте, действительно ли вам нужен параметр кортежа: в этом случае он на самом деле ограничивает гибкость, и, более того, «значение» / содержание двух параметров не предполагает, что они всегда должны храниться и отображаться вместе.Поэтому я бы посоветовал не использовать кортеж в этом случае.