Problem

Suppose you have a binding B with a deep nested structure and you want to find out whether B/x/y/z exists. You could make an expression with a bunch of existence tests combined with the logical and operator:

   1 B!x && B/x!y && B/x/y!z

But that can get repetitive if you do it many times. Of course we can solve this with a little function.

List Path Implementation

The obvious way to express a path is as a list of text strings. It's pretty easy to write a recusive function which will test for the existence of paths specified this way:

   1   /**nocache**/
   2   path_exists(b, path) {
   3     return if _length(path) == 0 then TRUE else {
   4       n = _head(path);
   5       value if (_is_binding(b) && b!$n)
   6       then path_exists(b/$n, _tail(path))
   7       else FALSE;
   8     };
   9   };

Here's an example using that:

   1   b = [a/b/c/d/e/f = 1];
   2   test = <
   3     path_exists(b, <"a", "b", "c", "d", "e", "f">),
   4     path_exists(b, <"a", "b", "c", "d", "e", "x">),
   5     path_exists(b, <"a", "b", "c", "d", "e">),
   6     path_exists(b, <"a", "b", "c", "d", "x">),
   7     path_exists(b, <"a", "b", "c", "d">),
   8     path_exists(b, <"a", "b", "c", "x">),
   9     path_exists(b, <"a", "b", "c">),
  10     path_exists(b, <"a", "b", "x">),
  11     path_exists(b, <"a", "b">),
  12     path_exists(b, <"a", "x">),
  13     path_exists(b, <"a">),
  14     path_exists(b, <"x">),
  15     path_exists(b, <>),
  16   >;

Binding Path Implementation

Though lists obviously work, it's shorter and perhaps more natural to write a path like this:

   1 [x/y/z=1]

Than like this:

   1 <"x","y","z">

So probably the user should be able to specify the path to test for as a binding. Of course if we do that, people might want to test for the existence of multiple paths in one call, which wouldn't be too hard.

   1   /**nocache**/
   2   paths_exist(b, path) {
   3     return if _is_binding(path) then {
   4       result = TRUE;
   5       foreach [n = v] in path do
   6       {
   7         result = (result &&
   8                   (if _is_binding(b) && b!$n
   9                    then paths_exist(b/$n, v)
  10                    else FALSE));
  11       };
  12       value result;
  13     } else TRUE;
  14   };

Here's an example using this version:

   1   b = [a/b/c/d = 1, e/f/g/h = 2];
   2   test = <
   3     paths_exist(b, [a/b/c/d = 1, e/f/g/h = 1]),
   4     paths_exist(b, [a/b/c/x = 1, e/f/g/h = 1]),
   5     paths_exist(b, [a/b/c/d = 1, e/f/g/x = 1]),
   6     paths_exist(b, [a/b/c = 1,   e/f/g = 1]),
   7     paths_exist(b, [a/b/x = 1,   e/f/g = 1]),
   8     paths_exist(b, [a/b/c = 1,   e/f/x = 1]),
   9     paths_exist(b, [a/b = 1,     e/f = 1]),
  10     paths_exist(b, [a/x = 1,     e/f = 1]),
  11     paths_exist(b, [a/b = 1,     e/x = 1]),
  12   >;

Combined Implementation

As long as we're written implementations that use both lists and bindings, we might as well combine them:

   1   /**nocache**/
   2   paths_exist(b, path) {
   3     return if _is_binding(path) then {
   4       result = TRUE;
   5       foreach [n = v] in path do
   6       {
   7         result = (result &&
   8                   (if _is_binding(b) && b!$n
   9                    then paths_exist(b/$n, v)
  10                    else FALSE));
  11       };
  12       value result;
  13     } else if _is_list(path) then {
  14       value if _length(path) == 0 then TRUE else {
  15         n = _head(path);
  16         value if (_is_binding(b) && b!$n)
  17         then paths_exist(b/$n, _tail(path))
  18         else FALSE;
  19       };
  20     } else TRUE;
  21   };

Here's a test showing both kinds:

   1   b = [a/b/c/d = 1, e/f/g/h = 2];
   2   test = <
   3     "binding paths:"
   4     paths_exist(b, [a/b/c/d = 1, e/f/g/h = 1]),
   5     paths_exist(b, [a/b/c/x = 1, e/f/g/h = 1]),
   6     paths_exist(b, [a/b/c/d = 1, e/f/g/x = 1]),
   7     paths_exist(b, [a/b/c = 1,   e/f/g = 1]),
   8     paths_exist(b, [a/b/x = 1,   e/f/g = 1]),
   9     paths_exist(b, [a/b/c = 1,   e/f/x = 1]),
  10     paths_exist(b, [a/b = 1,     e/f = 1]),
  11     paths_exist(b, [a/x = 1,     e/f = 1]),
  12     paths_exist(b, [a/b = 1,     e/x = 1]),
  13     "list paths:",
  14     paths_exist(b, <"a", "b", "c", "d">),
  15     paths_exist(b, <"a", "b", "c", "x">),
  16     paths_exist(b, <"a", "b", "c">),
  17     paths_exist(b, <"a", "b", "x">),
  18     paths_exist(b, <"a", "b">),
  19     paths_exist(b, <"a", "x">),
  20     paths_exist(b, <"a">),
  21     paths_exist(b, <"x">),
  22     paths_exist(b, <>),
  23   >;