3

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
asked Jun 29, 2018 at 3:57
4
  • 1
    Actually, the biggest problem in your code example is that your type definition is not a class definition because you left out the parentheses. You should have written type TimeRangeList<'e>() =. This won't fix your code but you will have more meaningful errors. Commented Jun 29, 2018 at 4:54
  • @ThomasCorbière I am aware of this but this is what the official documentation does for multiple constructors (follow the link 'here' between the two code excerpts). Using the usual default constructor syntax forces all the additional constructors to call the primary constructor which in turn calls one specific base constructor. If we want different derived constructors calling different base constructors, the syntax to be used is the one without primary constructor. Commented Jun 29, 2018 at 5:18
  • Oh, sorry, I missed that! I found the solution for the first issue, see my answer below. Commented Jun 29, 2018 at 6:01
  • "... your type definition is not a class definition because you left out the parentheses." That's not right. Static classes don't have constructors/parenthesis. Commented Jun 30, 2018 at 21:52

2 Answers 2

6

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 }
answered Jun 29, 2018 at 6:00
2
  • Thanks for the precious information. A bit more details in the official documentation wouldn't hurt though, imho Commented Jun 30, 2018 at 0:37
  • @FrancoTiveron - Agreed, the documentation could be better. If you scroll down to the bottom of the documentation, though, you can submit a PR through Github to improve the docs. Yes, Microsoft has gone open-source with their documentation! So now, when you discover something that's poorly documented, you at least have the ability to do something about it. You may not have the time (I don't at the moment), but I'm happy that the option to improve the docs is actually available. Commented Jun 30, 2018 at 7:51
1

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.

answered Jun 29, 2018 at 5:59

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.