Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Call base member in lambda function from inherited class constructor?

Tags:

f#

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

Update

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.

like image 982
colinfang Avatar asked Jan 09 '23 15:01

colinfang


1 Answers

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.

  • another name for unit in other languages is void

Update

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")
like image 91
sgtz Avatar answered Feb 02 '23 21:02

sgtz