Есть ли аналогичный tryFind для нового типа Result в F# 4.1?
Язык F# содержит тип "Дискриминационный союз" option<'T>
, Несколько модулей содержат полезные функции XYZ.tryFind
чье возвращаемое значение является объектом типа option<'T>
, (Примеры: List.tryFind
, Map.tryFind
, Array.tryFind
). F# 4.1
добавил тип Result<'S,'T>
который аналогичен option<'T>
но предоставляет больше информации. Есть ли функции, аналогичные tryFind
для Result<'S,'T>
тип?
Приведенный ниже код является попыткой создания такой функции.
let resultFind (ef: 'K -> 'T) (tryfind: 'K -> 'M -> 'T option) (m: 'M) (k: 'K) =
let y = tryfind k m
match y with
| Some i -> Result.Ok i
| None -> Result.Error (ef k)
let fields = [("Email", "jdoe@xyz.com"); ("Name", "John Doe")]
let myMap = fields |> Map.ofList
let ef k = sprintf "%s %A" "Map.tryFind called on myMap with bad argument " k
let rF = resultFind ef Map.tryFind myMap // analogous to tryFind
rF "Name"
rF "Whatever"
val resultFind :
ef:('K -> 'T) ->
tryfind:('K -> 'M -> 'T option) -> m:'M -> k:'K -> Result<'T,'T>
val fields : (string * string) list =
[("Email", "jdoe@xyz.com"); ("Name", "John Doe")]
val myMap : Map<string,string> =
map [("Email", "jdoe@xyz.com"); ("Name", "John Doe")]
val ef : k:'a -> string
val rF : (string -> Result<string,string>)
[<Struct>]
val it : Result<string,string> = Ok "John Doe"
[<Struct>]
val it : Result<string,string> =
Error "Map.tryFind called on myMap with bad argument "Whatever""
Кроме того, почему [<Struct>]
объявление появляется над Result
объекты?
1 ответ
Стандартная библиотека не имеет этих функций, и не должна. С функцией как tryFind
Существует только одна вещь, которая может пойти не так: значение не может быть найдено. Следовательно, действительно нет необходимости в полном представлении случая ошибки. Достаточно простого сигнала "да / нет".
Но это законный вариант использования, когда в известном контексте вам нужно "пометить" сбой конкретной информацией об ошибке, чтобы вы могли передать ее своему потребителю более высокого уровня.
Однако для этого случая использования было бы расточительно и многократно изобретать обертку для каждой функции: эти обертки были бы полностью идентичны, за исключением функции, которую они вызывают. Ваша попытка идет в правильном направлении, превращая вашу функцию в функцию более высокого порядка, но она не идет достаточно далеко: даже если вы принимаете функцию в качестве параметра, вы "запекаете" форму этой функции. Когда вам нужно работать с функцией, скажем, двух аргументов, вам придется скопировать и вставить обертку. В конечном итоге это происходит из-за того, что ваша функция заботится о двух аспектах - вызове функции и преобразовании результата. Вы не можете использовать одно без другого.
Попробуем еще больше расширить подход: разбить проблему на более мелкие части, а затем объединить их вместе, чтобы получить полное решение.
Во-первых, давайте просто придумаем способ "преобразовать" Option
значение в Result
один. Очевидно, нам нужно указать значение ошибки:
module Result =
let ofOption (err: 'E) (v: 'T option) =
match v with
| Some x -> Ok x
| None -> Error err
Теперь мы можем использовать это для преобразования любого Option
в Result
:
let r = someMap |> Map.tryFind k |>
Result.ofOption (sprintf "Key %A couldn't be found" k)
Все идет нормально. Но следующая вещь, на которую следует обратить внимание, это то, что значение ошибки не всегда необходимо, поэтому было бы расточительно вычислять его каждый раз. Давайте сделаем это вычисление отложенным:
module Result =
let ofOptionWith (err: unit -> 'E) (v: 'T option) =
match v with
| Some x -> Ok x
| None -> Error (err())
let ofOption (err: 'E) = ofOptionWith (fun() -> err)
Теперь мы все еще можем использовать ofOption
когда значение ошибки дешево вычислить, но мы также можем отложить вычисление, используя ofOptionWith
:
let r = someMap |> Map.tryFind k
|> Result.ofOptionWith (fun() -> sprintf "Key %A couldn't be found" k)
Далее, мы можем использовать эту функцию преобразования для создания оберток вокруг функций, которые возвращают Option
чтобы заставить их вернуться Result
:
module Result =
...
let mapOptionWith (err: 'a -> 'E) (f: 'a -> 'T option) a =
f a |> ofOptionWith (fun() -> err a)
Теперь мы можем определить ваш rF
функционировать с точки зрения Result.mapOptionWith
:
let rF = Result.mapOptionWith
(sprintf "Map.tryFind called on myMap with bad argument %s")
(fun k -> Map.tryFind k myMap)