Я не совсем уверен, как сделать ваше решение проще - я думаю, что очень причудливое использование типов в вашем подходе делает код довольно сложным. Могут быть и другие способы упростить это, сохраняя при этом какое-то печатание. Точно так же я думаю, что есть случаи, когда логика, которую вам нужно реализовать, достаточно динамична, и тогда, возможно, стоит использовать более динамичные методы, даже в F #.
В качестве примера, вот пример того, как сделать это с помощью библиотеки фреймов данных Deedle . Это позволяет вам представлять данные в виде фреймов данных (с именами столбцов в виде строк).
Записать две необходимые операции очистки для фрейма данных относительно легко - библиотека оптимизирована для операций на основе столбцов, поэтому структура кода немного отличается от вашей (мы вычисляем новый столбец, а затем заменяем его для всех строк во фрейме данных):
let correctAccount idCol nameCol df =
let newNames = df |> Frame.getCol idCol |> Series.map (fun _ id ->
match id with
| 1 -> "Account 1"
| 2 -> "Account 2"
| _ -> failwith "Cannot find account")
df |> Frame.replaceCol nameCol newNames
let otherValue newValue df =
let newOther = df |> Frame.getCol "OtherValue" |> Series.mapAll (fun _ _ -> Some newValue)
df |> Frame.replaceCol "OtherValue" newOther
Затем ваш конвейер может принимать записи, преобразовывать их во фреймы данных и выполнять всю обработку:
[ { Id = 1; Error = None; Name = None } ]
|> Frame.ofRecords
|> correctAccount "Id" "Name"
[ { Id = 1; Error = None; AccountId = 1; AccountName = None; OtherValue = "foo" } ]
|> Frame.ofRecords
|> correctAccount "Id" "AccountName"
|> otherValue "bar"
Это менее безопасно для типов, чем ваш подход, но я считаю, что люди действительно могут прочитать код и получить хорошее представление о том, что он делает, что может стоить компромисса.