Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / fpc-pascal / 2008-01

child units (was: dot within unit file name)

9 messages · 4 people · –

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

John Stoneham
Vinzent Hoefler wrote 5 lines
On Friday 18 January 2008 20:12, John Stoneham wrote: [snip] So, the only difference is that Ada plays it safe and complains about ambiguities while a Pascal compiler tries to resolve the ambiguity by itself

Perhaps that's true with regard to name resolution, but Ada's package system has a significant feature which Pascal's module system lacks: child packages. Conceptually, a child package is the same thing as a nested package:

package Parent is
...
package Child is
...

   end Child;

...
end Parent;

Now, the nested "Child" can be referenced outside "Parent" as "Parent.Child". This has much more to offer than simply another level (sometimes very necessary) of organization to a complex program. For example, the private part of a child package has access to it's parent's private members as well. The visibility rules are similar to those that relate to inheritance (via private, protected, and public sections). This adds a level of encapsulation that is very desirable in a complex program.

Normally, child packages are not defined simply as nested packages within the parent itself (although they can be). Usually, they are defined in their own file. The dot-notation in the package declaration tells the compiler that "Child" is a child package of "Parent":

package Parent.Child is
...
end Parent.Child;

When you have a lot of Ada programming experience, the flexibility of child packages adds enormously to program structure and design. In contrast, the single-level module system of Pascal feels very limiting (however, Ada is so much more restrictive in other areas that Pascal is overall a breath of fresh air).

I think it would be fantastic if Free Pascal could add child units as a language extension (of course this would reduce portability -- although on the other hand it might make porting Ada programs easier). It's difficult trying to describe the benefits to someone who has never used them (how do you know you're missing something if you've never used it and therefore never felt a need for it?)

Marco van de Voort
to John Stoneham
Show quoted text 6 lines
I think it would be fantastic if Free Pascal could add child units as a language extension (of course this would reduce portability -- although on the other hand it might make porting Ada programs easier). It's difficult trying to describe the benefits to someone who has never used them (how do you know you're missing something if you've never used it and therefore never felt a need for it?)

I've used Modula2, and know nested modules. However I haven't missed them.

I have missed opague types and forcedly qualified importing (from M2)

M2 doesn't have the nested module "put in arbitrary other file" system though. Such system would have the same problems with M2 as with Pascal.

Vinzent Hoefler
to Marco van de Voort
Marco van de Voort wrote 9 lines
> I think it would be fantastic if Free Pascal could add child units > as a language extension (of course this would reduce portability -- > although on the other hand it might make porting Ada programs > easier). It's difficult trying to describe the benefits to someone > who has never used them (how do you know you're missing something > if you've never used it and therefore never felt a need for it?) I've used Modula2, and know nested modules. However I haven't missed them.

Well, I've missed them. (Although I don't remember Modula-2 too well, so I can't say how much similar they are to Ada's child packages).

But child packages are really great for extending and adding functionality without even touching existing source code.

I have missed opague types and forcedly qualified importing (from M2)

Well, I miss them too. As I'll show you in a moment that they even go hand in hand with child packages.

M2 doesn't have the nested module "put in arbitrary other file" system though. Such system would have the same problems with M2 as with Pascal.

Well, you'd definitely need to store not only the information about the interface part in the .ppu file, but also the implementation part, so that any child package can access and use this information.

Because without separate compilation(-units), child packages are relatively useless. After all the real big advantage is that you can extend a package without touching the existing one.

The classic example from the Ada95 Rationale, for your convinience converted into "Pseudo Pascal":

--- 8< ---
unit

      ComplexNumbers;
   interface
   type
      Complex; // Incomplete (opaque) type.
               // Further specified in implementation part.
   operator + (Left, Right: Complex) : Complex;
   // similarly "-", "*" and "/"
   function CartesianToComplex (Real, Imag : Double) : Complex;
   function RealPart (X : Complex) : Double;
   function ImagPart (X : Complex) : Double;
   implementation
      ...

end {ComplexNumbers}.
--- 8< ---

Now the only ways to add functionality to the unit would be to

1) make "Complex" a non-opaque type, and add another unit "ComplexNumbersPolar", or
2) change the original "ComplexNumbers" unit by adding the newly needed funtionality, thus forcing recompilation or - even worse - possibly introducing bugs to already tested code.

Approach 1) has the disadvantage that I still can't use internal subroutines declared in the original "ComplexNumbers" unit. This usually forces me to add another package which exposes this formerly private part and declare that this private package *should* only be used by this, or that, or any extending unit, but not "normal" user code. As with any such suggestions, they can't be forced to other developers. ;)

