Recent

Author Topic: Can't we get rid off circular unit reference?  (Read 14063 times)

Pascal

  • Hero Member
  • *****
  • Posts: 932
Can't we get rid off circular unit reference?
« on: February 11, 2018, 12:22:21 pm »
I like (free)pascal very much. But one of the most anoying things is the limitations due to circular unit references.
Why do we still need this? Inside a unit we can have foward declaration which can be used to
make two classes to reference each other. Why can't this work accross units? The compiler can stop iterating thru the
units when he raches a unit he has already paresed and behave like he does with forward declarations?

I do not see the reason for this limitation! Maybe someone can give me a clue?

I know that i can use base classes but i have to cast the the base classes to the real classes in the implematation section
anyway.
laz trunk x64 - fpc trunk i386 (cross x64) - Windows 10 Pro x64 (21H2)

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 11383
  • FPC developer.
Re: Can't we get rid off circular unit reference?
« Reply #1 on: February 11, 2018, 01:07:23 pm »
I like (free)pascal very much. But one of the most anoying things is the limitations due to circular unit references.
Why do we still need this? Inside a unit we can have foward declaration which can be used to
make two classes to reference each other. Why can't this work accross units? The compiler can stop iterating thru the
units when he raches a unit he has already paresed and behave like he does with forward declarations?

The advantage of this way is that you only import something that is fairly completely defined (except a few things like inline, and changing of procedure attributes in the implementation). You never have to assume what an identifier means (and backtrack if wrong when you parse the other source) of have workarounds or assumptions in the language to disambiguate this. (and think not just of work to compile correct code, but also the effort to get clear and correct error messages)

And I never saw it as a problem to begin with. I like a nice tight declaration without code in it (C++ and Java classes look messy to me, I can live with it, but consider it suboptimal), and ordering the uses clauses quickly becomes a second nature. I only hit the limit when I'm deliberately experimenting during refactoring rarely or never during normal coding.

Anyway, once you start down a road, regardless what other languages do, you are stuck to that model, or totally redesign and rewrite the way multiple sources are combined to one program, which is one of the hardest part of the program.

Exploring what works and what not, would be more a job for a new and small and focussed  pascal compiler that can move more agilely than a twenty+ year old behemoth like Free Pascal with a zillion features that might need fixing and rethinking.

Quote
I know that i can use base classes but i have to cast the the base classes to the real classes in the implematation section
anyway.

Generics alleviate that somewhat. I was quite happy how my own container classes ported to it.
« Last Edit: February 11, 2018, 02:09:08 pm by marcov »

Thaddy

  • Hero Member
  • *****
  • Posts: 14201
  • Probably until I exterminate Putin.
Re: Can't we get rid off circular unit reference?
« Reply #2 on: February 11, 2018, 01:51:14 pm »
Note that iso mode has iirc such a feature to a certain extend. But only with all iso code. Can't mix it.
[edit] Ahh, I see, not implemented yet, since it is extended mode.
« Last Edit: February 11, 2018, 01:54:40 pm by Thaddy »
Specialize a type, not a var.

Pascal

  • Hero Member
  • *****
  • Posts: 932
Re: Can't we get rid off circular unit reference?
« Reply #3 on: February 11, 2018, 06:20:39 pm »
The advantage of this way is that you only import something that is fairly completely defined (except a few things like inline, and changing of procedure attributes in the implementation). You never have to assume what an identifier means (and backtrack if wrong when you parse the other source) of have workarounds or assumptions in the language to disambiguate this. (and think not just of work to compile correct code, but also the effort to get clear and correct error messages)

But it stays completely defined. It's just that two classes from two units can reference each other. Like it can be done with implicit forward declarations inside a unit allready.

And I never saw it as a problem to begin with. I like a nice tight declaration without code in it (C++ and Java classes look messy to me, I can live with it, but consider it suboptimal), and ordering the uses clauses quickly becomes a second nature. I only hit the limit when I'm deliberately experimenting during refactoring rarely or never during normal coding.

