4

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.

chtenb
  • 14,924
  • 14
  • 78
  • 116
  • 3
    You're using a functional programming language, so why not use the functional-programming version of the Visitor pattern: *functions*? I.e., `member this.InvokeVisit (visit : ObjectA -> unit) = visit this`. – rmunn Jun 25 '19 at 15:57
  • See also https://www.voxxed.com/2016/05/gang-four-patterns-functional-light-part-4/ for how to do the Visitor pattern in Scala; though I don't know Scala and only skimmed that article, its features should be similar enough to those in F# that you could convert the article to F# without too much impedance mismatch. – rmunn Jun 25 '19 at 15:59
  • 2
    While you're not asking for opinions about whether you should use this pattern, I don't think it's meaningfully possible to answer this question without doing so. – VoronoiPotato Jun 25 '19 at 15:59
  • @VoronoiPotato updated answer to give you reason. Also I disagree with your statement about this question not being answerable. If you are certain the answer is no, then post an answer explaining why. – chtenb Jun 25 '19 at 16:27
  • 1
    You can write any construct in any language, however there reaches a point where it is more tortuous than just using a different approach. Writing a visitor pattern in F# is like adding pedals to roller skates. It naturally begs the question why you think they are needed, interop or otherwise and what you would consider using them for. – VoronoiPotato Jun 25 '19 at 16:35
  • https://stackoverflow.com/questions/694651/what-task-is-best-done-in-a-functional-programming-style/694822#694822 Here's a good breakdown. and here's a way to consume a C# visitor without too much pain. http://bugsquash.blogspot.com/2012/03/wrapping-visitors-with-active-patterns.html – VoronoiPotato Jun 25 '19 at 16:44
  • @VoronoiPotato thanks, that looks like a useful link! Responding to your previous comment: if the pattern indeed turns out to be tortuous to implement (which is something I can't assess, and is implicitly posed as a question in the OP) then it makes sense to look for alternatives indeed. Being an F# newbie I'd like to know to what extent that is the case – chtenb Jun 25 '19 at 16:49
  • Of course :). For the record while I'm sure there is a "better" way to represent this pattern in F#, I wouldn't know how to do it. The primary way to do it is to not use a pattern at all, since the concept is native to the language. I personally would lean towards using pattern matching and DU's, and then exposing a set of functions to my C# consumer for convenience. see https://stackoverflow.com/questions/23843142/f-discriminated-union-usage-from-c-sharp However as of C# 8 you should be able to use pattern matching also from C#. – VoronoiPotato Jun 25 '19 at 16:52
  • 2
    It would be better to ask about the actual problem you are trying to solve, rather than how to fix your attempted solution. The latter is considered [an XY problem](http://xyproblem.info/), which tends to lead to unclear questions with lots of comments attemptingt to figure out what the problem actually is. – glennsl Jun 25 '19 at 17:01
  • @rmunn The particular limitation the question is asking about ("we are limited to have all the types implementing IVisitable in one file, due to the use of the and keyword") doesn't exist in Scala in any way. – Alexey Romanov Jun 25 '19 at 17:46
  • 1
    In F# we like to have types depend on types declared before it. There is technically a way around this but it's a closely guarded secret and I will defend it with my grave :P. We have direct solutions to many things that C# has patterns for, so it's better to steer users towards those solutions so that they learn the language. Encouraging them to implement C# patterns in an environment where they are no longer needed, may be doing more harm than help. – VoronoiPotato Jun 25 '19 at 18:28
  • It's `namespace rec NamespaceNameHere` https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/namespaces but you almost certainly don't need it! If you turn it on everything gets just a little bit harder to read and it's extremely hard to go back. In my experience it's better to just pretend it doesn't exist because doing so leads you away from the happy path, especially if you're not experienced in F#. – VoronoiPotato Jun 25 '19 at 18:38
  • Beginners love the idea that they can write OO code in F#, and sure it's great, but it's doing the language a disservice if you never explore outside of that OO space. Especially if you start writing patterns to solve problems that no longer exist :). – VoronoiPotato Jun 25 '19 at 18:43

2 Answers2

5

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();
    }
}
VoronoiPotato
  • 3,113
  • 20
  • 30
0

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
dumetrulo
  • 1,993
  • 9
  • 11
  • I don't see how this would work. How would you implement the `MyVisitor` class from my code example in terms of this solution? – chtenb Jun 27 '19 at 12:27