Now, if we had child packages all this could be avoided, we simply add a subpackage like this:

--- 8< ---
unit

      ComplexNumbers.Polar;
   interface
   function PolarToComplex (R, Theta : Double) : Complex;
   function Abs (Right : Complex) : Double;
   function Arg (X     : Complex) : Double;
   implementation
      ...
      // This part now has access to the (opaque) type
      // "Complex" just like "ComplexNumbers" has.
      // IOW: Like it was declared in here.

end {ComplexNumbers.Polar}.
--- 8< ---

No touching of the original "Complex_Numbers", no recompilation for units already using it and the knowledge that the original code has not changed a bit. Additionally the inner working of the type "Complex" is still not exposed to any other unit.

And yes, I miss that.

Vinzent.

Marco van de Voort
to Vinzent Hoefler
Show quoted text 15 lines
On Monday 21 January 2008 18:45, Marco van de Voort wrote: > I have missed opague types and forcedly qualified importing (from > M2) Well, I miss them too. As I'll show you in a moment that they even go hand in hand with child packages. > M2 doesn't have the nested module "put in arbitrary other file" > system though. Such system would have the same problems with M2 as > with Pascal. Well, you'd definitely need to store not only the information about the interface part in the .ppu file, but also the implementation part, so that any child package can access and use this information.

Then they are different from Modula2. Since in Modula2 you can only process the inner module as part of the whole module. They are not separately compilable and are part of the implementation (of course you can inc though)

Because without separate compilation(-units), child packages are relatively useless. After all the real big advantage is that you can extend a package without touching the existing one.

Maybe. Keep in mind that opague is roughly comparable with

type tmyopaguetype = type pointer;

Since the interface must 100% define the interface so that it can be used, which is a hard rule with Pascal and Modula2.

This makes e.g. the operator overloading already hard, since it can't be a record (even variant).

I think also that it puts a bomb under your example in other ways.

Now the only ways to add functionality to the unit would be to
Show quoted text 6 lines
No touching of the original "Complex_Numbers", no recompilation for units already using it and the knowledge that the original code has not changed a bit. Additionally the inner working of the type "Complex" is still not exposed to any other unit. And yes, I miss that.

It is a bit the .NET class helper (partial classes) trick in a modular setting. I don't like that either. The gain you get from your old and trusted code, you loose in complexity and clarity of overview in the module system (an import of complexnumbers in one place might suddenly react differently due to the partial module/classhelper)

Not to say that it also hugely complicates the compiler, since it has to factor the possible existance of these slave units in any unit usage.

Vinzent Hoefler
to Marco van de Voort
Marco van de Voort wrote 6 lines
Maybe. Keep in mind that opague is roughly comparable with type tmyopaguetype = type pointer; Since the interface must 100% define the interface so that it can be used, which is a hard rule with Pascal and Modula2.

Hmm, what if

--- 8< ---

interface
type
   tMyOpaqueType; // Incomplete.
private
type
   // Complete type declaration, but privately.
   tMyOpaqueType = record ... end;

--- 8< ---

would be possible?

The type is still declared in the interface part; yet, because it's in the private part of the interface, no other module can use it's inner structure.

This makes e.g. the operator overloading already hard, since it can't be a record (even variant).

Well, yes and no. As I wrote, you'd need to store that information in the unit file. And it just implies that the type will always be a reference type, because that's about the only way to ensure consistent usage across multiple compilation units. Of course, at the low level, this means pointer.

It is a bit the .NET class helper (partial classes) trick in a modular setting.

From what I understood, those partial classes are different. They don't extend functionality, they split it (or maybe, the call it "delegation of responsibility"). It seems like a concept to both avoid the classic multiple inheritance problems and the issues with interfaces implementation.

Maybe I got that totally wrong, but the name implies that I can't use a "partial class" in real world code, because it's, well, partial.

That's not the case here. Each module provides its very own set of (working) functionality. Of course, any child package depends on its respective parent package, but it's not that you need both to do something useful.

I don't like that either. The gain you get from your old and trusted code, you loose in complexity and clarity of overview in the module system

Actually I don't understand that part of your argument. Any software system that contains more than a couple of lines of code is complex or is going to be. That's why modularization was invented in the first place: To be able to fight and handle that complexity in some way.

I mean, I'm quite sure, you're not argueing for an "all source in one large file" approach... so instead of (shared) include files, child package would provide compiler checked extensibility.

IMO, it's much like the package system for the compiler. At least how it's laid out: You have FCL that consists of FCL.Base, FCL.XML, ...

