Reputation: 33
I am trying to rewrite a C# application I wrote long time ago into F#, but I am having some troubles, because this is my first attempt in writing something meaningful in F#
in my C# software, I have the following class "Buildings" which is the parent class that I need to usein the rest of the application:
public class Buildings()
{
Buildings()
{
Spectre = new Spectres(this);
}
public double Height;
}
and another class called "Spectres", which is only used through Buildings:
public class Spectres(Buildings prntBuilding)
{
Spectres()
{
_prntBuilding = prntBuilding;
}
private Buildings _prntBuilding;
public double doSomethingToheight()
{
return _prntBuilding.Height * 2.0;
}
}
my question is: how can I do this in F#, are there alternatives for this Parent-Child relationship in F#?
Edit: I found a solution
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
Upvotes: 2
Views: 422
Reputation: 36708
The best approach is to rethink your design.
Why does the Spectres
class need to know about its parent? In the sample code you've shown us, the Spectres
class only needs to know its parent in order to get one input parameter (the height) for the doSomethingToheight
method. Well, why not just pass the height directly to that method?
let doSomethingToHeight height = height * 2.0
As an added bonus, this version of the doSomethingToHeight
function is a pure function: it has no side effects, and its result is entirely determined by its input parameters. Pure functions have many advantages: they're much easier to test, and they're also much easier to reuse in other parts of your code. You'll be surprised how valuable that is: once you start rewriting your methods to be pure functions instead, you'll eventually discover all kinds of ways that they can be reused elsewhere.
And incidentally, now the Spectres
class in your example code looks like this in C# (I'll keep the examples in C# to show that this design approach isn't limited to F#, and because you're more familiar with reading C# code at this point):
public class Spectres()
{
Spectres(Buildings prntBuilding)
{
_prntBuilding = prntBuilding;
}
private Buildings _prntBuilding;
public double doSomethingToheight(double height)
{
return height * 2.0;
}
}
Now there's no particular reason for Spectres
to need to keep the reference to the parent building around. (At least in this example code; in your original code, you might have other uses for it. But I'm working just with what you've shown us so far). So let's get rid of that unnecessary constructor parameter and private field:
public class Spectres()
{
Spectres()
{
}
public double doSomethingToheight(double height)
{
return height * 2.0;
}
}
And now, this is a class that has just a single function in it and does no other work than supporting that single function. Have you ever read Steve Yegge's brilliant article, "Execution in the Kingdom of the Nouns"? Classes that do nothing but carry around a single function should be replaced with that function. In C# that's a bit tricky because all functions must live in a class somewhere, but you can get the closest to a standalone function by making it a static method in some utility class where you keep all of your standalone functions:
public static class StandaloneFunctions
{
public static double doSomethingToHeight(double height)
{
return height * 2.0;
}
}
Of course, in F# those seven lines can be reduced down to the simple one-line definition I showed you at the beginning of this answer:
let doSomethingToHeight height = height * 2.0
No need to declare a static class for that function to live in; the F# compiler will take care of that for you behind the scenes. All you have to do is think about your data, and how your data will be transformed.
At this point, you're probably saying, "Yes, but my Building
class has other interactions with Spectre
that you don't know about, and your solution won't work because of those interactions." Well, if you use a similar approach to those interactions (turn them into pure functions), I bet you'll discover that it does work, and furthermore, is much simpler to understand in the long run. And if you're not sure how to do that transformation, ask another question about the part you're unsure of; there are several F# experts around who will probably be happy to help you with it.
Upvotes: 2
Reputation: 2767
Presumably your issue is caused by the ordering of declarations in F# affecting visibility of the second type. (Order matters in F#.)
You can solve this without much hoopla with mutually recursive types, as in:
type Buildings() =
member val Height = 0.0 with get, set
and Spectres(prntBuilding : Buildings) =
member __.DoSomething () = prntBuilding.Height * 2.0
The and
indicates that they know about each other.
The documentation for mutually recursive types is here: https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/classes#mutually-recursive-types
Upvotes: 3