My first disappointment on F# type system.

Today I found that there are examples of code that correct for C# and could not be compiled in F#. I was very surprised and upset.

I continued playing with Neo4jClient and tried to implement more complex model. I have found that it  is not possible to define F# type for cross entity relationship. To make such relationship I need to define type that implements two interfaces, like this:

type FollowRelationship(target) =
    inherit Relationship(target)
    interface IRelationshipAllowingSourceNode<Person>
    interface IRelationshipAllowingTargetNode<Company>

    override this.RelationshipTypeKey
        with get() = "follow"

But F# compiler does not allow the creation of such type. I’ve got the following compilation error:

This type implements or inherits the same interface at different generic instantiations ‘IRelationshipAllowingParticipantNode’ and ‘IRelationshipAllowingParticipantNode’. This is not permitted in this version of F#.

It happens because IRelationshipAllowingSourceNode and IRelationshipAllowingTargetNode inherited from a single generic interface IRelationshipAllowingParticipantNode and F# does not allow to implement the same interface in different generic instantiations.

Here is an implementation of these interfaces from Neo4jClient source code.

public interface IRelationshipAllowingParticipantNode<out TNode>
{
}
public interface IRelationshipAllowingSourceNode<out TNode>
    : IRelationshipAllowingParticipantNode<TNode>
{
}
public interface IRelationshipAllowingTargetNode<out TNode>
    : IRelationshipAllowingParticipantNode<TNode>
{
}

As I found, there is actually no way to do it in F#. An only option is to write such types in C#. We have a similar question about this on StackOverflow: “Implementing the same interface at different generic instantiations“.

May be it is not a real constrain of F#, but it adds a noise to C#/F# integration. It is means that not all C# design patterns are integrable with F#.

It can be one more answer to

10 thoughts on “My first disappointment on F# type system.

  1. I suppose you could make a generic class in C# that inherits Relationship and implements both interfaces, and then inherit that class in F#. That would at least allow you to re-use the base class multiple times instead of writing C# for every relationship you want to create. If it worked, maybe the Neo4jClient people would accept a pull request to put that base class right into the client library.

  2. Hmm, it seems that even C# can’t compile such a base class. Something like this (hopefully my angle brackets won’t be eaten)…

    public class RelationshipBase :
    Relationship,
    IRelationshipAllowingSourceNode,
    IRelationshipAllowingTargetNode
    {
    }

    …gives this C# compiler error:

    ‘Neo4jClient.RelationshipBase’ cannot implement both ‘Neo4jClient.IRelationshipAllowingParticipantNode’ and ‘Neo4jClient.IRelationshipAllowingParticipantNode’ because they may unify for some type parameter substitutions.

    If you google that message, “because they may unify for some type parameter substitutions,” you’ll find a whole lot of discussion about how this pattern followed by Neo4jClient is generally frowned upon even in C# land.

  3. …and it ate my angle brackets. Substituting square ones:

    public class RelationshipBase[TSource, TTarget] :
    Relationship,
    IRelationshipAllowingSourceNode[TSource],
    IRelationshipAllowingTargetNode[TTarget]
    {
    }

    1. Thank you for your attempt to hack it. I do not find a solution other than implementing data model in C# or try to change Neo4jClient API.

  4. Can you use the “and” keyword with your definition of type FollowRelationship(target) to get around the same interface in multiple generic instantiations problem?

    Notice both these implementations of Vector implement the same interfaces twice by using “and”:
    https://github.com/fsharp/fsharpx/blob/master/src/FSharpx.Collections.Experimental/Vector.fs
    https://github.com/fsharp/fsharpx/blob/master/src/FSharpx.Core/Collections/Vector.fs

    1. Sorry, I did not catch the idea. Using `and` keyword I will define two types, but I need the one that implement two different generic instances of the same interface.

  5. Wow, I’ve met this constraint also when playing around Neo4j but in totally unrelated.
    The real issue is XUnit IClassFixture and ICollectionFixture that were designed with using inheritance of several generic interfaces on single type to make DI in test runtime based on this generic param.

Leave a comment