(an import of complexnumbers in one place might suddenly react differently due to the partial module/classhelper)

But that's the case already. Even the order of imports is semantically relevant (or well, it might be). Or what's with that "reuse" keywords for the uses clause?

Not to say that it also hugely complicates the compiler, since it has to factor the possible existance of these slave units in any unit usage.

Hmm. I don't see that. If I'd put the implementation all in the interface part, the compiler can handle it well already. The only difference would be some sort of flag that this or that information can only be used if it's a child package...
Don't get me wrong, I don't want to say that it would be easy to change the compiler to handle this, but I wouldn't say "hugely complicated", neither. It does most of the work already. ;)

Vinzent.

John Stoneham
to Marco van de Voort: Then they are different from Modula2. Since in Modula2 you can only process the inner…
Marco van de Voort wrote
[snip] ... an import of complexnumbers in one place might suddenly react differently due to the partial module/classhelper)

Then you misunderstand the concept. While it's true that child packages are similar to nested packages (and can be written that way if you want), if they are written as a separate compilation unit the parent package is unaffected, and that also includes code which imports the parent package. The child package is *not* imported when the parent package is imported, unless it is specifically written as a nested package within the parent source. Code which imports the parent, "ComplexNumbers" in this example, does not get the added functionality of "ComplexNumbers.Polar". There is no chance that code importing the original package would "suddenly react differently" just because the child package exists. In order to expose code to the child package, you have to specifically import it.

Again, it is very similar to inheritance. When using a TShape class, you don't worry about whether the extended functionality in TSquare or TCircle is somehow going to affect your own code, unless you specifically use those classes.

Not to say that it also hugely complicates the compiler, since it has to factor the possible existance of these slave units in any unit usage.

No it doesn't. Unless you specifically "use Parent.Child;" the compiler doesn't have to go looking for "Child" at all, it doesn't have to factor in the possibility of whether Child exists or not, just as when you use a "Shape" unit it doesn't have to wonder whether there is also a "Square" or "Circle" unit somewhere, unless they are imported. If you only "use Parent;" the compiler won't even compile the "Parent.Child" unit, even if it exists in the same working directory.

Marco van de Voort
to Vinzent Hoefler: Hmm, what if --- 8< --- interface type tMyOpaqueType; // Incomplete. private type //…
Show quoted text 28 lines
> type tmyopaguetype = type pointer; > > Since the interface must 100% define the interface so that it can be > used, which is a hard rule with Pascal and Modula2. Hmm, what if --- 8< --- interface type tMyOpaqueType; // Incomplete. private type // Complete type declaration, but privately. tMyOpaqueType = record ... end; --- 8< --- would be possible? The type is still declared in the interface part; yet, because it's in the private part of the interface, no other module can use it's inner structure.

No. Completely defines (most notably its size, probably also its alignment if it applies). In practice, nearly all M2 compilers only support pointers

Show quoted text 5 lines
> This makes e.g. the operator overloading already hard, since it can't > be a record (even variant). Well, yes and no. As I wrote, you'd need to store that information in the unit file.

You can't. Sb might already be needing the interface before the implementation is compiled. This is how interface - implementation uses circular references are solved.

And it just implies that the type will always be a reference type, because that's about the only way to ensure consistent usage across multiple compilation units. Of course, at the low level, this means pointer.

Yes.

Show quoted text 6 lines
> It is a bit the .NET class helper (partial classes) trick in a > modular setting. From what I understood, those partial classes are different. They don't extend functionality, they split it (or maybe, the call it "delegation of responsibility").

As far as I see it they add functionality to a base container. In partial classes case a class, and in Ada's case a module.

Both also seem to require to include the relevant "helper" module explicitely to unlock the extension.

That's not the case here. Each module provides its very own set of (working) functionality. Of course, any child package depends on its respective parent package, but it's not that you need both to do something useful.

Also not that different, though only to my admitted limited knowledge.

Show quoted text 12 lines
> I don't like that either. The gain you get from your > old and trusted code, you loose in complexity and clarity of overview > in the module system Actually I don't understand that part of your argument. Any software system that contains more than a couple of lines of code is complex or is going to be. That's why modularization was invented in the first place: To be able to fight and handle that complexity in some way. I mean, I'm quite sure, you're not argueing for an "all source in one large file" approach... so instead of (shared) include files, child package would provide compiler checked extensibility.

I don't like the idea of modules being mutable (even if it only means extension of the container)

IMO, it's much like the package system for the compiler. At least how it's laid out: You have FCL that consists of FCL.Base, FCL.XML, ...

