User:Dan Bron/Temp/Fix Locals

From J Wiki
Jump to navigation Jump to search

This paper introduces fl, an adverb like f. which fixes only local names.

motivation

A long standing wish in the J community is for an adverb like f. which fixes a name (recursively) iff it is defined locally. The primary desires motivating the request are for:

  1. A safe method to pass around definitions containing local names, without changing the definitions otherwise: example.
  2. A way to decompose long definitions without cluttering the namespace or losing important relationships between (globally) named entities: example.
  3. The ability to fix (certain) definitions without breaking J's debug facilities: example.

Each of these desires could be satisfied if we had a mechanism to indicate which names we want fixed, and fix only those.

Fortunately, J provides an easy indication mechanism: local assignment. Local names are ephemeral by design; they will go out of scope. If the entities which depend upon them are to function outside of that scope, the local definitions must be fixed.

Unfortunately, J does not provide a native way to fix only local names (or even detect if a name is local). This paper addresses that need.

implementation

definitions

All the utilities discussed herein are defined by the namescope script. The script is still in early beta; please test it and report any bugs to me. When J602 and the attendent JAL are released, I will publish a 'fix locals' addon.

The definitions you'll find most useful are:

name(s) definition input output
fl the adverb which fixes only local names. a verb or quoted name same as the input, with local names replaced by their definitions, recursively
nameScope provides information about the scope of names a (boxed) list of names a table of booleans s.t. the first column indicates whether the names have local definitions, and the last column indicates whether the names have global definitions
ns a more convenient cover to nameScope same as nameScope defined as #.@:nameScope
ns_isLOCAL, ns_isGLOBAL, ns_hasLocal, ns_hasGLOBAL (and several others) more convenient covers to ns same as ns boolean lists answering the obviously corresponding questions.
ns_type a debugging utility same as ns a table of boxes y ,. scope y, where scope is a human-readable string like 'LOCAL' or 'GLOBAL'

Examples are given in the next section.

examples

the problems

Problem A: Since proverbs are stacked by name in J, explicit operators can't return definitions containing local (non-"special") verbs, because the local parts will go out of scope:

   +/ 1 : 0
   	w =. long complicated expression@:u
   	v =. w;.2~ u
   	v
   )
|value error: v

Problem B: We'd like to a way to break up complicated tacit expressions, like this:

   global     =:  (foo .. bar)`(baz baz baz + blah blah blah)`@.(long
conditional verb)

by decomposing them into several shorter, easier to read definitions, like this:

   protasis   =.  long conditional verb
   apodosis   =.  baz baz baz $: blah blah blah
   peridosis  =.  foo .. bar

   global     =:  peridosis`apodosis@.protasis  f.

Note that the component definitions are made locally, and the final definition is "fixed" with `f.`. That's because the components can not be used independently; they're only useful within the context of the larger definition. Given this, we do not want them cluttering the namespace.

The problem with using `f.` to solve this problem is that it's indiscriminate. By design, it completely fixes a name, even incorporating the definitions of other global names.

This has two drawbacks:

  1. It destroys important relationships between global definitions, and
  2. It can make definitions so long and complicated as to be incomprensible.

For example, imagine you wanted to reverse each line of a file, using the verb fread from J's standard library:

   NB.  === file: some_script.ijs ===
   require 'strings files'
   rev    =: |.&.>
   revln  =: ([: rev LF cut fread)

Suppose you then decide that `rev` is a trivial function, not worthy of independent existence. So, as above, you assign `rev` locally and fix the definition of the global verb:

   rev    =. |.&.>
   revln  =: ([: rev LF cut fread) f. }}} Now, loading your script and inspecting the definition of `revln`, you see<<Anchor(verbose)>>: {{{ [: |.&.> (10{a.) 4 : 'x '' ''&$: :([: -.&a: <;._2@,~) y' 3 : 0
'' fread y
:
if. 1 = #y=. boxopen y do.
  dat=. 1!:1 :: _1: fboxname y
else.
  dat=. 1!:11 :: _1: (fboxname {.y),{:y
end.
if. dat -: _1 do. return. end.
if. (0=#x) +. 0=#dat do. dat return. end.
dat=. (-(26{a.)={:dat) }. dat
dat=. toJ dat
if. 0=#dat do. '' $~ 0 $~ >:'m'e.x return. end.
dat=. dat,LF -. {:dat
if. 'b'e.x do. dat=. <;._2 dat
elseif. 'm'e.x do. dat=. ];._2 dat
end.
)

Problem C: For the same reason f. makes manual debugging difficult (in B.ii, without looking at the script, could you tell what revln is doing?), it makes automated debugging impossible. J's debugger can only debug explicit named entities, and fixing a definition removes every name from it. In B.ii, if there was a problem in fread, you could not step into it; from the debugger's POV, revln is atomic.

Of course, each of the bullets above could be addressed with some simple code changes, but that's only because they are contrived examples. The general solution is fl, as described in the next section.

the solution

caveats

  • The foreign 15!:6 is undocumented and therefore unsanctioned (but, since it's a critical part of jmf, it's unlikely to be removed). There are probably other ways to detect the difference between each_base_ and each_z_, but the only ones I can think of have unsightly side effects (e.g. creating a temporary locale with no copath).
  • Undefined names are treated differently by f. and fl. The adverb f. always raises an error when it encounters an undefined name, whereas fl will leave such names unresolved. Do not be fooled by the error produced in undefined fl. There, the adverb isn't complaining; J is. It's complaining for precisely the same reason it would complain if you entered undefined (without the fl): it can't display the definition of an undefined name. Contrast defined =: undefined fl
  • Embedded nouns are treated differently by f. and fl. The latter __will__ operate upon nouns embedded in definitions (i.e. where (<,'0')-:{. at 1 = L. in the atomic rep (recursively) ) . For example:
   + 1 : 0
	local =. ]
	global=:[
	local`global@.u fl
   )
]`global@.+

This behavior is intentional, because I (the author) find independently fixing gerunds a nuisance. However, in the vanishingly rare case that your definition contains a bound rank-1 literal noun which itself contains a word that is a local name, `fl` will do the wrong thing. For example:

   + 1 : 0
	local =. 'cut the roses'
	NB. the __quoted string__ 'local' is bound, and 'local' is a local name.
	'local'&u fl
   )
(<;._1 '|0|cut the roses')&+

In this context, "bind" doesn't mean `&`, it means supplying any operator with a noun argument, e.g. `untrusted_noun"` or `untrusted_noun/`. Though the `&` case is the only one likely to occur (I don't know why yet, but `m : 'a_local_name'` doesn't appear to cause a problem).