To have two classes reference each other you have to put them in one unit which breaks the concept of clean and simple units.
Or you have to user base classes which makes you use casts in the implementation part which breaks the concept of type safety.
Or you use helper classes.

So why not do it in a straightforward and simple way and enable circular unit references?

Anyway, once you start down a road, regardless what other languages do, you are stuck to that model, or totally redesign and rewrite the way multiple sources are combined to one program, which is one of the hardest part of the program.

Yes, but this inconvenience always forces you to find some way around the "circular unit reference problem". So you always have to build some kind of hack!
Imho this is contradictory to the clean concept of the pascal language.

Exploring what works and what not, would be more a job for a new and small and focussed  pascal compiler that can move more agilely than a twenty+ year old behemoth like Free Pascal with a zillion features that might need fixing and rethinking.

Shouldn't this be quite easy to implement? The compiler just has to stop parsing a unit that he already knows. So unit A can use classes of unit B and vice versa.
laz trunk x64 - fpc trunk i386 (cross x64) - Windows 10 Pro x64 (21H2)

Thaddy

  • Hero Member
  • *****
  • Posts: 14201
  • Probably until I exterminate Putin.
Re: Can't we get rid off circular unit reference?
« Reply #4 on: February 11, 2018, 08:00:18 pm »
Note for allowing interface sections with multiple implementation sections we already have inc files.
The multi-platform support is even built on that.
Personally
- I can live with how it is now.
- I use include files when appropriate.
- If you have many circular references on the same platform your architecture is probably wrong and we can probably set you on a better track when we see some code..

Problem solved.
Specialize a type, not a var.

Pascal

  • Hero Member
  • *****
  • Posts: 932
Re: Can't we get rid off circular unit reference?
« Reply #5 on: February 12, 2018, 06:05:58 am »
Note for allowing interface sections with multiple implementation sections we already have inc files.
The multi-platform support is even built on that.
Personally
- I can live with how it is now.
- I use include files when appropriate.
- If you have many circular references on the same platform your architecture is probably wrong and we can probably set you on a better track when we see some code..

Problem solved.

I am talking about one platform. And i don't think that my architecture is wrong. The problem is that you always have to refactor your class layout when
you need to have two classes which need to reference each other. And this is only needed to circumvent the "circular unit problem". In Free Pascal it is
possible to have two classes reference each other as long as the two classes are in the same unit. So you are right, i can use include files but in fact that
will lead to one big unit.

I do not really see the point why that, what works inside on unit, is not allowed across two units. This really blows up the class layout and forces you to
build in some hacks. What is wrong with classes referencing each other when they are in different units instead of the same one?
You can not just say it's bad design!
There might have been reasons in the past why it should not be possible to have two units uses each other in the interface section. But i do not think
that we still need it today.

When the compiler finds a unit it already has parsed it can break stepping into this unit's interface section and proecess with the next used unit and
everything which is needed for building types should be there. If the parser the finds a class which is not defined it can do an implicit forward declaration and see if it will be solved until the interface section.
And that should be enough.

If i would know the compiler sources i would have done a try already. But unfortunately i do not! So maybe someone of the FPC team could give this idea a try?
laz trunk x64 - fpc trunk i386 (cross x64) - Windows 10 Pro x64 (21H2)

Handoko

  • Hero Member
  • *****
  • Posts: 5130
  • My goal: build my own game engine using Lazarus
Re: Can't we get rid off circular unit reference?
« Reply #6 on: February 12, 2018, 06:34:30 am »
Personally
- I can live with how it is now.
- I use include files when appropriate.
- If you have many circular references on the same platform your architecture is probably wrong and we can probably set you on a better track when we see some code..

I know include files, but I never use it. Can you explain on what cases it is good to use include files instead of make them as units or simply merge them into the code?

Thaddy

  • Hero Member
  • *****
  • Posts: 14201
  • Probably until I exterminate Putin.
