Franco Tiveron
Franco Tiveron

Reputation: 2928

F# inheritance with multiple constructors syntax

I have this F# class

module File1

open System
open System.Collections.Generic

type TimeRangeList<'e>(getter: DateTime * DateTime -> List<'e>, ?maybe_tFrom: DateTime, ?maybe_tTo: DateTime) as this = 
    inherit List<'e>()
    //inherit List<'e>(getter(defaultArg maybe_tTo DateTime.Now, defaultArg maybe_tFrom ((defaultArg maybe_tTo DateTime.Now).AddDays(-1.0))))

    let tTo = defaultArg maybe_tTo DateTime.Now
    let tFrom = defaultArg maybe_tFrom (tTo.AddDays(-1.0))
    do this.AddRange(getter(tFrom, tTo))

now I want to add constructors and use the syntax as in here

type TimeRangeList<'e> = 
    inherit List<'e>
    val tFrom: DateTime
    val tTo: DateTime
    new (getter: DateTime * DateTime -> List<'e>, ?maybe_tFrom: DateTime, ?maybe_tTo: DateTime) = {
            inherit List<'e>()
            //inherit List<'e>(defaultArg maybe_tFrom ((defaultArg maybe_tTo DateTime.Now).AddDays(-1.0)), getter(defaultArg maybe_tTo DateTime.Now))

            tTo = defaultArg maybe_tTo DateTime.Now
            tFrom = defaultArg maybe_tFrom (tTo.AddDays(-1.0)) //tTo undefined
            //tFrom = defaultArg maybe_tFrom ((defaultArg maybe_tTo DateTime.Now).AddDays(-1.0))
        }
    do this.AddRange(getter(tFrom, tTo)) //primary constructor required

this code gives two errors:

  1. in 'tFrom=...' it says 'tTo not defined' while tTo is clearly in scope; as a workaround I can repeat the defaultArg call as shown in the following (commented) line. Is there a better way?
  2. in the last line where 'AddRange' is called, it complains that do calls can be executed only in primary constructors, which is fair. But, how do I call the necessary AddRange to initialize the list? I have tried different options but couldn't find the way. A workaround is shown in the commented inherit line, but in the end I am calling defaultArg repeatedly and redundantly; there must be a clearer and more elegant way

Upvotes: 3

Views: 340

Answers (2)

rmunn
rmunn

Reputation: 36718

This is the syntax you're looking for:

module File1

open System
open System.Collections.Generic

type TimeRangeList<'e> = 
    inherit List<'e>
    val tFrom: DateTime
    val tTo: DateTime
    new (getter: DateTime * DateTime -> List<'e>, ?maybe_tFrom: DateTime, ?maybe_tTo: DateTime) as this =
        let to_ = defaultArg maybe_tTo DateTime.Now
        let from_ = defaultArg maybe_tFrom (to_.AddDays(-1.0))
        {
            inherit List<'e>()

            tTo = to_
            tFrom = from_
        }
        then
            this.AddRange(getter(this.tFrom, this.tTo))

Documentation links:

To explain a little bit, the { field = value; field2 = value2 } syntax doesn't have to be the only expression found in the new() block that defines a secondary constructor. It just has to be the last expression, that is, the expression that is returned. (Here, even though technically the then block is the "last" block in the constructor, its return value (which is required to be unit) is ignored and the actual return value of the constructor is the last expression not found in a then block). Therefore, it's safe to use let expressions earlier to define the values you want to put into your class's fields, and those let expressions can reference each other just as they would in normal code. So if you have a complicated or expensive calculation that you need to put into several fields, you could so something like:

new () =
    let result = expensiveCalculationIWantToDoOnlyOnce()
    { field1 = result; field2 = result + 1; field3 = result + 2 }

Upvotes: 6

user9895453
user9895453

Reputation:

To fix the first problem you should give a name to the current object: new (...) as this = and then access your variable with it this.tTo.AddDays(-1.0).

I don't have a solution yet for the second issue.

Upvotes: 1

Related Questions