Separating your code into independent modules boils down to segmenting the space of variable names into subspaces that overlap to the extent you want. We will discuss how J handles namespaces, and then see how this corresponds to the classes and objects provided by C++.
Every public named entity in J is a member of a single locale. Each locale has a name which is a list of characters. A locative is a name containing a simple name (the only kind of name we have encountered so far) and an explicit locale, in one of the two forms simplename_localename_ and simplename__var . In the form simplename_localename_, localename is the name of the explicit locale; in the form simplename__var, the variable var must be a scalar boxed string whose opened contents provide the name of the explicit locale. Examples:
abc_z_ is simple name abc and locale z
vv =. <'lname'
def__vv is simple name def and locale lname
Note that a simple name may contain an underscore; it may not end with an underscore or contain two underscores in a row.
(Note: J makes a distinction between named locales whose names are valid J variable names not including an underscore, and numbered locales whose names are strings representing nonnegative decimal integers with no leading zeroes. The difference between the two is small and we will ignore it).
The current locale is a value kept by J and used to influence the processing of names. We will learn what causes it to change. The current locale is the name of a locale. When J starts, the current locale is set to 'base' .
An assignment is private if it is of the form simplename=.value and is executed while an explicit definition is running (an explicit definition is the result of the : conjunction, for example a verb defined by 3 : 'text' or a modifier defined by adverb define). An entity assigned by private assignment is not part of any locale and is accessible only by sentences executed by the explicit definition that was running when the entity was assigned. The idea is this: there is a pushdown stack of namespaces in which private entities are assigned. When an explicit definition E is executed, it starts with a new empty namespace that will be destroyed when E finishes. Any private assignments made while E is running are made in this private namespace. Any private variables referred to by E or by tacitly-defined entities invoked by E are taken from this private namespace. If E invokes another explicit definition F, F starts off with its own private namespace and has no access to elements of E's private namespace. When F finishes, returning control to E, F's private namespace is destroyed. E is said to be suspended while F is running.
Assignments that are not private (because they assign to a locative, use =:, or are executed when no explicit definition is running) are public. Assignment to a locative creates an entity having the simple name in the locative, residing in the explicit locale given by the locative. A public assignment to a simple name creates the named entity in the current locale. Entities in a locale are not affected by completion of an explicit definition; they have a life of their own and can be referred to by any verb that knows how to reach the locale they are in. The following examples illustrate assignments; the interpretation given is correct if the lines are entered from the keyboard:
simp1 =. 5 NB. public (outside of explicit definition)
vb1 =: verb define NB. public
isimp =. simp1 NB. private, referring to public simp1
simp1 =. 8 NB. private (=. inside definition)
loc1_z_ =. 10 NB. public (locative)
simp2 =: 12 NB. public (=:)
isimp,simp1 NB. result
)
vb1 '' NB. execute vb1, see result
5 8
Note that simp1 was set to 8 by the explicit definition. Because this was a private assignment, the public value was not changed:
simp1
5
The public value is still 5, as it was before the explicit definition was executed.
loc1_z_
10
simp2
12
The other public assignments leave their results in the locale they were assigned to.
isimp
|value error: isimp
The entities assigned by private assignment were destroyed when vb1 finished.
Note that the load verb (which runs scripts) is an explicit definition. Any assignment using =. executed during load will be lost. Use =: to define names in scripts.
Names and locatives used to refer to entities look just like names appearing as targets of assignments, but there is an additional level of complexity for references. Each locale has a search path (usually called simply the path) which is a list of boxed locale names. The path is set and queried by the foreign 18!:2 which goes by the alias copath . Examples:
('loc1';'loc2';'z') 18!:2 <'loc3'
Sets the path for locale 'loc3' to 'loc1' followed by 'loc2' (and the obligatory 'z').
copath <'loc3'
+----+----+-+
|loc1|loc2|z|
+----+----+-+
Queries the path for 'loc3' .
Every reference to a name implicitly uses a path. A reference to a locative looks for the simple name in the explicit locale; if the name is not found there, the locales in the path of the explicit locale are examined one by one until the simple name is found (if the name is not found in any locale in the path, it is an undefined name). A reference to a simple name is similar, but first the private namespace of the executing explicit definition (if any) is searched, and only if that search fails are locales searched, starting in the current locale and continuing if necessary in the locales in the current locale's path.
Note that only the path of the starting locale (either the current locale or the explicit locale) specifies the search order. No other paths are used.
Examples of references:
('loc1';'loc2';'z') 18!:2 <'loc3'
a_loc1_ =: 'a'
a_loc2_ =: 'b'
c_loc3_ =: 'c'
c_loc2_ =: 'd'
a_loc3_
a
The name was not defined in 'loc3' so the path was used, and the name was found in 'loc1' .
a_loc2_
b
The value in 'loc2' can be retrieved if we start the search there.
c_loc3_
c
If the value is found in the starting locale, no search is performed.
c_loc1_
|value error: c_loc1_
We have not defined a path for 'loc1', so 'loc2' is not searched.
The current locale can be changed in two ways: explicitly by executing the cocurrent verb whose purpose is to change the current locale, and implicitly by executing a verb named by a locative.
cocurrent y sets the
current locale to y . Simple as that. cocurrent uses the
foreign 18!:4 .
Do not use 18!:4 directly! It is intended to be used under an alias,
and it has side effects.
Executing an entity named by a locative (almost always a verb, but it could be a modifier as well) saves the current locale, changes the current locale to the explicit locale of the locative before starting the entity, and resets the current locale to the saved value when the entity finishes. Note that the entity always runs in the explicit locale of the locative, even if the search for the name found the entity in some other locale in the search path.
Whenever a named entity finishes execution, the locale is restored to its original value, even if the entity changed the current locale.
Here are examples of actions affecting the current locale:
load 'printf'
18!:5 ''
+----+
|base|
+----+
This is how you query the name of the current locale. Next we define two verbs.
v1_z_ =: verb define
'Locale at start of v1 is %j' printf 18!:5 ''
qprintf 'n1 '
v2_result =. v2_loc1_ n1
'Value returned by v2 is %s' printf <v2_result
'Locale in v1 after calling v2 is %j' printf 18!:5 ''
qprintf 'n1 '
)
cocurrent <'loc2'
v2 =: verb define
'Locale at start of v2 is %j' printf 18!:5 ''
qprintf 'n1 y. '
cocurrent <'loc2'
qprintf 'n1 '
'Locale at end of v2 is %j' printf 18!:5 ''
n1
)
The verb v1 was defined in locale 'z' because it was an assignment to a locative; the verb v2 was defined in locale 'loc2' because it was an assignment to a simple name and the current locale at the time of its assignment was 'loc2' .
cocurrent <'loc3'
v2 =: [:
Now the verb v2 is defined in both locale 'loc2' and 'loc3' . Next we define the noun n1 in each of our locales, so we can see which locale a name was found in:
n1_loc1_ =: 'n1 in loc1'
n1_loc2_ =: 'n1 in loc2'
n1_loc3_ =: 'n1 in loc3'
Now run the verbs. I will insert interpretation of the execution.
v1 ''
J searches for the simple name v1 in the current locale 'loc3'; not finding it there it looks in 'loc1', 'loc2', and 'z', finally finding it in 'z' . J executes the definition of the verb found in 'z', but without changing the current locale.
Locale at start of v1 is loc3
Yes, the current locale is still 'loc3' …
n1=n1 in loc3
…and a name lookup uses the current locale as the starting point.
Locale at start of v2 is loc1
v1 has executed v2_loc1_ . J starts searching for the name v2 in locale 'loc1' and its path, eventually finding it in 'z' . v2 is executed, using the definition found in 'z', but with the current locale set to the explicit locale of the locative, namely 'loc1' . Note that v2 was also defined in 'loc3' (as [: which would give an error), but 'loc3' was never searched. The operand of v2 was n1; note that the lookup for n1 is completely independent of the lookup for v2; n1 is sought and found in 'loc3' and it is that value that becomes y. at the start of execution of v2 .
n1=n1 in loc1 y.=n1 in loc3
Simple name lookups start in the current locale 'loc1' . The private name y. has the value it was given on entry.
n1=n1 in loc2
Here we have switched the current locale to 'loc2' using cocurrent, and the name is found in the new current locale.
Locale at end of v2 is loc2
Value returned by v2 is n1 in loc2
Here v2 has finished and control has returned to v1 . Note that the value returned by v2 is simply the result of the last sentence executed; it is a noun. Here it is the value of n1 at the end of v2, at which time the current locale was 'loc2' .
Locale in v1 after calling v2 is loc3
Note that when v2 finished the current locale was restored to its value before execution of v2 . The cocurrent in v2 has no effect once v2 finishes.
n1=n1 in loc3
Execution of the verb v1 is complete. We should be back in locale 'loc3' from which we executed v1 :
18!:5 ''
+----+
|loc3|
+----+
It is a J convention, universally adhered to, that every locale's search path must end with the locale 'z' . Any name in the 'z' locale can then be referred to by a simple name from any locale, making names in the 'z' locale truly global names.
You have my sympathy for having to read through that detailed description of name processing; I refuse to apologize, though, because you really can't write programs if you don't know what names mean. But, you wonder, How do I use locales?
You won't go far wrong to think of a locale as akin to a class in C++. When you have a set of functions that go together as a module, define all their verbs and nouns in a single locale. The easiest way to do this is to put a line
coclass <'localename'
at the beginning of each source file for the module (coclass is like cocurrent but it supports inheritance). Then, every public assignment in the file will automatically be made in the locale localename .
The names defined in the locale are the equivalent of the private portion of class. To provide the public interface to the class, you need to put those names in a place where they can be found by other modules. The traditional way to do this is to define them in the locale 'z' by ending each file with lines like
epname_z_ =: epname_localename_
Here epname is the name of a verb, and localename is the name of the module's locale. Take a minute to see what happens when some other locale invokes epname . The name search for epname will end in the locale 'z' where this definition is found. Execution of this definition immediately results in execution of epname_localename_ which switches the current locale to localename and runs the definition of epname found there. The benefit is that the calling module doesn't need to know the locale that epname is going to be executed in.
If you tire of writing out the public definitions one by one, I have included in jforc.ijs a verb to do it for a list of entry points using a sentence like
PublishEntryPoints 'public1 public2 public3'
If you want to create multiple copies of objects derived from a class, you should consult the Lab on Object Oriented Programming. There you will learn about numbered locales and how to create and destroy objects. We will not discuss these topics here.
By following the guidelines given above you will be able to emulate the class facilities of C++. Because J is interpreted, you can do much more if you want: you can change search paths dynamically; you can use locales and paths to create a high-performance network database; you can pass locales as data and use them to direct processing; you can peek at a module's private data. You can even modify a module's private data from outside the module, but if you are struck by lightning after doing so the coroner will find it was suicide.
Using locale-names as data allows for dynamic separation of namespaces. For example, the processing of forms in J requires definition of verbs for a great many events. You may let these verbs all share the same locale; but if you want to segregate them, the Window Driver will remember what locale was running when each form was displayed, and direct events for a form to the locale handling the form.