Whole source-tree statefull preprocessing, notion of a whole program
Boris-Barboris via Digitalmars-d
digitalmars-d at puremagic.com
Sat Apr 8 13:37:33 PDT 2017
On Saturday, 8 April 2017 at 17:57:11 UTC, Vladimir Panteleev
wrote:
> Yes; in my opinion, I think that's desirable because it is
> aligned with the unidirectional flow of information from
> higher-level components to lower-level ones, and does not
> impose a particular configuration framework onto the
> lower-level components (they only need to declare their
> configuration in terms of a POD type).
...
> A similar effect can be achieved by allowing components to
> register themselves in a static constructor (not at
> compile-time, but at program start-up).
That is definetly possible and, I would say, trivial, and this
is the most popular way. However, any run-time registration
implies run-time collections to iterate over, with obvious
performance drawbacks (minor ones in this case). We are not using
the information we have a-priory, in compile time, and make CPU
pay for it instead (either because we are too lazy (too busy) to
update sources of higher-level components (while making them a
mess), or just because our language lacks expressibility, wich is
my point).
I side with another set of virtues. Source code consists of
files. Files contain related data, concepts, functionality,
whatever. Relations between those entities must by no means be
unidirectional. What direction can you impose to concept "For
every configurable entity, be that package, module, or class, I
need to have fields in global configuration singleton"?
IMO program has good architecture, when during extensive
development for arbitrary group of programmers it takes little
time and effort to make usefull changes. It is achieved by
extensive problem field research, use of abstraction to fight
complexities, yada yada... Part of it is to make sure, that you
can extend functionality easily. Adding new subclass in one file
and registering it in two others is not hard. But there is no
fundamental reason for it to not be easier: just add subclass and
slap some fancy attribute on it, or add some preprocessor-related
field or function in it's body.
Onde-directional flow is a consequence, not a principle. It's
because languages were made this way we are used to it. When
high-level concept or idea willingly implies feedback from it's
users, there is little reason to forbid it. Especially when it
actually improves development iteration times, lowers risks of
merge conflicts etc.
Look at this mess:
https://github.com/Boris-Barboris/AtmosphereAutopilot/blob/master/AtmosphereAutopilot/GUI/AutoGui.cs#L190
It's caching code for some C# reflection-based GUI I wrote some
time ago, that defines attribute to mark class fields with in
order to draw them in pretty little debug window. Why do I have
to do this? I've got all information right in the source. All
classes that will be drawn using this GUI module are there, in
text, accessible to build system. Why can't I just write clean
code, that doesn't involve double or tripple associative array
dispatch on runtime-reflected list of subclasses and
attribute-marked fields? Answer is simple - language lacks
expressibility. I provide drawing functionality in my module. It
is generic, it is virtuous, it is concentrated in one file, it
speeds up development. However, it needs to see it's client,
beneficient, in order to draw him. And it just doesn't. Because
C#, and you need to do runtime reflections. Yet again, by
throwing away information you already have and making CPU
reconstruct it again during runtime, over and over.
Yes, all things I describe can be done efficiently by writing a
lot of boilerplate code or using some text-templating magic. I
just don't see why languages can't have that functionality
built-in.
> Then you have problems such as the instance size of a class
> changing depending on whether the code that requires the
> instance size is seen by the compiler before the code that
> modifies the instance size. I think it would cause complicated
> design problems that limit the scalability of the language.
> Even without such features, DMD had to go through a number of
> bugs to iron out the correct semantics of evaluating types
> (e.g. with "typeof(this).sizeof" inside a struct declaration,
> or recursive struct template instantiations).
I agree. I think such mechanisms must be applied very early,
hence "premixin".
> I think this is not about technical limitations, but
> intentional design choices. Allowing types to be modified
> post-declaration invalidates many contracts and assumptions
> that code may have, and make it harder to reason about the
> program as a whole. Compare with e.g. INTERCAL's COMEFROM
> instruction.
I still don't see the problem. Declaration will contain
constructs that indicate that it will be changed, like, for
example, "premixin" that iterates over array and adds fields. I
have no doubt human can read this allright.
Indeed, question of ordering is important.
Well, we have goto, it's not like sky dropped down on us.
COMEFROM breaks logical time flow. I only want two-staged
preprocessing, when in first stage I can create and manipulate
some simple, visible to preprocessor state, even if it consists
of only immutable base types and arrays of those, and then being
able to use that state as immutable variables in next stages we
already have. We already can populate class with fields from
immutable string array. All I'm wanting is ability to populate
this array using preprocessor directives from across whole
compiled program, before all other complex stuff starts. I think
that would be beautiful.
>
> UFCS is widely used in D for component programming:
>
> http://www.drdobbs.com/architecture-and-design/component-programming-in-d/240008321
I'm not stating the opposite, just sharing what I encountered on
work or during programming for fun - I mostly needed to add
fields. People may feel otherwise, but I don't see this concept
harming them in any way.
More information about the Digitalmars-d
mailing list