3
\$\begingroup\$

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:

  1. Is there a better way for the type system to express the fact that ResultForDate is the same structure as InputForDate but with all option values replaced by the underlying types?
  2. 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
\$\endgroup\$
0

1 Answer 1

2
\$\begingroup\$

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.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.