Error handling &引用;“合并”;F#中的歧视工会?

Error handling &引用;“合并”;F#中的歧视工会?,error-handling,f#,Error Handling,F#,从开始,我在将不同类型的Result类型组合在一起时遇到了一个问题 (下面是一个人为的例子,不是真实的代码) 假设我有一个读取文件的函数: type ReadFileError = | FileNotFound of string let readFile (path : string) : Result<string, ReadFileError> = // --- 8< --- type ReadJsonError = | ReadFileError of R

从开始,我在将不同类型的
Result
类型组合在一起时遇到了一个问题

(下面是一个人为的例子,不是真实的代码)

假设我有一个读取文件的函数:

type ReadFileError = 
| FileNotFound of string

let readFile (path : string) : Result<string, ReadFileError> =
  // --- 8< --- 
type ReadJsonError = 
| ReadFileError of ReadFileError
| JsonParseError of JsonParseError

let readJson (path : string) : Result<Json, ReadJsonError> = 
  match path |> readFile with
  | Ok content -> 
    match content |> parseJson with 
    | Ok json -> Ok json
    | Error e -> Error (ReadJsonError.JsonParseError e)
  | Error e -> Error (ReadJsonError.ReadFileError e)
type ReadFileError=
|未找到字符串的FileNotFound
let readFile(路径:字符串):结果=
// --- 8< --- 
以及一个以某种方式解析它的函数:

type JsonParseError = 
| InvalidStructure of string

let parseJson (content : string) : Result<Json, JsonParseError> = 
  // --- 8< --- 
类型JsonParseError=
|字符串的无效结构
let parseJson(内容:字符串):结果=
// --- 8< --- 
现在,我可以将它们结合起来,创建一个读取和解析文件的函数:

type ReadFileError = 
| FileNotFound of string

let readFile (path : string) : Result<string, ReadFileError> =
  // --- 8< --- 
type ReadJsonError = 
| ReadFileError of ReadFileError
| JsonParseError of JsonParseError

let readJson (path : string) : Result<Json, ReadJsonError> = 
  match path |> readFile with
  | Ok content -> 
    match content |> parseJson with 
    | Ok json -> Ok json
    | Error e -> Error (ReadJsonError.JsonParseError e)
  | Error e -> Error (ReadJsonError.ReadFileError e)
type ReadJsonError=
|ReadFileError的ReadFileError
|JsonParseError的JsonParseError
让readJson(路径:字符串):结果=
将路径|>readFile与
|Ok内容->
将内容|>parseJson与
|Ok json->Ok json
|错误e->Error(ReadJsonError.JsonParseError e)
|错误e->错误(ReadJsonError.ReadFileError e)
如您所见,统一错误类型相当尴尬。我需要定义一个新的联合类型并正确地包装
错误
端。对于基于异常的方法,您不必担心这一点,因为throw对于类型是开放的


当组合不同类型的错误时,是否可以方便地使用
结果
样式

先回答简短易懂的问题。我稍后会回来再回答

如果您正在构建单片应用程序,建议只为整个应用程序创建一种错误类型:

type AllErrors = 
| FileNotFound of string
| InvalidJsonStructure of string
| OtherErrors ...
这将为您提供一个定义所有错误的好地方,您可以创建一个统一的
printError
和其他错误处理函数

有时这是不可能的,例如,如果您的代码是模块化的,并且每个模块都有自己的ErrorType,那么您有两个选项,仍然创建一个唯一的类型并映射到它,或者像您那样创建一个嵌套的、组合的类型。这是你的决定。在这两种情况下,都使用
Result.maperor

let readJson (path : string) : Result<Json, ReadJsonError> = 
    readFile path 
    |> Result.mapError ReadFileError
    |> Result.bind (fun content ->
    parseJson content 
    |> Result.mapError JsonParseError
    )
从语法上讲,有很多方法可以做到这一点。要避免嵌套的
匹配
s,请使用
Result.bind
Result.maperor

let readJson (path : string) : Result<Json, ReadJsonError> = 
    readFile path 
    |> Result.mapError ReadFileError
    |> Result.bind (fun content ->
    parseJson content 
    |> Result.mapError JsonParseError
    )
那么它看起来是这样的:

let readJson (path : string) : Result<Json, ReadJsonError> = result {
    let! content = readFile  path    |> Result.mapError ReadFileError
    return!        parseJson content |> Result.mapError JsonParseError
}
可能是这样的:

let readJson (path : string) : Result<Json, ReadJsonError> = 
    readFile path     |>>. ReadFileError
    >>= fun content ->
    parseJson content |>>. JsonParseError
并使用Kleisli运算符将其绑定:

let (>=>) f g p = f p |> Result.bind g

let readJson = readFileU >=> readJsonU

组合错误类型是
Result
的一个问题,我只有在尝试时才意识到这一点

对于异常,这是通过让所有异常继承一个基类来“解决”的。因此,一种类似的方法可以是
类型R

然而,我发现这很不吸引人,而且通常会陷入一种模式,在这种模式中,我定义了自己的结果类型,允许同类类型的聚合失败

有点像这样

type BadResult = Message of string | Exception of exn
type BadTree = Leaf of BadResult | Fork of BadTree*BadTree
type R<'T> = Good of 'T | Bad of BadTree
这可能对你一点帮助都没有,但也许它会产生一些关于如何继续的想法

let (>=>) f g p = f p |> Result.bind g

let readJson = readFileU >=> readJsonU
type BadResult = Message of string | Exception of exn
type BadTree = Leaf of BadResult | Fork of BadTree*BadTree
type R<'T> = Good of 'T | Bad of BadTree
let bind (t : Result<'T, 'TE>) (uf  'T -> Result<'U, 'UE>) : Result<'U, Choice<'TE, 'TU>> = ...