Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Inheritance null reference exception using F#

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, ...)

like image 320
Oldrich Svec Avatar asked Jan 13 '23 11:01

Oldrich Svec


2 Answers

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)
like image 115
kwingho Avatar answered Jan 18 '23 15:01

kwingho


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
like image 32
kvb Avatar answered Jan 18 '23 14:01

kvb