Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I hide methods in F#?

Tags:

f#

api

I am currently implementing a Spec framework in F# and I want to hide the Equals, GetHashCode etc. methods on my should type, so that the API is not cluttered with these.

I know in C# it is done by making the class implement an interface like this:

using System;
using System.ComponentModel;

public interface IFluentInterface
{
    [EditorBrowsable(EditorBrowsableState.Never)]
    bool Equals(object other);

    [EditorBrowsable(EditorBrowsableState.Never)]
    string ToString();

    [EditorBrowsable(EditorBrowsableState.Never)]
    int GetHashCode();

    [EditorBrowsable(EditorBrowsableState.Never)]
    Type GetType();
}

I tried doing the same in F#:

type IFluentInterface = interface

    [<EditorBrowsable(EditorBrowsableState.Never)>]
    abstract Equals : (obj) -> bool

    [<EditorBrowsable(EditorBrowsableState.Never)>]
    abstract ToString: unit -> string

    [<EditorBrowsable(EditorBrowsableState.Never)>]
    abstract GetHashCode: unit -> int

    [<EditorBrowsable(EditorBrowsableState.Never)>]
    abstract GetType : unit -> Type 
end

Implemented it in my type:

        interface IFluentInterface with
        member x.Equals(other) = x.Equals(other)
        member x.ToString()    = x.ToString() 
        member x.GetHashCode() = x.GetHashCode()
        member x.GetType()     = x.GetType() 

but without success.

I also tried to override the methods in my type and adding the attribute that way, but that didn't do the trick either.

So the question remains, how can I clean up my API ?

Edit:

Thanks to the help (see below) I was able to solve my problem.

In summary, .Equals and .GetHashCode can be hidden via [<NoEquality>] [<NoComparison>] but that will also change the semantics.

The hiding via EditorBrowsable attributes does not work.

The only way to have a clean API and still be able to overload methods is to make these method members static.

The resulting class can be found by browsing inside my project FSharpSpec.

The type in question can be found here.

Thanks to everyone who helped me solve this problem.

Cheers ...

like image 501
Thorsten Lorenz Avatar asked Mar 05 '10 17:03

Thorsten Lorenz


3 Answers

Alternatively, you could design the library using an alternative style using functions enclosed in a module. This is the usual way for writing functional code in F# and then you won't need to hide any standard .NET methods. To complete the example given by 'kvb', here is an example of object-oriented solution:

type MyNum(n:int) =
  member x.Add(m) = MyNum(n+m)
  member x.Mul(m) = MyNum(n*m)

let n = new MyNum(1)
n.Add(2).Mul(10) // 'ToString' shows in the IntelliSense

The functional way of writing the code might look like this:

type Num = Num of int
module MyNum =
  let create n = Num n
  let add m (Num n) = Num (m + n)
  let mul m (Num n) = Num (m * n)

MyNum.create 1 |> MyNum.add 2 |> MyNum.mul 10

If you type MyNum., the F# IntelliSense will show the functions defined in the module, so you won't see any noise in this case.

like image 131
Tomas Petricek Avatar answered Oct 21 '22 19:10

Tomas Petricek


Repeating my answer from

http://cs.hubfs.net/forums/thread/13317.aspx

In F# you can disallow Equals & GetHashCode (and remove them from intellisense) by annotating the type with the NoEquality and NoComparison attributes, as shown below. User-defined methods can also be hidden from the intellisense list via the Obsolete attribute or the CompilerMessage attribute with IsHidden=true. There is no way to hide the System.Object methods GetType and ToString from the F# intellisense.

[<NoEquality; NoComparison>]
type Foo() =
    member x.Bar() = ()
    member x.Qux() = ()
    [<System.Obsolete>]
    member x.HideMe() = ()
    [<CompilerMessage("A warning message",99999,IsError=false,IsHidden=true)>]
    member x.WarnMe() = ()

let foo = new Foo()
foo.  // see intellisense here
like image 5
Brian Avatar answered Oct 21 '22 18:10

Brian


I don't think that there is any way to do that in F# in general. In the particular case of .Equals and .GetHashCode, you can make them unusable by putting a [<NoEquality>] attribute on your type, but this actually has a semantic effect in addition to hiding those methods.

EDIT

It might also be worth mentioning that fluent interfaces are rarely used in F#, since it's much more idiomatic to use combinators and pipelining instead. For instance, imagine that we want to create a way to create arithmetic expressions. Rather than

let x = Expressions.MakeExpr(1).Add(2).Mul(3).Add(4)

I think that most F# users would prefer to write

open Expressions
let x = 
  1
  |> makeExpr
  |> add 2
  |> mul 3
  |> add 4

With this style, there's no need to hide members because expressions get piped to combinators, rather than calling methods of an expression builder.

like image 3
kvb Avatar answered Oct 21 '22 17:10

kvb