Getting myself all mixed up with interface, default, abstract and override

I have some simple code that I just can’t get to compile. (It compiled once but didn’t work properly and, after messing around with it a lot, I can’t now remember how it looked when it compiled.)

I know I will be doing something stupid but I’ve tried so many variants of this code that I can’t remember which way round I should be looking now.

The code, as it currently stands, is as follows:


open System 

type IMyThing = 
    abstract member GetNumberAsString : unit -> string  
    abstract member ChangeNumber : unit -> unit 

[<AbstractClass>]
type BaseThing(initialNumber : int) = 

    let mutable theNumber = initialNumber 
    
    interface IMyThing with 
        member _.GetNumberAsString() = theNumber |> string 
        member _.ChangeNumber() = () 

type RandomThing(initialNumber : int) = 

    inherit BaseThing(initialNumber) 

    override this.ChangeNumber() = 
        let rnd = System.Random.Shared.Next() 
        printfn "Random: Changing number to %A" rnd 
        theNumber <- rnd 

let randomThing = new RandomThing(7) :> IMyThing 
let before = randomThing.GetNumberAsString() 
do randomThing.ChangeNumber() 
let after = randomThing.GetNumberAsString()

What I want is to have BaseThing (which should not be instantiable) define ‘something’ which stores a number (preferably not available outside of BaseThing or dervied classes) which can then be modified by the derived class instances (via ChangeNumber) and then reported on in the exact same way (via GetNumberAsString) in each derived class instance.

I can’t seem to be able to tell the compiler to override the BaseThing version of ChangeNumber. (I don’t particularly want that useless member in BaseThing but, as far as I know, I have to give it something.)

I’ve tried all kind of variants, making things abstract, trying default (which I could never get to work), overrides, using ‘this.’, or not using ‘this.’, using a property (but that was causing other problems), etc. etc.

I’ve looked at a whole lot of blog posts, and Stack Overflow questions, the Microsoft documentation, and fsharpforfunandprofit, but can’t seem to get this working.

What stupid thing(s) am I doing wrong?

You’re trying to grok inheritance and interfaces at the same time. That’s often very troublesome if you are new to both concepts. Based on your code, here’s something that perhaps can be of some help. Replace the "Dump"s with Console.WriteLine or some other logging.

open System 

type IMyThing = 
    abstract member GetNumberAsString : unit -> string  
    abstract member ChangeNumber : unit -> unit 

[<AbstractClass>]
type BaseThing(initialNumber : int) = 

    let mutable theNumber = initialNumber
    
    member _.SetNumber value =
        theNumber <- value

    member _.GetNumberAsString() = 
        "base.GetNumberAsString".Dump()
        theNumber |> string

    abstract member ChangeNumber : unit -> unit
    default _.ChangeNumber() =
        "base.ChangeNumber".Dump()
        theNumber <- 8
        ()
    
    interface IMyThing with 
        member x.GetNumberAsString() = 
            "interface.GetNumberAsString".Dump()
            x.GetNumberAsString()
        member x.ChangeNumber() = 
            "interface.ChangeNumber".Dump()
            x.ChangeNumber()

type RandomThing(initialNumber : int) = 
    inherit BaseThing(initialNumber) 

    override x.ChangeNumber() =
        "inherited.ChangeNumber".Dump()
        let rnd = System.Random.Shared.Next() 
        printfn "Random: Changing number to %A" rnd 
        x.SetNumber rnd

let randomThing = new RandomThing(7) :> IMyThing 
let before = randomThing.GetNumberAsString() 
do randomThing.ChangeNumber() 
let after = randomThing.GetNumberAsString()
before.Dump()
after.Dump()
1 Like

You don’t need the default for ChangeNumber, but it’s there for experimentation.

When you put the implementation for IMyThing in the base class, then that’s where the interface will go to execute things, and then it’s the base class that through polymorphy will call the derived classes implementations. I suspect you understood this well enough, but just not exactly how to code it.

ChangeNumber was not implemented in the base class. That’s why you could not override it. So here’s where you see that interface and inheritance are two distinct things, and not two sides of the same coin. There is no need to mix these two mechanisms, but in your case you do have some common implementation detail that makes sense of the mix.

I didn’t understand how you wanted GetNumberAsString to work. If you want that too to be able to have different implementations, then just follow the same pattern used for ChangeNumber.

Oh my, I wouldn’t have guessed that all of that was needed just to do this.

Thanks for giving the full code, that made it much easier to see what to do.

I’ve had a play around with it and I’m starting to understand it a bit more now so I should be able to apply this to my real code without much trouble.

Much appreciate your assistance.

Unless you need interop with C#, you can probably do these things easier with functional stuff. Even if you want to use an interface, you can still use e.g. a record for the implementation. You can declare functions within a record, and instantiate that record with varying implementations for these functions. The record itself can also be your interface, so that you don’t need an explicit separate interface.

Thanks for the extra advice.

I’ve mostly been keeping away from classes in F# until now, hence my confusion when trying to use them in a non-basic way.

In this case the classes will be used to manage database connections (permanently connected versus temporarily connected, or in-memory for testing), obtained from a C# library, which are injected into functions that use another C# library, and that’s all done at the ‘domain edge’ so I think I’m probably going to be okay, unless something nasty pops up.

Even if I change my mind and do it differently I’ve learned something new, which is nice.

(I was able to apply the techniques to my real code without any major problems.)