My code takes a starting position (which must be completed) and a list of updates (each element of which is optional). It then projects future positions, using the appropriate value from each update, if present, or using a formula otherwise.
I'm looking for ways to reduce code duplication, especially as the real data structures will be substantially larger.
In particular:
- Is there a better way for the type system to express the fact that
ResultForDate
is the same structure asInputForDate
but with alloption
values replaced by the underlying types? - Can the duplication be removed from the repetitive code which checks for the existance of an input, uses it if possible and uses a formula otherwise? Perhaps using a monad?
module Demo
open System
type InputForDate = { Date: DateTime; AssetValue: option<float>; InterestRate: option<float> }
type ResultForDate = { Date: DateTime; AssetValue: float; InterestRate: float }
let projection (startingInput: ResultForDate) (futureInputs: InputForDate list) =
let project (previousYear: ResultForDate) (input: InputForDate) : ResultForDate =
{
Date = input.Date;
AssetValue =
match input.AssetValue with
| Some x -> x
| None -> previousYear.AssetValue * (1.0 + previousYear.InterestRate) ;
InterestRate =
match input.InterestRate with
| Some x -> x
| None -> previousYear.InterestRate
}
List.scan project startingInput futureInputs
// example of use
let startingInput = { ResultForDate.Date = new DateTime(2012, 12, 31); AssetValue = 100.0; InterestRate = 0.05 };
let futureInputs = [
{ InputForDate.Date = new DateTime(2013, 12, 31); AssetValue = Some 110.0; InterestRate = Some 0.04 };
{ Date = new DateTime(2014, 12, 31); AssetValue = None; InterestRate = Some 0.03 };
{ Date = new DateTime(2015, 12, 31); AssetValue = None; InterestRate = None } ]
let result = projection startingInput futureInputs
1 Answer 1
The code duplication problem can be solve using Operators.defaultArgs, which does pretty much what you're trying to do
AssetValue =
match input.AssetValue with
| Some x -> x
| None -> previousYear.AssetValue * (1.0 + previousYear.InterestRate) ;
InterestRate =
match input.InterestRate with
| Some x -> x
| None -> previousYear.InterestRate
to
AssetValue = defaultArg input.AssetValue <| previousYear.AssetValue * (1.0 + previousYear.InterestRate)
InterestRate = defaultArg input.InterestRate previousYear.InterestRate
I don't really have an idea for your data type. You both of your type doesn't have the same fields types, so you can't really inherit. And since both of your fields are optional, you can't really compose with another record.