
Mathew de Detrich schrieb:
I had the same issue zonks ago, and I resorted to using the hs-boot file method as well (which worked fine)
Which I guess brings me to my second point, is this something that GHC should do automatically when it sees circular dependencies? When I asked about it earlier on #haskell, I was told that its better that way because it discourages making bad design through circular dependencies (yet in my case and I assume the other cases as well, not using the hs-boot method would have made the design much worse). Are there any cases in particular where people would be encouraged to make interface design with circular dependencies (and that design be deemed as horrible) as opposed to what seems to be more realistic case where circular dependencies rarely crop up, and when they do they actually make the design better?
I like to compare it with Modula-3 where cyclic imports are strictly forbidden. There it is solved by dividing a module into an interface file and an implementation file. The interface can be compared with .hs-boot files: You can define types there, but you can also simply declare a type without its precise structure. Two mutually depending types in distinct modules could be defined as follows: A.i3: INTERFACE A; TYPE T <: REFANY; (* declare T to be a subtype of a general pointer *) END A. A.m3: MODULE A; (* the implementation part automatically imports the corresponding interface A without qualification *) IMPORT B; (* import the interface of module B *) REVEAL T = POINTER TO RECORD b : B.T END; END A. B.i3: INTERFACE B; TYPE T <: REFANY; END B. B.m3: MODULE B; IMPORT A; REVEAL T = POINTER TO RECORD a : A.T END; END B. Thus the circular dependency graph A -> B, B -> A is split into the non-circular dependency graph Ai -> Am, Ai -> Bm, Bi -> Am, Bi -> Bm Obviously this only works for pointers, but this is no problem since embedding a record in itself is not possible. Haskell cannot embed a record in another one, it uses pointers by default.