I have a base class
type MyBase() =
let param = myfun()
member this.Param = param
type MyInherited() =
inherit MyBase()
do
base.Param.DoSomething() // cannot access base in let or do binding.
I would like to call DoSomething
exact once during the inherited object instantiation. But here I am not allowed. What shall I do then?
Do I have to create a method
member this.DoSomething() = base.Param.DoSomething()
and call it in the constructor?
type MyInherited() as self =
inherit MyBase()
do
self.DoSomething()
It feels a bit weird, and duplicated
My initial simplified example is not appropriate. Check the followings:
type MyBase() =
let param = "test"
member this.Param = param
type MyInherited() =
inherit MyBase()
do
(fun () -> base.Param) () |> ignore // Doesn't work,
// A protected member is called or 'base' is being used.
// This is only allowed in the direct implementation of members
// since they could escape their object scope.
type MyInherited() as self =
inherit MyBase()
do
(fun () -> self.Param) () |> ignore // Works
Now actually it is a lot better, all I need to do is to use self
instead of base
...(I don't have to redefine Param
since it is already inherited.)
The reason why F# has such restriction is explained here:
why is base only possible in private members?
But I am still not clear why base
cannot be used in a closure, although it is accessible in a simple let binding.
your initial code was perfectly valid. You don't have to define a method first that calls through to a base.
This works:
do
this.DoSomething()
member this.DoSomething() = base.DoSomething()
but this avoids duplication like you mentioned:
do
base.DoSomething()
This is what was tripping you up -- constructors can not have a return value. If the last statement in a sequence has a return, F# assumes that the function has a return of whatever that value/type is. However, this can not act contrary to a method signature if explicitly defined. So F# asks you to be explicit about your intention in such a case. If your intention is to throw away the return of base.DoSomething(), use the pipe-to-ignore operator combination of |> ignore like this:
do
base.DoSomething() |> ignore
Let's put this another way, if a functions' purpose is to return a value with no side effects, and if this value is consequently not used, then we can safely conclude that the function doesn't need to be called in the constructor. So the compiler / interactive environment warns you about that. That's what F# is encouraging here.
To be fair, that's not immediately obvious, unless you take the following into account: do compiles all such statements into the primary constructor. If you check out the F# OO-style for constructor overloading it might be clearer where the unit * signature was coming from.
Normally, having implicitly defined return value makes writing function a more fluid experience... as Rich Hickey puts it, it is just down to familiarity now.
Possibly a compiler constraint is being applied in an overeager fashion, or probably behind-the-scenes the do is defined as a closure before getting unwound and applied to the constructor by the compiler. The closure would be able to see the this, but a method / constructor gets a this and a base. That's not very intuitive is it? It seems like you've found a rough edge that needs some sanding. Please consider making a feature request. F# is now completely open source, so it is worthwhile documenting cases like this.
I did a short exercise on this. While this can be used in a constructor, that will not be adequate when there's an override. I think the following might be a decent way forward (see the new() block at the end).
[<AbstractClass>]
type MyBase() =
let letValue = "let value"
abstract Method : unit -> string
default this.Method() = "test"
member this.Method2() = "Param2"
type MyInherited(param : string) as this =
inherit MyBase()
// let localLetValue() = this.letValue // fails. let values are private members
do
//(fun () -> base.Param) () |> ignore // Error: The 'base' keyword is used in an invalid way. Base calls cannot be used in closures. Consider using a private member to make base calls.
(fun () -> this.Method) () |> ignore // succeeds -- takes local
(fun () -> this.base_Method) () |> ignore // succeeds -- takes base
(fun () -> this.Method2) () |> ignore // succeeds -- takes base
override this.Method() = "ABCdefHIJ"
member this.base_Method() = base.Method()
new() as this =
let a = base.Method() // succeeds
let b = this.Method() // succeeds
MyInherited("some value")
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With