Skip to content

Instantly share code, notes, and snippets.

@ilyash-b
Created February 9, 2022 11:02
Show Gist options
  • Save ilyash-b/fd87050fd55e5d0de65a19cbee4b3ceb to your computer and use it in GitHub Desktop.
Save ilyash-b/fd87050fd55e5d0de65a19cbee4b3ceb to your computer and use it in GitHub Desktop.
Autovivification for Next Generation Shell - experiment
# Following line will be needed when files will be auto-wrapped in "ns { ... }"
{ global deep_get, deep_set, deep_get_step, deep_set_step, deep_index_to_container_type }
doc %STATUS - experimental
F deep_get_step(node, _) {
throw LookupFail().set(container=node)
}
doc %STATUS - experimental
F deep_set_step(container, idx_or_key, next_idx_or_key) throw NotImplemented()
doc %STATUS - experimental
F deep_get_step(node, key) {
guard node =~ AnyOf(Hash, HashLike)
node[key]
}
doc %STATUS - experimental
F deep_set_step(node, key, val) {
guard node =~ AnyOf(Hash, HashLike)
node[key] = val
}
doc %STATUS - experimental
F deep_index_to_container_type(key) Hash
doc %STATUS - experimental
F deep_get_step(node:NormalTypeInstance, field:Str) {
node.(field)
}
doc %STATUS - experimental
F deep_set_step(node:NormalTypeInstance, field:Str, val) {
node.(field) = val
}
doc %STATUS - experimental
F deep_get_step(node, idx:Int) {
guard node =~ AnyOf(Arr, ArrLike)
node[idx]
}
doc %STATUS - experimental
F deep_set_step(node, idx:Int, val) {
guard node =~ AnyOf(Arr, ArrLike)
section "Ensure array is long enough" for(i=len(node);i<=idx;i+=1) {
node.push(null)
}
node[idx] = val
}
doc %STATUS - experimental
F deep_index_to_container_type(idx:Int) Arr
doc %STATUS - experimental
F deep_get(root, path, default=null) block b {
node = root
for p in path {
try {
node = deep_get_step(node, p)
} catch(lf:LookupFail) {
guard lf.container === node
b.return(default)
}
}
node
}
doc %STATUS - experimental
F deep_set(root, path:Arr, val) {
assert(path, 'path is expected to have at least one element, deep_set can not set the root')
path_iter = Iter(path)
container = null
idx_or_key = null
node = section "Navigate to last present element" block b {
node = root
while path_iter {
try {
container = node
idx_or_key = path_iter.next()
node = deep_get_step(node, idx_or_key)
} catch(lf:LookupFail) {
guard lf.container === node
b.return(node)
}
}
node
}
# echo("P $path_iter")
for p in path_iter {
# echo("P $path_iter $p")
next_container = deep_index_to_container_type(p)()
node = deep_set_step(container, idx_or_key, next_container)
container = next_container
idx_or_key = p
# echo("for end: container $container idx_or_key $idx_or_key")
}
deep_set_step(container, idx_or_key, val)
root
}
test("get existing hash->hash", {
h = {"a": {"b": 1}}
deep_get(h, ['a', 'b']).assert(1)
})
test("get non-existing hash->hash", {
h = {"a": {"b": 1}}
deep_get(h, ['a', 'bx']).assert(null)
deep_get(h, ['a', 'b', 'c']).assert(null)
})
test("set first level on custom type", {
type T
t = T()
t.deep_set(['field1'], 'val1').assert(T, 'deep_set should return object of type T')
t.assert({"field1": "val1"})
})
test("set two levels on custom type (hash)", {
type T
t = T()
t.deep_set(['field1', 'key1'], 'val1').assert(T, 'deep_set should return object of type T')
t.assert({"field1": {"key1": "val1"}})
})
test("set two levels on custom type (arr)", {
type T
t = T()
t.deep_set(['field1', 2], 'val1').assert(T, 'deep_set should return object of type T')
t.assert({"field1": [null, null, "val1"]})
})
@rdje
Copy link

rdje commented Feb 10, 2022

You could even provide a variant of deep_get() for probing for a path without constructing the said path.

It could be called

deep_peek()

This would be deep_get() without the side effect of constructing the path when consuming it.

For short, using get() would have a well explained and understood side effect, which could be exactly what the user is looking for and peek() which would travel along path without affecting anything, that is it would check for path in ninja or stealth mode that is without leave a trace.

That is deep_peek() won't autovivify but deep_get() will.

I really think you should not rule out this get/peek pair as the idea is to give people (me ? :) ) choices and not corner them as long as things are well explained so there is no surprise with what to expect.

What do you think ?

@rdje
Copy link

rdje commented Feb 10, 2022

Another comment to something you wrote earlier

You wrote

My view on that: the whole purpose of get family functions is that the caller knows the data is not necessarily there. If you know it's there, just use the regular h.a.b.c.

I assume that in

h.a.b.c

The keys a, b and c are identifiers, that is they match the following RE

/[[:alpha:]_]\w*/

Whereas using any of the deep_*() functions you would free the user from that constraint and allow any string of characters to be used when doing deep parkour along path which may be specified in pieces or components where each could be one of

  • Int
  • Str
  • Arr of either of the above. Ints and Strs may appear in the same Arr

At least that is how I see it.

@ilyash-b
Copy link
Author

The keys a, b and c are identifiers

Correct.

constraint

Nope. There is no constraint because of alternative syntax:

  • For Hash, like in JavaScript, one can use h["what ever"]
  • For objects, including user defined, the dot access syntax h.a access becomes h.("what ever")

@ilyash-b
Copy link
Author

For short, using get() would have a well explained and understood side effect

Somehow I think that anything with get should not have side effects. We can maybe add deep_make() or deep_ensure() or anything of that sort to act like deep_set() but without the final step of setting the leaf value.

@ilyash-b
Copy link
Author

deep_set(path, null)

mm... absence of an element is different than having it with null value in NGS.

@ilyash-b
Copy link
Author

I guess it's time to try the "discussions" feature of GitHub. Did not have the chance yet.

@ilyash-b
Copy link
Author

Moving discussion to ngs-lang/ngs#550

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment