views:

110

answers:

4

I am trying to implement a library with extended parsing capabilities. I decided that I will use fsyacc because I knew it from the university. Unfortunately I encountered following problem.

I defined a class for the head of my grammar (Head) and place its implementation in a single file. Then I defined parser as:

...
%start head
%type <Head> head
...

Fsyacc generates seeparated module (Parser). In order to succeed it has to be compiled in following order: Head.fs Parser.fs

In order to make this library similar to what you can find in .NET I would like to add a static Parse method to Head. Unfortunately I would need to make use of methods from Parser module.

I know that such type dependencies can be solved with 'and' operator but it is only applicable to types defined in one file.

Is there any other way to create types that depend on each other even when they are in separate files? I was looking for declaration/implementation separation mechanism like the one in C/C++ but I couldn't find anything.

+6  A: 

Short answer: no. There's no way to do mutually recursive entities across multiple files in F# 2.0. (This is something we plan to address in the next version of the language.)

You can work around this in a variety of ways, typically using a point of indirection and mutation. For example, your Head type could have a static 'InitializeParser' method that pokes a function value into a mutable global variable, and then the static Parse method defined in Head could call via that mutable global, and after the parser is actually defined, it can go and call InitializeParser to poke the value in. (If that doesn't make sense, I can spell it out in more detail.)

Brian
I'm intrigued: do you also plan to loosen linear dependency in general so that ordering of files and functions doesn't matter?
Stephen Swensen
No, no, no, no, and no. Also, no.
Brian
+1  A: 

Can't you work around this with a 3rd file which compiles after these two and extends Head with the new method?

Massif
This would work for a Head because no class is inherits after it but I’ve got also a hierarchy with ExpressionBase on top. I would like to add a Parse() method to this class as well. Of course complete hierarchy needs to be compiled before Parser. Then if create a class that inherits ExpressionBase there is no way to make it supertype to already defined classes.
proxon
You might be able to make an extension method, though.
Joel Mueller
+2  A: 

I was hoping that it is possible. After I read Brian's reply I started looking for a proper workaround. I didn't want to force library users to call any initialization methods. Therefore I came up with something diffrent.

If compiler cannot resolve dependencies at compile-time I can do it on my own at run-time. Here is definition of my DepencendciesResolver

module DependenciesResolver = 
    let GetMethod(_type, _method) =
        lazy (
            let a = System.Reflection.Assembly.GetExecutingAssembly()
            let t = a.GetType(_type)
            t.GetMethod(_method)
            )

And example of classes defined in separated files:

A.fs

namespace MutualRecursion
type A() =
    static member _b = DependenciesResolver.GetMethod("MutualRecursion.B", "b")
    static member b() = A._b.Value.Invoke(null, [||])

B.fs

nameespace MutualRecursion
type B =
    static member b() = 
        printf "Called b()"

Compilation order is: A.fs B.fs

proxon
+1  A: 

I'd do something like the following (which I suspect is roughly what Brian was proposing). Note that the user doesn't have to do any tricky initialization - the types themselves know how to "tie the knot".

Head.fs

type IParser =
  abstract Parse : string -> int // or whatever
  ...

type Head() =
  static let mutable parser = Unchecked.defaultof<IParser>
  static member internal SetParser p = parser <- p
  member x.DoSomethingCool() =
    let i = parser.Parse("something to parse")
    ...

Parser.fs

type Parser private () =
  static do
    Head.SetParser (Parser())
  interface IParser with
    member x.Parse s = 0
    ...
kvb
Have you tried to compile and run your code? I did and got **NullReferenceException** from `let i = parser.Parse("something to parse")` line. The problem lies in CLR. According to ‘CLR via C# (Dev-Pro)‘ the type constructor you used to set parser is *called before code that would create the first instance of this type or immediately before code that accesses a noninherited field or member of the class*. Therefore, if you want to force CLR to call Parser type constructor you need to access any of its members – back to drawing board.
proxon