How to implement a visitor pattern in F# while not being restricted to one file?

Viewed 353

The following code sample demonstrates an implementation of the visitor pattern in F#

module VisitorPattern

    type IVisitor =
        abstract Visit : ObjectA -> unit
        abstract Visit : ObjectB -> unit

    and IVisitable =
        abstract InvokeVisit : IVisitor -> unit

    and ObjectA =
        interface IVisitable with
            member this.InvokeVisit (visitor: IVisitor) =
                visitor.Visit(this)

    and ObjectB =
        interface IVisitable with
            member this.InvokeVisit (visitor: IVisitor) =
                visitor.Visit(this)

    type MyVisitor =
        member this.Visit (a : ObjectA) =
            printfn "Visited object A"

        member this.Visit (b : ObjectB) =
            printfn "Visited object B"

This compiles fine, but we are limited to have all the types implementing IVisitable in one file, due to the use of the and keyword. This keyword seems to be necessary to allow for the mutual type references.

Is there a way to implement this pattern in such a way that we are not restricted to one file?

(I'm not asking for opinions on whether you should use this pattern in F#)

EDIT: I'm asking this question because the Visitor Pattern is relevant when doing interop with C# code.

2 Answers

Pattern matching should accomplish the same goal with a fraction of the complexity and overhead. In my personal experience this is the best way to implement a visitor pattern in F#.

type Visitor = A of int | B of int

match a with 
| A x -> 1 * x
| B x -> 2 * x

then for some possible C#

private static void Main()
{
    Visitor a = Visitor.A(7)
    switch(a){
        case Visitor.A x:
            x.Item * 1;
            break;
        case Visitor.B x:
            x.Item * 2;
            break;
        default:
            throw new ArgumentOutOfRangeException();
    }
}

If you really need to use OO patterns for C# interop, I believe the best way to decouple the types is to use generics:

module VisitorPattern =

    type IVisitor<'T> =
        abstract Visit : 'T -> unit

    type IVisitable<'T> =
        abstract InvokeVisit : IVisitor<'T> -> unit

module Visitors =

    open VisitorPattern

    type ObjectA () =
        interface IVisitable<ObjectA> with
            member this.InvokeVisit (visitor : IVisitor<ObjectA>) =
                visitor.Visit this

    type ObjectB () =
        interface IVisitable<ObjectB> with
            member this.InvokeVisit (visitor : IVisitor<ObjectB>) =
                visitor.Visit this
Related