What's the best way around the cycle/recursive limitation on module imports?
I keep running into this issue as my coding style is to have references back to "owner" objects. The best I've been able to do is the code that follows, but I don't really like it. Is there another solution anyone is using?
(I'm hoping someone says, "Oh yea, there's a forward declaration for types like there is for procs.")
File: ModuleA.nim
type
ItemTemp = ref object
ItemA* = ref object
FaBee: ItemTemp # I wanted "aBee: ItemB"
import ModuleB
proc NewItemA*: ItemA =
new(result)
result.FaBee = cast[ItemTemp](NewItemB())
proc aBee(this: ItemA): ItemB =
return cast[ItemB](this.FaBee)
>>> File: ModuleB.nim
import ModuleA
type
ItemB* = ref object
anAyee: ItemA
proc NewItemB*: ItemB =
new(result)
You can put all your types in the same file. After all, if they are recursive why do you want to split them apart? By scattering the pieces apart you are not helping modularity or anything.
AllTypes.nim
type
ItemB* = ref object
anAyee: ItemA
ItemA* = ref object
FaBee*: ItemB
ModuleA.nim
import AllTypes, ModuleB
proc NewItemA*: ItemA =
new(result)
result.FaBee = NewItemB()
proc aBee(this: ItemA): ItemB =
return this.FaBee
ModuleB.nim
import AllTypes
proc NewItemB*(): ItemB =
new(result)
That works, but it feels just as weird to my OO eyes. I'm so used to keeping "classes" with the methods that are associated with them.
After all, if they are recursive why do you want to split them apart?
I think "recursive", while technically correct, doesn't reflect real usage. And my example was a bit contrived to show my potential solution. A typical scenario would be something more along the lines of the following code. Rinse and repeat dozens of times in a typical application.
#module Series.nim
type
Series = ref object
FBar: seq[Bar]
# lots of proc(this.Series ...) here
#module Bar.nim
type
Bar = ref object
open, high, low, close: float
ownerSeries: Series # LINK BACK TO OWNER
# lots of proc(this.Bar ...) here
Maybe putting related types in one module is typical for Nimrod. If so, I could get used to it. I'll have to scan the source and see how often the pattern appears.
Not quite sure what you're saying ... that Nimrod does or does not have a proper module system? Cycle limitations and forward references are definitely a throwback to Delphi though. I've not seen this limitation any of the ten or so languages I've looked at from this latest wave of languages. I don't know if the selective import feature Nimrod supports is useful or not yet. I have already taken advantage of the selective export (for strutils.`%`) and do like it for convenience, but I don't know about this "pass through" use in a corporate setting.
As far as "grouping similar things," I would never group Series in a module with Bar. There are literally dozens of different Series and they don't all belong grouped the same module. And while there are fewer "Bar" type objects, you can't necessarily say that a given Bar implementation always always goes with a particular Series. So this being, for me, the case that makes the rule, I've decided to stick with the cast workaround even while learning Nimrod rather than force artificial groupings.
NewGuy: That's how it should be done in modern languages with proper module systems. This is how you'd do it in pretty much every language other than C++ or Java (which are insane in the membrane in this respect). Modules are about grouping similar things.
Modules can be about grouping similar things and you can still have types in one module referring to types in another.
For example, assume that you are implementing a compiler and have objects that represent expressions and objects that represent types. It's not exactly outlandish to put all the expression stuff in one module and all the type stuff in another, even though types and expressions can reference each other.
What's worse in Nimrod is that mutually recursive type definitions need to occur in the same type declaration; i.e., you can't even use include to at least textually separate the two.
The only practical workaround is parametrization, e.g.:
# module a
type
TApar*[T] = ref object
x: T
# module b
import a
type
TB* = ref object
y: TApar[TB]
type
TA* = TApar[TB]
Jehan: I thought this was very clever! I'd have never guessed it would compile. However, when I try to declare a variable of any of the types defined I get an error message: "SIGSEGV: Illegal storage access. (Attempt to read from nil?)"
Are you able to declare a variable using any of these types?
Yes. Are you initializing them? Remember, they're references. E.g.:
# module a
type
TApar*[T] = ref object
x*: T
# module b
import a
type
TB* = ref object
y*: TApar[TB]
type
TA* = TApar[TB]
var x: TA
var y: TB
new x
new y
x.x = y
y.y = x
echo repr(x)
yields the following for me (addresses may vary):
ref 0x107ed1050 --> [x = ref 0x107ed1068 --> [y = ref 0x107ed1050]]
Also, this is with a fairly recent GitHub version; I remember that there was some sort of bug with 0.9.2 and using ref object with parametric types that Araq fixed a while ago.
P.S.: I can't really take the credit for the solution. This is a common workaround in OCaml/F#, which have the exact same problem.
A workaround in 0.9.2 is to split the ref from the actual type declaration, e.g.:
type
TAObj[T] = object
x*: T
TApar*[T] = ref TAObj[T]
There are substantial stability improvements in 0.9.3 that are still missing from 0.9.2, unfortunately. It would be great if there were nightly tarballs available.