4

I was wondering if there is a way to let type members reference each other. I would like to write the following program like this:

type IDieRoller =
    abstract RollDn : int -> int
    abstract RollD6 : int
    abstract RollD66 : int

type DieRoller() =
    let randomizer = new Random()

    interface IDieRoller with
        member this.RollDn max = randomizer.Next(max)
        member this.RollD6 = randomizer.Next(6)
        member this.RollD66 = (RollD6 * 10) + RollD6

But, this.RollD66 is unable to see this.RollD6. I can sort of see why, but it seems most functional languages have a way of letting functions know that they exist ahead of time so that this or similar syntax is possible.

Instead I've had to do the following, which isn't much more code, but it seems that the former would look more elegant than the latter, especially if there are more cases like that.

type DieRoller() =
    let randomizer = new Random()

    let rollD6 = randomizer.Next(6)

    interface IDieRoller with
        member this.RollDn max = randomizer.Next(max)
        member this.RollD6 = rollD6
        member this.RollD66 = (rollD6 * 10) + rollD6

Any tips? Thanks!

McMuttons
  • 871
  • 7
  • 22
  • Both great answers, but had to pick one, and went with the one that will probably be closer to my usage. Thanks for the help! – McMuttons Apr 09 '11 at 00:03

2 Answers2

6

Try the following:

open System

type IDieRoller =
    abstract RollDn : int -> int
    abstract RollD6 : int
    abstract RollD66 : int

type DieRoller() =
    let randomizer = Random()

    interface IDieRoller with
        member this.RollDn max = randomizer.Next max
        member this.RollD6 = randomizer.Next 6
        member this.RollD66 =
            (this :> IDieRoller).RollD6 * 10 + (this :> IDieRoller).RollD6

However, you may find the following easier to use (as F# implements interfaces explicitly, unlike C# which implements them implicitly by default):

open System

type IDieRoller =
    abstract RollDn : int -> int
    abstract RollD6 : int
    abstract RollD66 : int

type DieRoller() =
    let randomizer = Random()

    member this.RollDn max = randomizer.Next max
    member this.RollD6 = randomizer.Next 6
    member this.RollD66 = this.RollD6 * 10 + this.RollD6

    interface IDieRoller with
        member this.RollDn max = this.RollDn max
        member this.RollD6 = this.RollD6
        member this.RollD66 = this.RollD66
ildjarn
  • 62,044
  • 9
  • 127
  • 211
  • Thanks for the tips. Both options I hadn't thought of. :) Not sure either are more concise though. I guess the first one is, but does seem more cluttered. – McMuttons Apr 08 '11 at 14:36
  • Neither is intended to be more concise than the other; it's the *usage* of the latter version that will usually be more concise. – ildjarn Apr 08 '11 at 17:14
  • Fair enough, I did ask for elegance and not conciseness. A bit of a brain fart when I typed. Wouldn't the usage be the same for both of those? – McMuttons Apr 09 '11 at 00:00
  • 1
    @McMuttons : No, not at all. With the former, you could not merely instantiate a `DieRoller` object and call any of those methods; you would first have to cast to `IDieRoller`. This is what I was referring to by "explicit interface implementation". – ildjarn Apr 09 '11 at 00:10
  • Ah ok, I see what you mean. Thanks for the explanation. – McMuttons Apr 12 '11 at 07:11
6

If the class is nothing more than an interface implementation, you can use an object expression. I prefer this, when possible, for its conciseness.

namespace MyNamespace

type IDieRoller =
    abstract RollDn : int -> int
    abstract RollD6 : int
    abstract RollD66 : int

module DieRoller =

    open System

    [<CompiledName("Create")>]
    let makeDieRoller() =
        let randomizer = new Random()
        { new IDieRoller with
            member this.RollDn max = randomizer.Next max
            member this.RollD6 = randomizer.Next 6
            member this.RollD66 = this.RollD6 * 10 + this.RollD6 }

F#

open MyNamespace.DieRoller
let dieRoller = makeDieRoller()

C#

using MyNamespace;
var dieRoller = DieRoller.Create();
Daniel
  • 47,404
  • 11
  • 101
  • 179
  • I like that, but it seems that then I have to give up namespaces, since it's not possible to have floating 'let's without an enclosing type when using namespace. Or so I gather. :) Since I'm referencing the code from C#, having them is really handy. I realize that goes beyond what I asked in the question though. – McMuttons Apr 08 '11 at 14:33
  • 1
    You don't have to give up namespaces. I updated the code to demonstrate. It's possible to make the same API feel very natural within F# _and_ C#. – Daniel Apr 08 '11 at 14:46