You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 05:22:38