The only cases you need to worry about are (A) when you bind a noun you do not control, or (B) where you use indirect local assignment where the LHA of `=.` is a noun you do not control. However, since you (the applier of `fl`) have control of all local names (by definition), if you encounter either problem, you can make a quick and comprehensive fix by selecting different local names (perhaps using a common prefix, in the case of B).

  • Self-reference, $:, is treated differently by f. and fl. In verbs fixed with fl, $: refers to the derived verb, whereas f. forces $: to refer to the named verb which contains it, even if that name is local. For example, using f.:
  local  =.  $:@:-:
  (<;.1~ local)^:([: *./ 0&<) f.
(<;.1~ 3 : '$:@:-: y')^:([: *./ 0&<)

Contrast that with the result of `fl`:

  local  =.  $:@:-:
  (<;.1~ local)^:([: *./ 0&<) fl
(<;.1~ $:@:-:)^:([: *./ 0&<)
  • If the argument to fl is itself a (quoted) global name, nothing will change. EG:
   local  =.  +

   global =:  local~

   g0     =:  global fl
   g0     NB.  fl doesn't fix global names
global


   glbadv =:  local&
   g1     =:  'glbadv' fl
   g1
glbadv

This is consistent, but perhaps not useful. It may be changed in the future.

  • These caveats reflect choices I (the author) made to solve the common frustrations I have when fixing names. (In general, these favor the case of breaking up a complicated global into simple locals.) However, alternative choices (in particular, f.-compatible behaviors) are not without their uses.

    For example, one may want $: to refer only to the local name in which it is embedded, rather than the entire fixed name. Or, one may not want to worry that bonding a noun may one day, unpredictably, break his application.

    Therefore, in a future version of the script, I may define certain, special, auxillary names. These names would have the prefix fl, but would be suffixed with boolean digits, one boolean for each caveat which has a sensible (implementable) alternative. In the case of incompatibilities with f., each digit would indicate whether the corresponding option should be compatible with f. (1) or not (0). The default would be f.-incompatible (i.e. all zeros).

    For example, if the two caveats are "fix nouns, too" and "$: refers to the entire derived verb", then fl is the same as fl00, but fl01 would make $: refer to the local name, fl10 would not try to fix nouns, and fl11 would do both those things.

    Then, in your namespace, you could set fl =: flBB_z_ where BB are boolean digits indicating your preference, and use fl without fear.

notes

You can play around with locals without creating bunches of temporary files, by creating and executing scripts directly in memory. Remember to force local context though (which is introduced by :).

We can rework the example from problem B.ii to remove its dependence upon a disk file ("some_script.ijs")

   3 : '0!:0 (0 : 0) [ 4!:55<,''y''' 0

       require 'files strings'
       rev    =. |.&.>
       revln  =: ([: rev LF cut fread)  f.

)

   revln
[: |.&.> (10{a.) 4 : 'x '' ''&$: :([: -.&a: <;._2@,~) y' 3 : 0
'' fread y
:
if. 1 = #y=. boxopen y do.
  dat=. 1!:1 :: _1: fboxname y
else.
  dat=. 1!:11 :: _1: (fboxname {.y),{:y
end.
if. dat -: _1 do. return. end.
if. (0=#x) +. 0=#dat do. dat return. end.
dat=. (-(26{a.)={:dat) }. dat
dat=. toJ dat
if. 0=#dat do. '' $~ 0 $~ >:'m'e.x return. end.
dat=. dat,LF -. {:dat
if. 'b'e.x do. dat=. <;._2 dat
elseif. 'm'e.x do. dat=. ];._2 dat
end.
)

Of course, such a directly keyboarded script cannot itself contain multiline explicit definitions (rather, it can contain one, at the end). If you need those, you have the option compose a proper literal argument to 0!:0, but at that point it's probably easier to just create a new, temporary script on disk rather than work through all the issues with quotes, etc.

See also ijxer.ijs, a utility to convert a multi-line disk script into a single line, suitable for pasting into immediate execution (or, more relevantly, to one of the J evaluation bots on IRC).

references

Requests for fl (or similar, or associated) behavior: