Re: confusion about include

new topic     » goto parent     » topic index » view thread      » older message » newer message
Jerry_Story said...

The Euphoria 4.0 documentation, under "4.8.1 include" does not clearly explain "public" and "export".

What exactly do these lines do?

include file1.e 
public include file2.e 
export include file3.e 
-- Is there such a thing as global include file4.e 

It used to be simple. Include something and it's included for all my files. What do I gotta do to make it work like it used to?

OK, I'll have a go at an explanation of how and why it works the way it does now.

This will be a lot of words but it's not really that complex is when you come to use it.

A bit if background first ...

Scope

A scope is block of code that can access a symbol. There are five levels of scoping in which symbols can be declared ...

  1. Application
  2. File (module)
  3. Routine
  4. if ... end if (Not in v3)
  5. loops (Only for ... end for in V3)


Version 3 Scope

A symbol is either private or global. It is private if it does not have the global keyword before the declaration.

private : Can be accessed by code in the same enclosing block as the declaration and by code in blocks defined inside the enclosing block.
global : Can be accessed by anything in the application.


The effect of these rules is that if a library writer wants applications to access a symbol in the library file. that symbol must be declared as 'global'. The side effect of this is that the symbol is now accessible by all modules in the application, and that can lead to interpreter errors when multiple libraries just happen to use the same 'global' identifier name. Most of these situations can be worked around by using the namespace qualifier but some situations can only be fixed by editing a third party library.

In other words, if your application includes AA.E then all global symbols, and only global symbols, declared inside AA.E can be accessed by your application. Furthermore, global symbols declared inside files that are in turn included inside AA.E are also accessible to your application. So if AA.E includes BB.E then your application can also see the globals declared inside BB.EE

Now consider large libraries like Win32Lib. This library consists of many files, some of which have symbols declared for internal usage only and applications are not really supposed to have access them. But in V3, you need 'global' to expose these internal-use symbols to other files within the library, and this inadvertently exposes them to the application and third-party included files too.

Version 4 Scope

The private and global scopes still exist with identical semantics in V4. However, additionally V4 has public symbols. A public symbol can only be declared at the file scope level and makes the symbol accessible by anything in the same file and by any file the directly includes it. Its kind of like the visibility is raised one level only.

For example, if CC.E declares a public symbol FOO and BB.E includes CC.E, then code in BB.E can access FOO. But if AA.E includes BB.E then code in AA.E cannot see FOO.

Let's go back to the Win32Lib library. In version 4, an application that wants to use the library has

include win32lib.ew 


Now if win32lib.ew includes a file called structure.ew that has public symbols declared (eg. public function acquire_mem() ) it means that code in win32lib.ew can use acquire_mem(). Now, as that is a public symbol it means that the application cannot see the function, which is not the desired result.

In V4, we have introduced the public include statement. This has the effect of propagating any public symbols visible in the included file up one more level. So, in our example, win32lib.ew actually has public include structure.ew, which means that not only can code in win32lib.ew see the function, so can anything that includes win32lib.ew. It is not like global in that this propagation goes up one level only and not all levels.

By using public declared symbols, we decrease the chance of accidental name clashes between third party libraries.

Again, returning to the Win32LIb library. While the public include concept solves one problem for us, it doesn't solve the issue of preventing internal-use symbols from being exposed to applications. To overcome this, V4 has a slight variant of the public declared symbol; it can declare a symbol as export rather than public. The only difference is that export symbols are not propagated by the public include statement whereas public symbols are.

All these new refinements of scope are really of interest to library writers only. People writing applications would just use the simple include statement to get access to a library's API. Library writer would declare symbols as export if they are only intended to be used within a multi-file library's own include files, otherwise they would declare all symbols needed by an application as public and use the public include for its own internal include files.

global

After decades of computer programming research, it has been shown that 'global' symbols should be avoided because they increase maintenance costs and increase the chance of bugs. This will become increasingly obvious as multi-core architectures split an application up into parallel tasks, which frown on having shared symbols between tasks. I can envisage that a future version of Euphoria will implement multi-core support with symbols being 'thread-safe' by default and using 'global' symbols as shared data between threads.

I told a slight untruth before. In V4, the semantics of 'global' is a little different. Visibility is strictly limited to a reference's include tree. Let me illustrate...

The file DD.E contains the declaration global constant foo ... and BB.E includes DD.E so it can use its foo symbol. No problem there. Furthermore the file EE.E contains the declaration global procedure foo( ) and CC.E includes EE.E so it can use its foo symbol. Still no problem. Now, AA.EX includes BB.E. Still no problem. But when AA.EX also includes CC.E, version 3 Euphoria has a problem. The interpreter no longer knows which 'foo' BB.E wants to use - the one in DD.E or the one in EE.E which has suddenly become exposed by including CC.E.

        ---DD.E----------------                 ---EE.E------------------- 
        global constant foo = 1                 global function foo() ... 
        -----------------------                 -------------------------- 
                  |                                   | 
                  |                                   |  
                  v                                   v 
            ---BB.E-------------             ---CC.E-----------      
            include DD.E                     include EE.E 
            constant x = foo                 constant y = foo() 
            --------------------             ------------------ 
                       \                           / 
                        \                         / 
                         \                       / 
                          v                     v 
                         -----------AA.EX------------ 
                                 include BB.E 
                                 include CC.E 
                         ---------------------------- 


The version 3 interpreter allows BB.E to see the 'foo' declared in EE.E, even though it is not in the same include tree. In version 4, the interpreter realizes that BB.E doesn't know that CC.E, and thus EE.E, even exists so it doesn't raise a name clash error in BB.E (or CC.E for that matter).

Of course, a V4 library would be using public rather than global so it would never had been a problem anyway.

Cheating the System

Of course, if you want to revert to the poorer coding style of not having to directly include only the files that your application is using, there is a trick.

Create a file, let's call it ALL.E for this example, and let it only contain a list of public includes for the standard library ...

---- all.e ---- 
public include std/cmdline.e 
public include std/common.e 
public include std/complex.e 
public include std/console.e 
public include std/convert.e 
public include std/datetime.e 
public include std/dll.e 
public include std/eds.e 
public include std/error.e 
public include std/eumem.e 
public include std/filesys.e 
public include std/flags.e 
public include std/format.e 
public include std/get.e 
public include std/graphcst.e 
public include std/graphics.e 
public include std/image.e 
public include std/io.e 
public include std/lcid.e 
public include std/locale.e 
public include std/localeconv.e 
public include std/machine.e 
public include std/map.e 
public include std/math.e 
public include std/mathcons.e 
public include std/memconst.e 
public include std/memory.e 
public include std/mouse.e 
public include std/os.e 
public include std/pipeio.e 
public include std/pretty.e 
public include std/primes.e 
public include std/rand.e 
public include std/regex.e 
public include std/search.e 
public include std/sequence.e 
public include std/serialize.e 
public include std/sets.e 
public include std/socket.e 
public include std/sort.e 
public include std/stack.e 
public include std/stats.e 
public include std/task.e 
public include std/text.e 
public include std/types.e 
public include std/unittest.e 
public include std/utils.e 
public include std/wildcard.e 
-------------- 


Then all your applications ever have to include is ALL.E to get access to every public system in the standard library.

new topic     » goto parent     » topic index » view thread      » older message » newer message

Search



Quick Links

User menu

Not signed in.

Misc Menu