如何在F#类型层面实现仅接受Error string的Result列表的函数?
问题描述
给定以下F#代码:
let es = [ Error 1; Error 2; Error 3 ] let mergeErrors<'a> (errors: Result<'a,int> list) : Result<'a,int list> = errors |> List.map (function | Error e -> e) |> Error mergeErrors (es: Result<int,int> list) //=> Error [1; 2; 3]
这段代码在输入包含Ok值时会触发运行时匹配失败异常,请问能否将该错误提前到编译阶段暴露?
上下文场景
脚本中存在Result<'a, string> seq seq类型的数据,需要对每个子Result<'a, string> seq做扁平化处理:
- 若子序列中存在
Error,则丢弃所有Ok值,返回合并了错误信息的Error - 若子序列全为
Ok,则根据'a的结构合并结果
尝试的初步方案:
let filterError (row: Result<'a, string> seq) = row |> Seq.filter (function | Error _ -> true | _ -> false) let mergeErrors inputFromFilterError = // ...
解决方案
一、将运行时错误提前到编译阶段
原mergeErrors函数的问题在于输入类型允许包含Ok值,但函数内部未处理该分支。要在编译阶段阻止传入含Ok的列表,可通过自定义包装类型+安全构造函数实现:
// 定义私有包装类型,仅表示全是Error的集合 type OnlyErrors<'e> = private OnlyErrors of 'e list module OnlyErrors = // 安全构造函数:验证输入是否全为Error,符合则返回Some,否则返回None let create (results: Result<'a,'e> list) = let errors = results |> List.choose (function Error e -> Some e | Ok _ -> None) if errors.Length = results.Length then Some (OnlyErrors errors) else None // mergeErrors仅接受OnlyErrors类型,确保输入必然全是Error let mergeErrors (OnlyErrors errors) = Error errors
使用示例
// 全Error的输入:可正常构造并调用 let pureErrors = [ Error 1; Error 2; Error 3 ] match OnlyErrors.create pureErrors with | Some onlyErrs -> mergeErrors onlyErrs // 输出:Error [1;2;3] | None -> Ok [] // 此处不会触发,仅为编译完整性 // 含Ok的输入:编译时必须处理None分支,避免运行时崩溃 let mixedResults = [ Ok 42; Error 1 ] match OnlyErrors.create mixedResults with | Some onlyErrs -> mergeErrors onlyErrs | None -> Ok [] // 可在此处添加含Ok时的处理逻辑
通过这种方式,编译阶段就会强制你处理输入可能包含Ok的情况,彻底避免原代码的运行时崩溃问题。
二、上下文场景的完整处理方案
针对Result<'a, string> seq seq的扁平化需求,无需先过滤再合并,一次遍历即可完成错误收集与结果合并,效率更高且逻辑更清晰:
// 定义合并Ok结果的函数(需根据'a的实际结构实现) let mergeOks (oks: 'a list) = // 示例:若'a是int则求和,实际需替换为你的业务逻辑 oks |> List.sum // 处理单个Result序列的核心逻辑 let processSingleSeq (seq: Result<'a, string> seq) = let errors = ResizeArray<string>() let oks = ResizeArray<'a>() for res in seq do match res with | Ok x -> oks.Add(x) | Error e -> errors.Add(e) // 根据是否存在错误返回对应结果 if errors.Count > 0 then Error (String.concat "; " errors) // 按需求拼接错误信息 else Ok (mergeOks (oks |> List.ofSeq)) // 处理整个seq seq let processSeqSeq (seqSeq: Result<'a, string> seq seq) = seqSeq |> Seq.map processSingleSeq
使用示例
// 测试数据:包含两个子序列,一个全Ok,一个含Error let testData = seq { yield seq { Ok 1; Ok 2; Ok 3 } // 全Ok,合并后为6 yield seq { Ok 4; Error "invalid value"; Error "missing field" } // 含Error,合并错误信息 } processSeqSeq testData |> Seq.toList // 输出:[Ok 6; Error "invalid value; missing field"]
内容的提问来源于stack exchange,提问作者toraritte
相关产品推荐
相关产品推荐

