Avoiding using Option.Value
I have a type like this:
type TaskRow =
{
RowIndex : int
TaskId : string
Task : Task option
}
The function returns a list of these records for further processing. Some of the functions that perform this processing are only applicable to items TaskRow
where Task
- Some
. I am wondering what is the best way to do this.
Naive way:
let taskRowsWithTasks = taskRows |> Seq.filter (fun row -> Option.isSome row.Task)
and passing it on to these functions, just assuming it Task
never will None
and using Task.Value
it at the risk of NRE if I don't go to this special list. This is exactly what the current C # code does, but seems pretty uniomatic for F #. I don't have to "accept" things, but let the compiler tell me what will work.
More "functional" would be to match patterns every time it matters, and then do / return nothing (and use choose
or the like) for None
, but this seems repetitive and wasteful since the same work is done multiple times.
Another thought was to introduce a second, slightly different type:
type TaskRowWithTask =
{
RowIndex : int
TaskId : string
Task : Task
}
The original list will then be filtered into "subscriptions" of this type, which will be used where needed. I think that would be fine from a functional point of view, but I'm wondering if there is a nicer, idiomatic way without resorting to a "helper type" like this.
Thanks for any pointers!
source to share
There is quite a bit of information here, knowing that tasks are already filtered out, so using two different types can be helpful. Instead of defining two different types (which is not a big deal in F #), you might also consider defining a type Row
:
type Row<'a> = {
RowIndex : int
TaskId : string
Item : 'a }
This allows you to define the projection as follows:
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
If the original value taskRows
is of type seq<Row<Task option>>
, then the resulting sequence taskRowsWithTasks
is of type seq<Row<Task>>
.
source to share
I agree with you that the more "purely functional" way is to repeat pattern matching, I mean using a function c Seq.choose
that does the filtering instead of storing it in a different structure.
let tasks = Seq.choose (fun {Task = t} -> t) taskRows
The problem is performance, because it will be evaluated many times, but you can use it Seq.cache
, so behind the scenes it is stored in an intermediate structure, while keeping your code more "functional clean".
source to share