Могут ли члены типа F# ссылаться друг на друга?

Мне было интересно, есть ли способ позволить членам типа ссылаться друг на друга. Я хотел бы написать следующую программу следующим образом:

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

Но this.RollD66 не может видеть this.RollD6. Я вроде как понимаю, почему, но похоже, что в большинстве функциональных языков есть способ сообщить функциям, что они существуют заранее, так что такой или подобный синтаксис возможен.

Вместо этого мне пришлось сделать следующее, что ненамного больше кода, но кажется, что первое будет выглядеть более элегантно, чем второе, особенно если таких случаев больше.

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

Какие-нибудь советы? Спасибо!


person McMuttons    schedule 07.04.2011    source источник
comment
Оба отличных ответа, но пришлось выбрать один и выбрать тот, который, вероятно, будет ближе к моему использованию. Спасибо за помощь!   -  person McMuttons    schedule 09.04.2011


Ответы (2)


Если класс представляет собой не что иное, как реализацию интерфейса, вы можете использовать объектное выражение. Я предпочитаю это, когда это возможно, из-за его лаконичности.

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();
person Daniel    schedule 07.04.2011
comment
Мне это нравится, но кажется, что тогда мне придется отказаться от пространств имен, поскольку при использовании пространства имен невозможно иметь плавающие 'let's без объемлющего типа. Или я так понимаю. :) Поскольку я ссылаюсь на код из C#, иметь их очень удобно. Я понимаю, что это выходит за рамки того, что я задал в вопросе. - person McMuttons; 08.04.2011
comment
Вам не нужно отказываться от пространств имен. Я обновил код для демонстрации. Один и тот же API можно сделать очень естественным в F# и C#. - person Daniel; 08.04.2011

Попробуйте следующее:

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

Тем не менее, вы можете найти следующее более простым в использовании (поскольку F# реализует интерфейсы явно, в отличие от C#, который реализует их неявно по умолчанию):

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
person ildjarn    schedule 07.04.2011
comment
Спасибо за советы. Оба варианта я не рассматривал. :) Не уверен, что они более лаконичны. Я предполагаю, что первый, но кажется более загроможденным. - person McMuttons; 08.04.2011
comment
Ни один из них не предназначен быть более кратким, чем другой; это использование последней версии, которая обычно будет более лаконичной. - person ildjarn; 08.04.2011
comment
Честно говоря, я просил об элегантности, а не о лаконичности. Что-то вроде мозгового пука, когда я печатал. Разве использование не будет одинаковым для обоих из них? - person McMuttons; 09.04.2011
comment
@McMuttons: Нет, совсем нет. С первым вы не могли бы просто создать экземпляр объекта DieRoller и вызвать любой из этих методов; вам сначала нужно было бы привести к IDieRoller. Это то, что я имел в виду под явной реализацией интерфейса. - person ildjarn; 09.04.2011
comment
Ах хорошо, я понимаю, что вы имеете в виду. Спасибо за объяснение. - person McMuttons; 12.04.2011