避免使用Option.Value

时间:2014-08-23 15:44:31

标签: f# idiomatic

我有这样的类型:

type TaskRow =
    {
        RowIndex : int
        TaskId : string
        Task : Task option
    }

函数返回要进一步处理的这些记录的列表。执行该处理的某些功能仅与TaskRowTask的{​​{1}}项相关。我想知道最好的方法是什么。

天真的方式就是

Some

并将其传递给这些函数,只需假设let taskRowsWithTasks = taskRows |> Seq.filter (fun row -> Option.isSome row.Task) 永远不会Task并使用None,如果我没有传入一个特殊列表,则冒险尝试NRE。这正是当前C#代码所做的,但对于F#而言似乎相当单一。我不应该“假设”事情,而是让编译器告诉我什么会起作用。

更多'功能'将是每次值相关时进行模式匹配,然后对Task.Value执行/返回任何内容(并使用choose等),但这似乎是重复和浪费的同样的工作会多次完成。

另一个想法是引入第二种略有不同的类型:

None

然后将原始列表过滤到这种类型的“子列表”中,以便在适当的时候使用。我想从功能的角度来看这是可以的,但我想知道是否有一种更好的,惯用的方式而不诉诸这种“助手类型”。

感谢您的任何指示!

2 个答案:

答案 0 :(得分:5)

知道任务已经过滤,有很多价值,因此有两种不同类型可能会有所帮助。你可以考虑定义一个通用的Row类型,而不是定义两种不同的类型(在F#中,这不是那么大的交易):

type Row<'a> = {
    RowIndex : int
    TaskId : string
    Item : 'a }

这使您可以定义这样的投影:

let project = function
    | { RowIndex = ridx; TaskId = tid; Item = Some t } ->
        Some { RowIndex = ridx; TaskId = tid; Item = t }
    | _ -> None

let taskRowsWithTasks =
    taskRows
    |> Seq.map project
    |> Seq.choose id

如果初始taskRows值的类型为seq<Row<Task option>>,则生成的taskRowsWithTasks序列的类型为seq<Row<Task>>

答案 1 :(得分:3)

我同意你的观点,更多“纯粹的功能”方式是重复模式匹配,我的意思是使用Seq.choose进行过滤的函数,而不是将其保存到另一个结构。

let tasks = Seq.choose (fun {Task = t} -> t) taskRows

问题在于性能,因为它会多次计算,但您可以使用Seq.cache,因此在幕后将其保存到中间结构中,同时保持代码更“纯粹功能”。