35

Is it possible to define a static class that contains overloadable members in F#? let module bindings cannot be overloaded, even though they are compiled into static members in static classes.

type declarations can contain static members, but I don't know if the type itself can be made static.

My current solution is to define a type with a private constructor and just use that. I'm wondering if there is a way I can define a static type as I want.

Ruben Bartelink
  • 59,778
  • 26
  • 187
  • 249
GregRos
  • 8,667
  • 3
  • 37
  • 63

5 Answers5

48

As Robert Jeppeson pointed out, a "static class" in C# is just short-hand for making a class that cannot be instantiated or inherited from, and has only static members. Here's how you can accomplish exactly that in F#:

[<AbstractClass; Sealed>]
type MyStaticClass private () =
    static member SomeStaticMethod(a, b, c) =
       (a + b + c)

    static member SomeStaticMethod(a, b, c, d) =
       (a + b + c + d)

This might be a little bit of overkill, as both the AbstractClass and the private constructor will prevent you from creating an instance of the class, however, this is what C# static classes do - they are compiled to an abstract class with a private constructor. The Sealed attribute prevents you from inheriting from this class.

This technique won't cause a compiler error if you add instance methods the way it would in C#, but from a caller's point of view there is no difference.

Joel Mueller
  • 28,324
  • 9
  • 63
  • 88
  • 1
    Note that, if you omit the default constructor, you cannot create an instance of the class from F#, meaning that it will behave like a static class. However, C# won't see it that way unless you also add the attributes `AbstractClass` and `Sealed` (see also this bug report: https://github.com/dotnet/fsharp/issues/8093). – Abel Jan 17 '20 at 15:35
  • updated issue replacing the above https://github.com/fsharp/fslang-suggestions/issues/906 – Ruben Bartelink Apr 21 '23 at 07:11
10

This is explained in The F# Component Design Guidelines.

[<AbstractClass; Sealed>]
type Demo =
    static member World = "World"
    static member Hello() = Demo.Hello(Demo.World)
    static member Hello(name: string) = sprintf "Hello %s!" name

let s1 = Demo.Hello()
let s2 = Demo.Hello("F#")

It is still possible to define instance methods, but you can't instantiate the class when there is no constructor available.

edit Jan 2021 : See the comment from Abel, and the linked issue. Joel Mueller's answer seems to be the best advice so far, but things will perhaps change in the future.

Aage
  • 5,932
  • 2
  • 32
  • 57
Bent Tranberg
  • 3,445
  • 26
  • 35
  • Things seems to work well without AbstractClass, Why would I leave it in? – Bent Tranberg Dec 26 '16 at 14:39
  • ...to prevent `let foo = new Demo()` – Joel Mueller Nov 14 '17 at 21:35
  • No, you can't do that anyway when there's no constructor. Remove AbstractClass, and you still can't do that. – Bent Tranberg Nov 14 '17 at 21:39
  • 2
    As of VS2019, including the private constructor actually emits a constructor, which presumably is unusable. So, this answer is as-desired: it produces a sealed, static class that when decompiled looks like this (C#): `public static class Demo { ...`. Omitting `AbstractClass` causes it to be **not** be a static class. This is perhaps more obvious if you're interop-ing with another language. – Curt Nichols Jun 04 '19 at 16:49
  • 1
    @bent, you still can't do that *within F#*, but if your code is going to be consumed by other languages, you can create an instance of the class, *unless* you also include the `AbstractClass` attribute. See also this bug report: https://github.com/dotnet/fsharp/issues/8093 – Abel Jan 05 '20 at 14:08
6

There is no facility for defining static types in F#.

The first alternative is to define a module, but it lacks the capability of overloading functions (which is what you're after). The second alternative is to declare a normal type with static members.

Regarding the second approach, it is exactly what the accepted answer to your old question described. I refactor the code to explain it easier. First, a dummy single-case discreminated unions is defined:

type Overloads = Overloads

Second, you exploit the fact that static members can be overloaded:

type Overloads with
    static member ($) (Overloads, m1: #IMeasurable) = fun (m2: #IMeasurable) -> m1.Measure + m2.Measure 
    static member ($) (Overloads, m1: int) = fun (m2: #IMeasurable) -> m1 + m2.Measure

Third, you propagate constraints of these overloaded methods to let-bounds using inline keyword:

let inline ( |+| ) m1 m2 = (Overloads $ m1) m2

When you're able to overload let-bounds using this method, you should create a wrapper module to hold these functions and mark your type private.

Community
  • 1
  • 1
pad
  • 41,040
  • 7
  • 92
  • 166
  • I was asking if there is a way to define a static type, and that I was aware that I could circumvent the issue by simply using a non-static type with a private constructor. This solution is somewhat convoluted, and doesn't really answer the question. I mean, the result is a class that can be instantiated. You could edit the answer and add that you need to restrict access to the union members of `Overloads`, but it would still be very convoluted. – GregRos Oct 27 '12 at 16:52
  • 1
    Yes, you can hide union case `type Overloads = private | Overloads`. Static classes aren't really useful in F#, except for overloading purpose. I think your best bet is to use private constructor. – pad Oct 27 '12 at 17:19
5

I'm not sure there is such a thing as a static class. 'static' on class level in C# was introduced in 2.0, I believe, mostly as convenience (avoid private constructors and compile-time checking that no instance members are present). You can't examine the type and conclude that it is static: http://msdn.microsoft.com/en-us/library/system.reflection.typeinfo.aspx

Update: MSDN declares a static class is a class that is sealed and has only static members: http://msdn.microsoft.com/en-us/library/79b3xss3(v=vs.80).aspx

So, what you're doing at the moment is the way to do it.

Robert Jeppesen
  • 7,837
  • 3
  • 35
  • 50
-4

I think the problem here is trying to make F# into C#. If the problem can't be solved in a non imperative fashion use C# or write an object oriented library and use it in F#.

Drew Holiday
  • 113
  • 4
  • 2
    F# contains lots of imperative and object-oriented features, exactly for the cases when you need them. – svick Jul 16 '14 at 11:47