Re: Can't we get rid off circular unit reference?
« Reply #7 on: February 12, 2018, 06:41:47 am »
Suppose you have optimized implementations for arm and x86. You can include a single interface include file and two implementation include file.
If the interface is well defined you need just a single interface, platform independent, and implement the platform implementations as multiple includes.
That way the interface stays unified. Look at the structure of the rtl. Many such examples.
Specialize a type, not a var.

Handoko

  • Hero Member
  • *****
  • Posts: 5130
  • My goal: build my own game engine using Lazarus
Re: Can't we get rid off circular unit reference?
« Reply #8 on: February 12, 2018, 06:52:47 am »
Okay, I understand now. Include files are good when used on writing cross platform codes. Thank you.

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 11383
  • FPC developer.
Re: Can't we get rid off circular unit reference?
« Reply #9 on: February 12, 2018, 09:51:28 am »
The advantage of this way is that you only import something that is fairly completely defined (except a few things like inline, and changing of procedure attributes in the implementation). You never have to assume what an identifier means (and backtrack if wrong when you parse the other source) of have workarounds or assumptions in the language to disambiguate this. (and think not just of work to compile correct code, but also the effort to get clear and correct error messages)

But it stays completely defined. It's just that two classes from two units can reference each other. Like it can be done with implicit forward declarations inside a unit allready.

Only if you would forward define it, which is the workaround thing again. And then only for reference types. (interfaces and classes, e.g. not TP objects)

Quote
To have two classes reference each other you have to put them in one unit which breaks the concept of clean and simple units.
Or you have to user base classes which makes you use casts in the implementation part which breaks the concept of type safety.
Or you use helper classes.

Or maybe even generics.

Quote
So why not do it in a straightforward and simple way and enable circular unit references?

I think it is not straightforward as implementation goes, and you will be dealing a long time with fallout. Moreover you will have to rewrite a significant and difficult part.

I'm not stopping you to try to come with a patch, but I would first research the consequences first.

Yes, but this inconvenience always forces you to find some way around the "circular unit reference problem". So you always have to build some kind of hack!

Mutual reference is not "clean" by definition, and breaking it is actually clean. Doing it straightforward for simple cases is actually the "hack". Some languages allow it for ease of use, but it is trouble from a language design view.

Shouldn't this be quite easy to implement? The compiler just has to stop parsing a unit that he already knows. So unit A can use classes of unit B and vice versa.

Anything that goes over unit borders and is not quite defined is usually a nightmare.  And soon the bugs for corner cases with this functionality will start to pile up. (somebody will try to do a 3-unit cycle etc) Anybody taking this on better be prepared to monitor it for a long time.

Pascal

  • Hero Member
  • *****
  • Posts: 932
Re: Can't we get rid off circular unit reference?
« Reply #10 on: February 12, 2018, 11:05:23 am »
I'm not stopping you to try to come with a patch, but I would first research the consequences first.

That's my problem! I would like to do, but my knowledge of the compiler sources is very limited atm. If someone could lead me to the
significant places (unit interface parsing, forward declarations) i would like to give it a try.

Btw: Is there an up to date wiki about the compiler and its inner structure?

Mutual reference is not "clean" by definition, and breaking it is actually clean. Doing it straightforward for simple cases is actually the "hack". Some languages allow it for ease of use, but it is trouble from a language design view.

From the language point of view i can agree with you. But from the ease of use point of view i would like to see this "hack" across units.

Anything that goes over unit borders and is not quite defined is usually a nightmare.  And soon the bugs for corner cases with this functionality will start to pile up. (somebody will try to do a 3-unit cycle etc) Anybody taking this on better be prepared to monitor it for a long time.

Atm i am just thinking about simple type references.

The problem here is that while parsing the used units the parser will find types which will be resolved in the interface of the unit it is parsing. So
we would need something like implicit forward declarations across units as explicit forward declarations will not work here (only work per unit).
laz trunk x64 - fpc trunk i386 (cross x64) - Windows 10 Pro x64 (21H2)

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 11383
  • FPC developer.
Re: Can't we get rid off circular unit reference?
« Reply #11 on: February 12, 2018, 11:15:58 am »

