代码之家  ›  专栏  ›  技术社区  ›  sdgfsdh

F#中的“合并”歧视工会?

  •  2
  • sdgfsdh  · 技术社区  · 7 年前

    this question ,我在组合不同类型的文件时遇到问题 Result

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

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

    type JsonParseError = 
    | InvalidStructure of string
    
    let parseJson (content : string) : Result<Json, JsonParseError> = 
      // --- 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)
    

    如您所见,统一错误类型相当尴尬。我需要定义一个新的联合类型并包装 Error 正确地站在一边。对于基于异常的方法,您不必担心这一点,因为throw对于类型是开放的。

    当组合不同类型的错误时,样式是否方便?

    2 回复  |  直到 6 年前
        1
  •  3
  •   Just another metaprogrammer    7 年前

    组合错误类型是一个问题 Result 我只是在尝试的时候才意识到。

    type R<'T> = Result<'T, exn>

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

    有点像这样

    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
    

    另一种方法可以是合并 后果 使用失败 Choice

    let bind (t : Result<'T, 'TE>) (uf  'T -> Result<'U, 'UE>) : Result<'U, Choice<'TE, 'TU>> = ...
    

    这可能对你一点帮助都没有,但也许它会产生一些关于如何继续的想法?

        2
  •  3
  •   AMieres    6 年前

    首先简单明了的回答。我稍后会回来再回答。

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

    type AllErrors = 
    | FileNotFound of string
    | InvalidJsonStructure of string
    | OtherErrors ...
    

    printError

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

    从语法上讲,有很多方法可以做到这一点。要避免嵌套 match 你用什么 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
        )
    

    如果你有一个 result

    type Builder() =
        member inline this.Return          x       = Ok  x
        member inline this.ReturnFrom      x       =     (x:Result<_,_>)
        member        this.Bind           (w , r ) = Result.bind  r w
        member inline this.Zero           ()       = Ok ()
    let result = Builder()
    

    那么它看起来是这样的:

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

    操作员:

    let (>>= ) vr f = Result.bind     f vr
    let (|>>.) vr f = Result.mapError f vr
    

    可能是这样的:

    let readJson (path : string) : Result<Json, ReadJsonError> = 
        readFile path     |>>. ReadFileError
        >>= fun content ->
        parseJson content |>>. JsonParseError
    

    let readJson (path : string) : Result<Json, ReadJsonError> = 
        path 
        |>   readFile  
        |>>. ReadFileError
        >>= fun content ->
        content 
        |>   parseJson 
        |>>. JsonParseError
    

    甚至这个:

    let readJson (path : string) : Result<Json, ReadJsonError> = 
        path           |>
        readFile       |>>. 
        ReadFileError  >>= 
        fun content    ->
        content        |>   
        parseJson      |>>. 
        JsonParseError
    

    好的,最后一个只是为了好玩。我不是在提倡你编写这样的代码。

    let readFileU = readFile  >> Result.mapError ReadFileError
    let readJsonU = parseJson >> Result.mapError JsonParseError
    

    并使用Kleisli运算符将其绑定:

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