如何在F#中做父子关系

时间:2017-09-29 20:39:43

标签: c# .net oop f#

我正在尝试将我很久以前写的C#应用​​程序重写为F#,但我遇到了一些麻烦,因为这是我第一次尝试在F#中写一些有意义的东西

在我的C#软件中,我有以下课程"建筑物"这是我需要在应用程序的其余部分使用的父类:

public class Buildings()
{
    Buildings()
    {
        Spectre = new Spectres(this);
    }
    public double Height;
}

另一个名为"幽灵"的类,仅用于建筑物:

public class Spectres(Buildings prntBuilding)
{
    Spectres()
    {
        _prntBuilding = prntBuilding;
    }

    private Buildings _prntBuilding;
    public double doSomethingToheight()
    {
       return _prntBuilding.Height * 2.0;
    }
}

我的问题是:我怎么能在F#中做到这一点,F#中有这种父子关系的替代方案吗?

编辑:我找到了解决方案

type Buildings() as self =
   member this.Hn = 1.0 with get, set
   member val Spectre = new Spectres(self) with get, set

and Spectres(prntBuinding : Buildings) = 
   let DoSomethingToHeight = prntBuilding.Hn * 2.0

2 个答案:

答案 0 :(得分:3)

据推测,您的问题是由F#中的声明排序影响第二种类型的可见性引起的。 (订单在F#中很重要。)

你可以用相互递归的类型解决这个问题,如:

type Buildings() =

    member val Height = 0.0 with get, set

and Spectres(prntBuilding : Buildings) =

    member __.DoSomething () = prntBuilding.Height * 2.0

and表示他们彼此了解。

相互递归类型的文档位于:https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/classes#mutually-recursive-types

答案 1 :(得分:2)

最好的方法是重新考虑你的设计。

为什么Spectres类需要了解其父级?在您向我们展示的示例代码中,Spectres类只需知道其父级,即可获得doSomethingToheight方法的一个输入参数(高度)。那么,为什么不直接将高度传递给该方法?

let doSomethingToHeight height = height * 2.0

作为一个额外的好处,这个版本的doSomethingToHeight函数是一个函数:它没有副作用,其结果完全由其输入参数决定。纯函数具有许多优点:它们更容易测试,并且在代码的其他部分中重用它们也更容易。你会感到惊讶的是:一旦你开始将你的方法重写为纯函数,你最终会发现它们可以在其他地方重用的各种方法。

顺便说一下,现在示例代码中的Spectres类在C#中看起来像这样(我将保留C#中的示例以表明此设计方法不限于F#,并且因为此时您更熟悉阅读C#代码):

public class Spectres()
{
    Spectres(Buildings prntBuilding)
    {
        _prntBuilding = prntBuilding;
    }

    private Buildings _prntBuilding;
    public double doSomethingToheight(double height)
    {
       return height * 2.0;
    }
}

现在Spectres没有特别要求保持对父母建筑的引用。 (至少在这个示例代码中;在您的原始代码中,您可能还有其他用途。但我现在只使用您向我们展示的内容)。因此,让我们摆脱那个不必要的构造函数参数和私有字段:

public class Spectres()
{
    Spectres()
    {
    }

    public double doSomethingToheight(double height)
    {
       return height * 2.0;
    }
}

现在,这是一个只有一个函数的类,并且没有其他工作而不是支持单个函数。你有没有读过Steve Yegge的精彩文章"Execution in the Kingdom of the Nouns"?除了携带单个函数之外什么都不做的类应该替换为该函数。在C#中有点棘手,因为所有函数都必须存在于某个类的某个类中,但是你可以通过在某个实用程序类中将它作为一个静态方法来保持最接近独立函数 all 您的独立功能:

public static class StandaloneFunctions
{
    public static double doSomethingToHeight(double height)
    {
        return height * 2.0;
    }
}

当然,在F#中,这七行可以简化为我在本回答开头给你看到的简单的单行定义:

let doSomethingToHeight height = height * 2.0

无需为该函数声明一个静态类; F#编译器将在幕后为您处理。您所要做的就是考虑您的数据以及数据的转换方式。

此时,您可能会说,"是的,但我的Building课程与您不了解的Spectre有其他互动,而您的因为这些互动,解决方案不会起作用。"好吧,如果你对这些交互使用类似的方法(将它们变成纯函数),我敢打赌你会发现它 工作,而且,长期来看,它更容易理解跑。如果您不确定如何进行转换,请询问有关您不确定的部分的其他问题;有几位F#专家可能很乐意帮助你。