Reputation: 4231
Is there any way to make the following code work?
open System.Collections.Generic
type Geometry<'t>(child: 't) =
let values = List()
member o.add (v: float) = values.Add v; child
and Line() as self =
inherit Geometry<Line>(self)
member o.length v = o.add v
let line = Line().length(50.0)
I get
System.NullReferenceException: Object reference not set to an instance of an object.
EDIT:
It is enough to call the following to trigger the exception.
let line = Line()
The motivation is that you can do e.g.:
let line = Line().x1(10).y1(20).x2(30).y2(10).color("blue") // ...
and you can reuse common members among all geometries (circle, ellipse, ...)
Upvotes: 4
Views: 277
Reputation: 55184
In .NET (and by extension F#) you need to initialize an object before you can call any method on it or pass it to another method. Thus, your Line
object would need to be initialized before it is passed to the base constructor Geometry<Line>(self)
. But it can't be initialized before the base constructor is called, so there's no direct way to do what you want.
Having said that, F# has a system that is supposed to catch illegal recursive uses of values before they're defined, so I'm surprised you don't get a more meaningful exception instead of a NullReferenceException
(or better yet, a compile-time error). Compare, for example, what happens if you try to call new Rec()
with the following definition:
type Rec(r:Rec) =
new() as self = Rec(self)
One way around this problem is should be use laziness to delay the use of the self
identifier:
type Geometry<'t>(child: Lazy<'t>) =
let values = List()
member o.add (v: float) = values.Add v; child.Value
and Line() as self =
inherit Geometry<Line>(lazy self)
member o.length v = o.add v
Sadly, this doesn't work, lending further credence to the theory that there's an F# bug with checking the initialization soundness in this case. Fortunately, this can be worked around by using an explicit constructor for Line
instead of a primary constructor:
type Geometry<'t>(child: Lazy<'t>) =
let values = List()
member o.add (v: float) = values.Add v; child.Value
and Line =
inherit Geometry<Line>
new() as self = { inherit Geometry<Line>(lazy self) }
member o.length v = o.add v
Upvotes: 3
Reputation: 11525
One thing to note about F# -- one of it's major features is that it's very concise (especially compared to C# or Java). In my experience, though, it's also easy to get carried away with trying to make your code as concise as possible; instead, you're better off aiming to write code which is as concise as practical.
The reason I bring this up is because of your use of the semicolon to put multiple statements on one line -- it makes the code shorter by saving one or two newlines, but the downside is that it doesn't play nicely with the VS debugger.
If you remove your uses of the semicolon, you should be able to place breakpoints on the lines in question, then step through them with the VS debugger (or MonoDevelop / Xamarin Studio). Knowing specifically which statement is causing the exception to be thrown makes it much easier to determine the cause of the problem.
If you try this and are able to identify the line in question, please add that information to your question and we'll be able to give you a better answer.
type Geometry<'t> (child: 't) =
let values = ResizeArray ()
member o.add (v: float) =
values.Add v
child
and Line () as self =
inherit Geometry<Line> (self)
member o.length v =
o.add v
let line =
let l = Line ()
l.length 50.0
Upvotes: 1
Reputation: 422
open System.Collections.Generic
type Geometry<'t when 't :> Geometry<'t>>() =
let values = List()
member o.add (v: float) = values.Add v; o :?> 't
and Line() =
inherit Geometry<Line>()
member o.length v = o.add v
let line = Line().length(50.0)
Upvotes: 5