-
-
Save ilyash-b/fd87050fd55e5d0de65a19cbee4b3ceb to your computer and use it in GitHub Desktop.
# 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"]}) | |
}) | |
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.
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 useh["what ever"]
- For objects, including user defined, the dot access syntax
h.a
access becomesh.("what ever")
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.
deep_set(path, null)
mm... absence of an element is different than having it with null
value in NGS.
I guess it's time to try the "discussions" feature of GitHub. Did not have the chance yet.
Moving discussion to ngs-lang/ngs#550
You could even provide a variant of deep_get() for probing for a path without constructing the said path.
It could be called
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 ?