I don't see any relation. FCL only exists as parts of names and in documentation. On src level there are only the packages, and they are not spelled above (where they imply some hierarchic system), but with dashes that aren't separators.

Show quoted text 5 lines
> (an import of complexnumbers in one place might > suddenly react differently due to the partial module/classhelper) But that's the case already. Even the order of imports is semantically relevant (or well, it might be

But does exuse throwing out all restraint?

Or what's with that "reuse" keywords for the uses clause?

I'm no fan of that. But at least it doesn't require scanning and indexing all files up front. (though, admitted, neither do child packages, I just fail to see their use)

They do have in common that a use case that would interest me could swing me. And reuse seems to make some sense in breaking up large header sets, though I'm not really convinced yet.

Show quoted text 11 lines
> Not to say that it also hugely complicates the compiler, since it has > to factor the possible existance of these slave units in any unit > usage. Hmm. I don't see that. If I'd put the implementation all in the interface part, the compiler can handle it well already. The only difference would be some sort of flag that this or that information can only be used if it's a child package... Don't get me wrong, I don't want to say that it would be easy to change the compiler to handle this, but I wouldn't say "hugely complicated", neither. It does most of the work already. ;)

(I assumed a child package would be autoloaded into a module that imported the base package. Apparantly I'm wrong there)

Tony Pelton
to John Stoneham: Perhaps that's true with regard to name resolution, but Ada's package system has a…
John Stoneham wrote 15 lines
On Jan 21, 2008 1:05 AM, Vinzent Hoefler wrote: > On Friday 18 January 2008 20:12, John Stoneham wrote: Perhaps that's true with regard to name resolution, but Ada's package system has a significant feature which Pascal's module system lacks: child packages. Conceptually, a child package is the same thing as a nested package: package Parent is ... package Child is ... end Child; ... end Parent;

this looks exactly like Java inner classes.

Now, the nested "Child" can be referenced outside "Parent" as "Parent.Child".

java :

org.freepascal.example.OuterClass.InnerClass

defined in a file called "OuterClass.java"

This has much more to offer than simply another level (sometimes very necessary) of organization to a complex program.

yes.

among other things, in the java world, it makes for a great way to be able to encapsulate and control dependent objects that belong to the implementation that encapsulates them.

data structure like classes for example that really don't have any business outside of the parent class.

and even if those child classes are intended to escape out of the parent class via a return type, it still keeps the namespace and file system much cleaner, again, where the child has no other general use in the application.

Show quoted text 6 lines
For example, the private part of a child package has access to it's parent's private members as well. The visibility rules are similar to those that relate to inheritance (via private, protected, and public sections). This adds a level of encapsulation that is very desirable in a complex program.

yes, this sounds identical to how java does this as well.

FWIW.

Tony

X-SA user ? 0.5.1 is out ! XData 0.1 for X-SA is out ! http://x-plane.dsrts.com
Vinzent Hoefler
to Marco van de Voort: No. Completely defines (most notably its size, probably also its alignment if it…
Marco van de Voort wrote 31 lines
> > type tmyopaguetype = type pointer; > > > > Since the interface must 100% define the interface so that it can > > be used, which is a hard rule with Pascal and Modula2. > > Hmm, what if > > --- 8< --- > > interface > > type > tMyOpaqueType; // Incomplete. > > private > > type > // Complete type declaration, but privately. > tMyOpaqueType = record ... end; > > --- 8< --- > > would be possible? > > The type is still declared in the interface part; yet, because it's > in the private part of the interface, no other module can use it's > inner structure. No. Completely defines (most notably its size, probably also its alignment if it applies). In practice, nearly all M2 compilers only support pointers

Yes, but that's what it does, defining it, I mean. The "private" keyword would only mark it as "can't use, if you're not a child unit". It is still in the interface, and thus "completely defined" AFAICS.

Show quoted text 8 lines
> > This makes e.g. the operator overloading already hard, since it > > can't be a record (even variant). > > Well, yes and no. As I wrote, you'd need to store that information > in the unit file. You can't. Sb might already be needing the interface before the implementation is compiled.

Yes, I know that. (Ada is a lot more strict about it, where interface (spec) and implementation (body) are even in different files and you can actually compile your code without the body available.)

You may have overlooked it, but the "incomplete type" is already completed while still in the interface part. To be more clear on the subject, let me try a more complete example again:

--- 8< ---
unit

   Foo;
interface
   type
      Bar = record; // Incomplete for now.
   procedure FooBar (Arg : Bar);
private
   type
      Bar =
      record
         X : Integer;
         Y : Double;
      end;
implementation
    ...
end;

--- 8< ---

So it's still in the interface, the compiler has all the information it needs (even if only the interface is available at time of compilation), only that the inner working of the type is marked as sort of "non-public".
It's more a question of the type system, rather than trickery with the symbol tables.
Or you may view it as a special form of a forward declaration.

Show quoted text 9 lines
> > It is a bit the .NET class helper (partial classes) trick in a > > modular setting. > > From what I understood, those partial classes are different. They > don't extend functionality, they split it (or maybe, the call it > "delegation of responsibility"). As far as I see it they add functionality to a base container. In partial classes case a class, and in Ada's case a module.

Maybe. I'm not a .NET-expert, but then the name is understood that a class could be implemented in several different modules (I understood "functionality sets") and only upon completion of all modules/functionality, the resulting class as a whole is usable.

If that understanding is wrong, then I fail to see what "partial classes" do differently than a normal class. In the end, classes are there for extending them, that's more or less the whole point of OOP.

Both also seem to require to include the relevant "helper" module explicitely to unlock the extension.

Well, in Ada there are no "helper" modules, so this must be wrong for at least this case. ;)

> > I don't like that either. The gain you get from your > > old and trusted code, you loose in complexity and clarity of > > overview in the module system >

[...]

Show quoted text 7 lines
> > I mean, I'm quite sure, you're not argueing for an "all source in > one large file" approach... so instead of (shared) include files, > child package would provide compiler checked extensibility. I don't like the idea of modules being mutable (even if it only means extension of the container)

The modules do *not* change in any way. You can extend them only by declaring it that way and need to put them in the "uses" clause if you want to use that "extended functionality".

It's no more "mutable" than adding "Math_Complex" as another unit to your source file as "extension" to the "Math" unit.

Actually, it only takes the modularization to another level and makes it even more flexible.

Show quoted text 5 lines
> IMO, it's much like the package system for the compiler. At least > how it's laid out: You have FCL that consists of FCL.Base, FCL.XML, > ... I don't see any relation.

Well, I do. YMMV.

FCL only exists as parts of names and in documentation. On src level there are only the packages, and they are not spelled above (where they imply some hierarchic system), but with dashes that aren't separators.

Yes, I tried to make an example, how such a system would look at source level. For me, I 1:1 relation between the documentation of an intended hierarchy and its appropriate source would be a good thing.

Show quoted text 7 lines
> > (an import of complexnumbers in one place might > > suddenly react differently due to the partial module/classhelper) > > But that's the case already. Even the order of imports is > semantically relevant (or well, it might be But does exuse throwing out all restraint?

On the contrary, I'm trying to throw it in by making it possible to make some things more private than they are now.

> Or what's with that "reuse" keywords for the uses clause? I'm no fan of that.

Glad to hear.

But at least it doesn't require scanning and indexing all files up front. (though, admitted, neither do child packages, I just fail to see their use)

Well, I used child packages to a great extend, so I am bound to see their use. And I missed them, because a hierarchical source layout for certain modules is sometimes very desirable, even if only in respect to possible name clashes.

They do have in common that a use case that would interest me could swing me. And reuse seems to make some sense in breaking up large header sets, though I'm not really convinced yet.

It saves about 5 minutes of typing work in a typical project. But this gets lost in the noise once you're trying to find out, which import fails your compile. While I'm at it, I ran into an interesting issue yesterday:

-- 8< -- random.pas --
unit

   Random;
interface
implementation

end {Random}.
-- 8< --

-- 8< -- bp.pas --
unit

   BP;
uses
   Random;
interface
implementation
end {BP};
-- 8< --

-- 8< -- main.pas
uses

   BP;
begin
   WriteLn (Random (1.0));
end {Main}.
-- 8< --
|Compiling main.pas
|main.pas(5,13) Error: Identifier not found "Random"
|main.pas(5,20) Error: Illegal expression

Is this a bug in the compiler (I used fpc2.0.0)? Or is there any reason, why this should happen? The unit "Random" has not been used by the main program, so I would expect it to only find the "System.Random" identifier here, not the identifier of a unit, I haven't even put in the "uses" clause.

If I move the "uses Random" from the interface into the implementation part of unit "BP", it compiles again, so it seems to be a problem with the interface/implementation visibility.

(I assumed a child package would be autoloaded into a module that imported the base package. Apparantly I'm wrong there)

Yes, you are. Of course this is not allowed to happen. Only explicitely declared "uses" are allowed.

Vinzent.