I see the subject is diverging and close to be closed…
Yet while my last post had a testing purpose, it did lack context and conclusion, I am completing it with this present post. (I am not considering JSON usage, my use-cases of None-checks are mainly performed within class constructors taking different optional arguments.)
What I was trying to assess is “Would it be possible to process every kind of complex expressions performing None checks in different manners as logical operators just with one container for which these logical operations work consistently.”
The answer is yes, but trying to instanciate these obect containers to perform operations on, you also need the reverse “containing” operation (some ‘get’) function.
Let say you want to perform this :
def select_safe(a, b1, b2, c):
if a is not None:
return a
elif b1 is not None and b2 is not None:
return (b1, b2)
elif c is not None:
if c[d] is not None:
if c[d][e] is not None:
return c[d][e]
return None
selected = select_safe(a, b1, b2, c)
You might want to write the operations resulting in selected object in pseudo-code as
# pseudo-code ('|' stands for 'or', '&' for 'and', '@' for 'at key/index')
selected = ?{a | b1 & b2 | c @ d @ e}
(Notice that I did not use conventional parenthesis (), but braces {}, because this is not a def, there are multiple separators between arguments (|, &, @), while def takes a unique separator (,), because a pre-processing is required on each argument before the operators execution)
- Minimal pseudo-code implementation possibilities in current python
Taking that pseudocode as a basis, it is possible to write its computation in several ways by defining proper functions in current python (I skip writing the functions) :
(nesting functions):
selected = safe_or(a, safe_or(safe_and(b1, b2), safe_at(safe_at(c, d), e)))
(passing references to operators as arguments):
selected = select_safe_parsing(a, '|', b1, '&', b2, '|', c, '@', d, '@', e)
(with chained constructors):
selected = select_safe_construct(a).or(b1).and(b2).or(c).at(d).at(e)
(instanciating the containers):
selected = (Ω(a) | Ω(b1) & Ω(b2) | Ω(c) @ Ω(d) @ Ω(e)).get()
(Notice two things in this case : There is no way to obviate the usage of some get here. The Ω calls could be removed excepted the first one, by carefully leveraging dynamic typing.)
- Necessity of xor and xor_index retrieval
Finally, I think all of this would not really be useful without a mutex (mutually-exclusive) check in real-case scenarios. However, you have a mutex by introducing the xor operator, but the real value you want is the index of which part is not None within the succession of expressions between the xor operators. You might write this in pseudo-code as
#pseudo-code, mutex index ('^' stand for 'xor')
selected_xor_index = ?xor_index{a ^ b1 & b2 ^ c @ d @ e}
(selected_xor_index will be 0 if a is defined, or 1 if b1 and b2 are defined, or 2 if c.d.e is defined)
Assuming this is valid python code, you obtain the following usage possibility for processing based on optional parameters :
match ?xor_index{a ^ b1 & b2 ^ c @ d @ e}:
case 0 : value = process_a(a)
case 1 : value = process_b(b1, b2)
case 2 : value = process_c(c.d.e)
It is possible to manage a wide generalization of combinations of None-checks by implementing four operators (‘or’, ‘and’, ‘at’, ‘xor’) in a consistent (and possibly one-lined) way. Some kind of ‘metafunction’ (function that selects sub-function based on multiple possible separators) would directly provide this possibility. You also need a way to return the xor index to fully benefit from the None-checks combination.