1. Forward reference to included function
- Posted by ArthurCrump Jan 13, 2014
- 1730 views
The manual for Euphoria 4.0.5 states that:
The scope of all procedures, functions and types, which do not have a scope modifier, starts at the beginning of the source file and ends at the end of the source file in which they are declared. In other words, these can be accessed by any code in the same file.However, the following code works even though the function called is not in the same file.
any_key("\nChoose a key and press it\n") include std/console.e
Are forward references intended to find routines in include files or was it intended that the routine should be in the same file as stated in the 4.0.5 documentation?
Arthur
2. Re: Forward reference to included function
- Posted by _tom (admin) Jan 13, 2014
- 1716 views
Traditional documentation uses defined or declared when describing scope.
For a variable you define atom x = 1, and thereafter x is "in scope" to the the end of the file; routines used to be the same. However, O[ has a bonus feature for routines; they are "in scope" for the entire file (start to end) even if the routine is defined in the last statement in the file. The advantage is the ability to perform "mutual recursion" where one routine calls another routine; without this bonus feature of full file scope it is like watching a dog chase its tail. (Python is not as friendly as O[ when it comes to mutual recursion.)
I propose a new term: we now say an identifier (variable or routine) is introduced. In a file a definition or declaration introduces a new identifier. The scope rules for a variable are from the point of introduction to the end of a file. The scope rules for a routine are for the entire file where the identifier is introduced. (Sounds much like the existing documentation.)
If we have an include statement, that statement is the point of introduction for identifiers (variables and routines) that are found in the included file. (The identifiers have to be export, public, or global to be visible.)
- For a variable the scope is from the introduction (the include statement) to the end of the file. The actual definition is hidden in the include file.
- For a routine the scope is from the introduction (the include statement) for the entire file. The actual definition is hidden in the include file.
The term introduction now explains why:
any_key("\nChoose a key and press it\n") include std/console.e
works in O[:
- The any_key routine is introduced by the include statement (any_key is actually defined somewhere, hidden, in the include file.)
- The scope of any_key, as a routine, is for the entire file where there is an introduction.
_tom
3. Re: Forward reference to included function
- Posted by mattlewis (admin) Jan 13, 2014
- 1685 views
The manual for Euphoria 4.0.5 states that:
The scope of all procedures, functions and types, which do not have a scope modifier, starts at the beginning of the source file and ends at the end of the source file in which they are declared. In other words, these can be accessed by any code in the same file.However, the following code works even though the function called is not in the same file.
any_key("\nChoose a key and press it\n") include std/console.e
Are forward references intended to find routines in include files or was it intended that the routine should be in the same file as stated in the 4.0.5 documentation?
Yes! The bit you quoted was about things without a scope modifier. You'll never see them from another file. Stuff in other files is available, period, if you've included the file. You don't have to carefully arrange code so that files can reference each other. You can safely put all of your includes at the top, instead of hiding them inside your code.
Matt
4. Re: Forward reference to included function
- Posted by mattlewis (admin) Jan 13, 2014
- 1712 views
The term introduction now explains why:
any_key("\nChoose a key and press it\n") include std/console.e
works in O[:
- The any_key routine is introduced by the include statement (any_key is actually defined somewhere, hidden, in the include file.)
- The scope of any_key, as a routine, is for the entire file where there is an introduction.
This doesn't seem any clearer to me. In addition to the previously quoted bit, which handles symbols without a scope modifier, we have this in the manual:
- If the keyword global precedes the declaration, the scope of these identifiers extends to the whole application. They can be accessed by code anywhere in the application files.
- If the keyword public precedes the declaration, the scope extends to any file that explicitly includes the file in which the identifier is declared, or to any file that includes a file that in turn public includes the file containing the public declaration.
- If the keyword export precedes the declaration, the scope only extends to any file that directly includes the file in which the identifier is declared.
I think adding the new "introduce" concept makes this more confusing, because it makes it sound like where the file is included relative to use of the symbol is important. That was true in versions previous to 4.0, but is no longer true.
Matt
5. Re: Forward reference to included function
- Posted by _tom (admin) Jan 13, 2014
- 1658 views
I think adding the new "introduce" concept makes this more confusing, because it makes it sound like where the file is included relative to use of the symbol is important. That was true in versions previous to 4.0, but is no longer true.
Matt
The location of the the include statement is important!
A variable originating in an include file will be visible, in the including file, only from the point of the include statement to the end of the file--but not anywhere before the include statement.
For a procedure or function this restriction does not apply; if you only use routines from an include file (most of the time) then you have full freedom where the include statement is written.
This is why the single term introduce is better than define and declare. Knowing where a variable is defined does not help you understand the full range of scope when there are included files. If you try to use a constant in the std/mathcons.e module and forget to include it then you get an error. If you do include std/mathcons.e the variable is only in scope from the point of introduction, that is the include statement.
The term introduce is simpler and more universal. The same description for scope applies to one file and a set of nested include files.
_tom
6. Re: Forward reference to included function
- Posted by mattlewis (admin) Jan 13, 2014
- 1686 views
I think adding the new "introduce" concept makes this more confusing, because it makes it sound like where the file is included relative to use of the symbol is important. That was true in versions previous to 4.0, but is no longer true.
Matt
The location of the the include statement is important!
A variable originating in an include file will be visible, in the including file, only from the point of the include statement to the end of the file--but not anywhere before the include statement.
Not so. For example:
-- test.e export object foo = 1 -- test.ex ? object( foo ) procedure bar() ? foo end procedure include test.e bar()
Now, it's true that the variable might not have a value at the top level if it's used that way. At that point, it all depends on the order of inclusion in the program itself (i.e., has another file already included test.e?). But the variable is in scope in the including file prior to the include statement itself.
For a procedure or function this restriction does not apply; if you only use routines from an include file (most of the time) then you have full freedom where the include statement is written.
This is why the single term introduce is better than define and declare. Knowing where a variable is defined does not help you understand the full range of scope when there are included files. If you try to use a constant in the std/mathcons.e module and forget to include it then you get an error. If you do include std/mathcons.e the variable is only in scope from the point of introduction, that is the include statement.
The term introduce is simpler and more universal. The same description for scope applies to one file and a set of nested include files.
As demonstrated by the example, your assumptions are wrong. I think define / declare are fine, and in euphoria they are interchangeable (though it might be preferable to settle on one).
Matt
7. Re: Forward reference to included function
- Posted by _tom (admin) Jan 13, 2014
- 1661 views
The module (aka included file):
-- test.e export object foo = 1
The main file (aka the including file):
-- test.ex ? foo --^ I added this line to illustrate the "introduce" concept ? object( foo ) procedure bar() ? foo end procedure include test.e bar()
My result:
$ eui test.ex test.ex:3 variable foo has not been assigned a value --> See ex.err
My conclusion: the variable foo has not been "introduced" because the include statement comes after the ? foo statement. But foo has been properly "declared" within the module.
I have noticed that each programming language ends up with its own documentation language. Words are not used uniformly in the literature:
- declare
- often used to "declare a variable"
- define
- sometimes specific to "define a routine"
- declare, define
- sometimes used to mean the same thing, by the same author
_tom
8. Re: Forward reference to included function
- Posted by _tom (admin) Jan 13, 2014
- 1652 views
Another nuance on the term "introduce":
-- example1.ex object x -- variable x has been 'declared' ? x -- produces error message: --> variable x has not been assigned a value -- that means variable x has not been "introduced" (since two steps needed...declare and assign)
-- example2.ex object x --....various lines of code x = 10 -- > variable has now been "introduced" -- from now to end of file you can use the variable ? x -- displays value
The term "introduce" describes the exact spot from where a variable may be used.
_tom
9. Re: Forward reference to included function
- Posted by mattlewis (admin) Jan 13, 2014
- 1658 views
-- that means variable x has not been "introduced" (since two steps needed...declare and assign)
Sorry, I don't think this clarifies anything. It just introduces another term. I think it is less clear and will lead to more confusion.
Matt
10. Re: Forward reference to included function
- Posted by jaygade Jan 13, 2014
- 1622 views
I have to agree with Matt here.
In your example, "foo" has been declared in test.e, therefore it is a valid identifier. However, the code "foo = 1" will not be executed until the program counter physically reaches it.
See the corrected example below:
-- test.e export object foo = 1
-- test.ex ? object( foo ) procedure bar() puts(1, "bar() = ") ? foo end procedure foo = 42 ? foo -- by including test.e, foo will be DECLARED for all of test.ex. -- However, the statement "foo = 1" doesn't get executed until -- AFTER test.e is included (after line 15). This is because -- actual program statements (including assignments) are sequential -- in nature. -- At that point, foo will be DEFINED. include test.e ? foo bar()
Technically, the word "declared" is the same as your "introduced". "Defined" is when a value is actually assigned.
For purposes of Euphoria, procedures and functions are necessarily defined when they are declared.
However, inline (default) assignment of variables is treated as a shortcut to a regular statement.
I don't know if there is any to make it any more clearer other than to change Euphoria's behavior to promote default assignment to a higher level.
11. Re: Forward reference to included function
- Posted by petelomax Jan 14, 2014
- 1628 views
Somewhat offtopic, but I thought I'd share a little about the situation in Phix.
Suppose you have
-- file1.e include file2.e f1p()
and
-- file2.e include file1.e global procedure f1p() end procedure
Now, if main.exw includes file1.e then everything is resolved cleanly, however if main.exw includes file2.e, then the compiler hits EOF in file1.e before (the second half of file2.e is processed and) f1p is actually declared, which generates a compilation error. (Feel free to giggle at this point!) In Phix, f1p() is an implicit call and therefore local, not global, though it can get automatically promoted to global, if found "in time". In Phix, you need an explicit "forward global procedure f1p()" at the top of file1.e to get round this (or include the root of the tree, rather than start with one of the branches). Also (cue more giggling) Phix does not permit forward variable references, period, which has never bothered me in the slightest.
Anyway, I did actually have something possibly useful to say:
I don't know if there is any [way] to (...) change Euphoria's behavior to promote default assignment to a higher level.
In Phix, integer (and more recently pure literal constant) assignment on declaration effectively occurs at compilation time leading to forward-safe code looking like:
integer xinit = 0 sequence table global procedure x() if not xinit then table = repeat(0,200) xinit = 1 end if ... end procedure
So no matter how you invoke x(), the (integer) variable xinit never suffers one of those pesky "has not been assigned" errors.
Regards, Pete
12. Re: Forward reference to included function
- Posted by mattlewis (admin) Jan 14, 2014
- 1623 views
Suppose you have
-- file1.e include file2.e f1p()
and
-- file2.e include file1.e global procedure f1p() end procedure
Now, if main.exw includes file1.e then everything is resolved cleanly, however if main.exw includes file2.e, then the compiler hits EOF in file1.e before (the second half of file2.e is processed and) f1p is actually declared, which generates a compilation error. (Feel free to giggle at this point!) In Phix, f1p() is an implicit call and therefore local, not global, though it can get automatically promoted to global, if found "in time". In Phix, you need an explicit "forward global procedure f1p()" at the top of file1.e to get round this (or include the root of the tree, rather than start with one of the branches). Also (cue more giggling) Phix does not permit forward variable references, period, which has never bothered me in the slightest.
Yes, this is pretty much how pre-4.0 euphoria worked (minus the forward definition, of course). The forward declaration is really useful for not requiring you to rearrange code or use routine_ids when you need to reference a previous routine. It also makes situations where files include each other easier on the developer. We have a fair amount of this in the standard library.
I don't know if there is any [way] to (...) change Euphoria's behavior to promote default assignment to a higher level.
In Phix, integer (and more recently pure literal constant) assignment on declaration effectively occurs at compilation time leading to forward-safe code looking like:
We do this for constants, but haven't done it for variables.
Matt
13. Re: Forward reference to included function
- Posted by _tom (admin) Jan 15, 2014
- 1551 views
Does this explaination make sense? By the time you get to the last line Arthur's question gets answered...
_tom
All identifiers must be declared which means "you pick an identifier name and state what it will be used for." In addition, the identifier has to be defined which means "you state what the identifier is or does."
The scope of an identifier is "the region of source-code where the identifier can be used."
Any One File
If you examine any one source-code file:
- For an object (variable, constant, enum) you can declare and define the identifier on different lines or you can declare and define in one statement. The scope of the object is from the definition to the end of the source-code file.
- For a routine (procedure, function, type) you declare and define the identifer when you write structure of the routine. The scope of a routine is for the entire source-code file (top to bottom.) Notice that a routine is in scope within the structure of the routine--this is why you can write recursive routines.
A scope modifier (export, public, global) preceding the declaration of an identifer has no impact on the scope within the single source-code file.
Any Two Files
A primary file is "the file doing the including." A module file is "the file being included."
The file extension ( .ex or .e ) has no impact on how including and included files behave. You will see an included file described as an "include", a "module", or a "library." The standard library files can be described as modules that are included by your program.
No Scope Modifier
When File A (the primary file) includes File B (the module file) the following properties apply:
- The top-level statements of the module will be executed when included by the primary file.
- All module file identifiers will be invisible to the primary file.
- All primary file identifiers will be invisible to the module file.
- A module is physically included in the primary file only once; if you repeat an include statement it is quietly ignored.
With Scope Modifier
Only identifiers with a scope modifier (export, public, global) declared in a module will be visible to the primary file.
When you include a module with a scope modified identifier that identifier has the scope properties of a locally declared identifier. In the primary file:
- The possible scope of an object (variable, constant, enum) is from the include statement to the end of the primary file. As always, an object has to be defined (assigned a value) before you may use it.
- A routine (procedure, function, type) will be in scope for the entire file (top to bottom).
Forward Reference
A forward reference is "when you use an identifier that is declared later in the source-code than the statement you are writing."
For an object (variable, constant, enum) no forward references are possible. You must always declare and define an object before you may use it.
For a routine (procedure, function, type) forward references are possible because the scope of a routine is for the entire source-code file (top to bottom).
14. Re: Forward reference to included function
- Posted by mattlewis (admin) Jan 15, 2014
- 1540 views
All identifiers must be declared which means "you pick an identifier name and state what it will be used for." In addition, the identifier has to be defined which means "you state what the identifier is or does."
I don't see the difference between declared and defined in euphoria.
- For an object (variable, constant, enum) you can declare and define the identifier on different lines or you can declare and define in one statement. The scope of the object is from the definition to the end of the source-code file.
First diagram: A variable is certainly in scope before it is assigned. You can use object(x) to test for assignment, in fact. If you try to do something else before it is assigned, you get an error about that. If it weren't in scope, then you'd get a "variable not found" sort of error.
Second diagram: This isn't quite right. A variable or constant declared anywhere in the file at top level is visible from inside a routine that is located anywhere in the file. It is only visible to code at the top level after the declaration. Likewise, inside a routine, the variable is only visible to code in the routine that comes after the declaration. Both of these are subject to the caveat that code at a higher scope level cannot see variables declared at a lower level (e.g., a variable declared within a loop is not visible outside of that loop). I may have higher/lower confused.
Matt
15. Re: Forward reference to included function
- Posted by _tom (admin) Jan 15, 2014
- 1541 views
I don't see the difference between declared and defined in euphoria.
I was seeing "defined" as a variable after being assigned a value. But, since you can use atom(x) before an assignment I now understand how we just need "declared".
This probably means I can do a search and replace on all (most) occurances of "define" in the documentation and replace them with "declare".
This simplifies the first diagram.
Second diagram: This isn't quite right. A variable or constant declared anywhere in the file at top level is visible from inside a routine that is located anywhere in the file.
This I don't understand:
It suggests to me that this should work:
foo() procedure foo() ? x end procedure atom x = 1 -- variable x has not been assigned a value
But what actually works is:
atom x = 1 foo() procedure foo() ? x end procedure
Both of these are subject to the caveat that code at a higher scope level cannot see variables declared at a lower level (e.g., a variable declared within a loop is not visible outside of that loop). I may have higher/lower confused.
Creating a diagram to explain nested scopes is going to take some work.
_tom
16. Re: Forward reference to included function
- Posted by mattlewis (admin) Jan 15, 2014
- 1570 views
This I don't understand:
It suggests to me that this should work:
foo() procedure foo() ? x end procedure atom x = 1 -- variable x has not been assigned a value
You got an error, but not because of scope. The error was due to execution order and the variable not being assigned before it was used.
Matt
17. Re: Forward reference to included function
- Posted by evanmars Jan 16, 2014
- 1519 views
This I don't understand:
It suggests to me that this should work:
foo() procedure foo() ? x end procedure atom x = 1 -- variable x has not been assigned a value
You got an error, but not because of scope. The error was due to execution order and the variable not being assigned before it was used.
Matt
If I may comment as someone that isn't that great of a programmer and relies heavily on the documentation to understand how Euphoria is supposed to work.
I think the point is is that the variable is definitely in scope; it can be written to, but it cannot be read from until it has first been written to.
Is this correct?
18. Re: Forward reference to included function
- Posted by mattlewis (admin) Jan 16, 2014
- 1508 views
I think the point is is that the variable is definitely in scope; it can be written to, but it cannot be read from until it has first been written to.
Is this correct?
Yes, that's exactly it.
Matt
19. Re: Forward reference to included function
- Posted by jaygade Jan 16, 2014
- 1522 views
It is a little unintuitive, since constants work one way and variables another.
If a change in behavior is desired to make variable combined declaration and assignment work like constant declaration and assignment, I would suggest putting in a feature request ticket.
At a minimum, the behavior should be documented.