As an example of a corner case, think of things like

 
Code: Pascal  [Select][+][-]
  1.    type
  2.       TFoo = class
  3.                      anOhterFoo  : TOtherFoo;
  4.                      property bah : integer read anohterfoo.bah;
  5.                   end;
  6.  

After TOtherfoo is defined in the other unit, you need to make sure that it has a "bah". Moreover you can't generate code till you have the full definition of TOtherFoo. Which can then have a circular reference much larger than 2 units, which already gives me a headache THINKING of it, let alone the implementation.

Pascal

  • Hero Member
  • *****
  • Posts: 932
Re: Can't we get rid off circular unit reference?
« Reply #12 on: February 12, 2018, 12:57:04 pm »

As an example of a corner case, think of things like

 
Code: Pascal  [Select][+][-]
  1.    type
  2.       TFoo = class
  3.                      anOhterFoo  : TOtherFoo;
  4.                      property bah : integer read anohterfoo.bah;
  5.                   end;
  6.  

After TOtherfoo is defined in the other unit, you need to make sure that it has a "bah". Moreover you can't generate code till you have the full definition of TOtherFoo. Which can then have a circular reference much larger than 2 units, which already gives me a headache THINKING of it, let alone the implementation.

If all interfaces are okay then everything should be solved until the implementation part. If not, generate an error like:
Code: Text  [Select][+][-]
  1. unit1.pas(3,36) Error: Forward type not resolved "TOtherClass"
or
Code: Text  [Select][+][-]
  1. unit1.pas(4,61) Error: Unknown record field identifier "bah"

So pretty everything like it is done with forward declarations now.

The parser just has to insert an implicit forward declaration when it reaches an undefined type while parsing the interfaces of the used units.
laz trunk x64 - fpc trunk i386 (cross x64) - Windows 10 Pro x64 (21H2)

Thaddy

  • Hero Member
  • *****
  • Posts: 14201
  • Probably until I exterminate Putin.
Re: Can't we get rid off circular unit reference?
« Reply #13 on: February 12, 2018, 01:08:57 pm »
So pretty everything like it is done with forward declarations now.

The parser just has to insert an implicit forward declaration when it reaches an undefined type while parsing the interfaces of the used units.
Well, as Marco implicitly explained this is not the case:
Suppose unitA has a type or procedure declared, like say, TPoint. And unitB re-declares TPoint. Now, the parser can only resolve its use based on unit order..
What you propose does not work with forward declarations, probably not even with a multi-pass compiler. At parse time the compiler does not know which scope you mean: A or B.
This is already a common scenario in the existing code base, where clashes already occur and need to be resolved by explicit scoping: unitB.Tpoint, unitA.Tpoint.
Maybe a record is not a proper example but the same goes for classes, procedures and functions.
« Last Edit: February 12, 2018, 01:12:04 pm by Thaddy »
Specialize a type, not a var.

Pascal

  • Hero Member
  • *****
  • Posts: 932
Re: Can't we get rid off circular unit reference?
« Reply #14 on: February 12, 2018, 01:16:22 pm »
So pretty everything like it is done with forward declarations now.

The parser just has to insert an implicit forward declaration when it reaches an undefined type while parsing the interfaces of the used units.
Well, as Marco implicitly explained this is not the case:
Suppose unitA has a type or procedure declared, like say, TPoint. And unitB re-declares TPoint. Now, the parser can only resolve its use based on unit order..
What you propose does not work with forward declarations, probably not even with a multi-pass compiler. At parse time the compiler does not know which scope you mean: A or B.
This is already a common scenario in the existing code base, where clashes already occur and need to be resolved by explicit scoping: unitB.Tpoint, unitA.Tpoint.
Maybe a record is not a proper example but the same goes for classes, procedures and functions.

Yes, but this is another issue. I unfortunately do not see what this has to do with my topic?
laz trunk x64 - fpc trunk i386 (cross x64) - Windows 10 Pro x64 (21H2)

 

TinyPortal © 2005-2018