I have written some code which assigns variables using the results of condition expressions without the explicit use of IF-ELSE statements.
In the simplest form, the problem looks like this:
Version 1
if (x < K)
y = A;
else
y = B;
I've seen a "trick" in the past in which people accomplish the same task in one line without the conditional like this:
Version 2
y = (x < K) * A + !(x < K) * B;
This approach extends relatively easily to handle IF-ELSE IF-ELSE assignments. The trick is to ensure that the conditions are all mutually exclusive.
From a unit testing perspective, I'm required to achieve 100% code path coverage.
My coworkers agree that the Version 2 is more elegant, but they contend it is less readable. Furthermore, they argue that I am "side-stepping" the path coverage requirement and that I would be able to achieve 100% path coverage by "hiding" the conditional logic inside the single line of code without actually exercising both conditions ((x < K) and !(x < K)).
I argue that I am able to blend Boolean algebra and numeric algebra to perform variable assignment because the computer treats Boolean 'true' and 'false' as '1' and '0' which can be multiplied by 'float' and 'int' variables. To me, it becomes simply an arithmetic expression with zeros and ones multiplying variables.
Why am I doing this?
I am doing this blend of Boolean and numeric algebra to minimize the number of IF-statements, minimize lines of code, and general code cleanup. Obviously performance can be improved by saving the result of the condition to a variable and referencing.
The Question
Is this practice (and ternary operators) frowned upon from a unit testing perspective?
If this question is too subjective, please suggest edits.
I'd suggest avoiding it (this trick is actually useful when the intention is to avoid branching, which may be the context you've seen it in). Given that the language doesn't have a conditional operator, you should be able to define the equivalent of
cond(bool, x, y) { if (bool) return x; else return y; }
yourself and write y = cond(x < K, A, B). It's more readable, harder to make a mistake when writing, is usable with non-number types, and is considered correctly in path coverage. It evaluates both sides, unlike the actual conditional operator (unless the language has macros or lazy evaluation), but so does the described trick.
Related
I understood most of the Backward Chaining algorithm (for first-order logic), but not what Standardize-Variables(rule) is for. Below is the pseudo-code of the algorithm:
function FOL-BC-Ask(KB, query) returns a generator of substitutions
return FOL-BC-Or(KB, query, {})
function FOL-BC-Or(KB, goal, θ) returns a substitution
for each rule in Fetch-Rules-For-Goal(KB, goal) do
(lhs ⇒ rhs) ← Standardize-Variables(rule)
for each θ' in FOL-BC-And(KB, lhs, Unify(rhs, goal, θ)) do
yield θ'
function FOL-BC-And(KB, goals, θ) returns a substitution
if θ = failure then return
else if Length(goals) = 0 then yield θ
else
first, rest ← First(goals), Rest(goals)
for each θ' in FOL-BC-Or(KB, Subst(θ, first), θ) do
for each θ'' in FOL-BC-And(KB, rest, θ') do
yield θ''
I'm studying on the book Artificial Intelligence - A Modern Approach and the code comes from there. The book simply says
FOL-BC-Or works by fetching all clauses that might unify with the goal, standardizing the variables in the clause to be brand-new variables, and then ...
I do understand this, but I do not understand why it needs to be done, or what would happen without it.
I hope someone can explain this. Thank you.
The reason for standardizing variables apart is rather mundane: scope. A variable is "local" to its clause, so when it appears in multiple clauses, it really should be treated as a different variable in each clause. Standardizing apart makes sure this is made clear by using different names in each clause.
Let me explain in more detail. In a normalized first-order logic theory, each clause is implicitly universally quantified. If I have a theory with two clauses
happy(X)
happy(X) or not friends(X,Y),
it means the same as
for all X: happy(X)
for all X : for all Y : happy(X) or not friends(X,Y)
You can think of "for all X" as a sort of "declaration" of X (in the programming sense of "declaration"), so each of these variables is, so to speak, "local" to the clause, in the same sense that a local variable in programming is local to its scope. It is pure coincidence that X is used in both clauses, and in fact we can rename them at will within each clause and obtain perfectly equivalent theories such as
for all U: happy(U)
for all V : for all W : happy(V) or not friends(V,W)
or even
for all X: happy(X)
for all Y : for all X : happy(Y) or not friends(Y,X)
Standardizing apart comes into play because if we try to unify these two clauses, there will be two variables with the the same name X even though they do not necessarily refer to the same entities. If we try to unify the two clauses above without standardizing apart first, we will unify X and Y and end up with
happy(X) or not friends(X,X)
which implies that both arguments of "friends" are the same even though that would not be implied if we simply renamed the variables. Unifying the same perfectly equivalent two clauses using U, V, W names results in
happy(U) or not friends(U, W)
where now the two arguments of "friends" are not required to be the same.
The fact that we obtained different results from unifying perfectly equivalent theories shows us that something must be incorrect. And indeed what is incorrect here is unifying two clauses that use a variable with the same name (X) even though they are not really the same variable and could be equivalently renamed to something else.
David Einsentat's comment is correct that failing to standardize apart is incorrect as it does not provide the most general unifier, because it may provide an unifier that has spurious constraints such as the equality we saw above, preventing it from being as general as it should.
Standardizing apart solves this problem by renaming the variables to "brand-new ones", meaning variables that do no appear anywhere else and which therefore do not pose the risk of colliding in this way and introducing a false equality based on purely arbitrary name choices.
Let's say we have a type called d:
type d = D of int * int
And we want to do some pattern matching over it, is it better to do it this way:
let dcmp = function
| D (x, y) when x > y -> 1
| D (x, y) when x < y -> -1
| _ -> 0
or
let dcmp = function
| D (x, y) ->
if x > y then 1 else if x < y then -1 else 0
Just in general is better to match patterns with many "when" cases or to match one pattern and the put an "if-then-else" in it?
And where can I get more information about such matters, like good practices in OCaml and syntactic sugars and such?
Both approaches have their cons and pros so they should be used accordingly to the context.
The when clause is easier to understand than if because it has only one branch, so you can digest a branch in a time. It comes with the price that when we analyze a clause in order to understand its path condition we have to analyze all branches before it (and negate them), e.g., compare your variant with the following definition, which is equivalent,
let dcmp = function
| D (x, y) when x > y -> 1
| D (x, y) when x = y -> 0
| _ -> -1
Of course, the same is true for if/then/else construct it is just harder to accidentally rearrange branches (e.g., during refactoring) in the if/then/else expression and completely change the logic of the expression.
In addition, the when guards may prevent the compiler from performing decision tree optimizations1 and confuse2 the refutation mechanism.
Given this, the only advantage to using when instead of if in this particular example is that when syntax looks more appealing as it perfectly lined up and it is easier for the human brain to find where are the conditions and their corresponding values, i.e., it looks more like a truth-table. However, if we will write
let dcmp (D (x,y)) =
if x = y then 0 else
if x > y then 1 else -1
we can achieve the same level of readability.
To summarize, it is better to use when when it is impossible or nearly impossible to express the same code with if/then/else. To improve readability it is better to factor your logic into helper functions with readable names. For example, with dcmp the best solution is to use neither if or when, e.g.,
let dcmp (D (x,y)) = compare x y
1)In this particular case the compiler will generate the same code for when and if/then/else. But in more general cases, guards may prevent the matching compiler from generating the efficient code, especially when branches are disjoint. In our case, the compiler just noticed that we're repeating the same branch and coalesced them into a single branch and turned it back into the if/then/else expression, e.g., here is the cmm output of the function with the when guards,
(if (> x y) 3 (if (< x y) -1 1))
which is exactly the same code as generated by the if/then/else version of the dcmp function.
2) Not to the state where it will not notice a missing branch, of course, but to the state where it will report missing branches less precisely or will ask you to add unnecessary branches.
Quoting the OCaml Towards Clarity and Grace style guide:
Code is more often read than written - make the life of the reader easy
and
Less code is better, cryptic code is worse
The first makes me think that the version with multiple when clauses is the better choice, as it makes it easy to predict or evaluate the result when reading the code depending on condition. The second goes further, against the if-then-else because, even if shorter, is cryptic when looking from afar.
Also, in the section Functions, we find out that "Pattern matching is the preferred way to define functions"
From a Haskell functional programmer's point of view.
I want to realize this function:
Do[
expr(1),
expr(2)...
expr(n),
{i,1,j}]
to execute expr(k), the result of expr(k-1) is required, so the function can not realized by simply multiple layer of do loop. How can I execute the function by do loop? Or by other loop in mathematica?(I also notice that both for and while loop can only support one expr just as do loop)
Try separating by semicolon rather than comma. Like:
y = 0
Do[y += x; Print[y], {x, 1, 5}]
Do loops, and many of the other control-flow constructs from imperative programming languages, are almost always not the right answer to Mathematica programming questions.
You don't tell us what expr is supposed to calculate so it's difficult to provide more than a very general answer ... so I'll use the factorial as a simple example of how one might program a function where expr[n] depends on expr[n-1]
fact[0] = 1
fact[n_] := n*fact[n-1]
Here, I've defined the factorial function in two rules; the first establishes the base case, and the second establishes the case for values other than 0. To avoid situations where the function is fed bad data we'd probably prefer a formulation such as
fact[0] = 1
fact[n_Integer /; n > 0] := n*fact[n-1]
In this version the function fact will only operate on positive integers or on 0.
(Note: to those knowledgeable about Mathematica: Yes I know that this is not a good way to program the factorial function, and that there is a built-in function for calculating factorials. But this is supposed to help someone who appears to be a complete novice.)
Imagine that we have a been given an Excel spreadsheet with three columns, labeled COND, X and Y.
COND = TRUE or FALSE (user input)
X = if(COND == TRUE) then 0 else Y
Y = if(COND == TRUE) then X else 1;
These formulas evaluate perfectly fine in Excel, and Excel does not generate a Circular Dependency error.
I am writing a compiler that tries to convert these Excel formulas to C code. In my compiler, these formulas do generate a circular dependency error. The issue is that (naïvely) the expression of X depends on Y and the expression for Y depends on X and my compiler is unable to logically continue.
Excel is able to accomplish this feat because it is a lazy, interpreted language. Excel will just lazily evaluate the formulas at run-time (with user inputs), and since no circular dependency occurs at run-time Excel has no problem evaluating such logic.
Unfortunately, I need to convert these formulas to a compiled language (not an interpreted one). The actual formulas, in the actual spreadsheets, have more complicated dependencies between multiple cells/variables (involving up to over half a dozen different cells). This means that my compiler has to perform some kind of sophisticated static, semantic analysis of the formulas and be smart enough to detect that there are no circular references if we "look inside" the conditional branches. The compiler would then have to generate the following C code from the above Excel formulas:
bool COND;
int X, Y;
if(COND) { X = 0; Y = X; } else { Y = 1; X = Y; }
Notice that the order of the assignment instructions is different in each branch of the if-statement in C.
My question is, is there any established algorithm or literature on compilers that explains how to implement this type of analysis in a compiler? Do functional programming language compilers have to solve this problem?
Why aren't standard optimization techniques adequate?
Presumably, the Excel formulas form a DAG with the leaves being primitive values and the nodes being computations/assignments. (If the Excel computation forms a cycle, then you need
some kind of iterative solver assuming you want a fixpoint).
If you simply propagate the conditional by lifting it (a class compiler optimization), we start with your original equations, where each computation is evaluated in any order WRT to others, such that the result computes dag-like (that "anyorder" is an operator intending to model that):
X = if(COND == TRUE) then 0 else Y;
anyorder
Y = if(COND == TRUE) then X else 1;
then lifting the conditional:
if (COND) { X=0; } else { X = 1; }
anyorder
if (COND) { Y=X; } else { Y = 1; }
then
if (COND) { X=0; anyorder Y=X; } else { X = Y; anyorder Y = 1; }
Each of the arms must be dag-like.
The first arm is daglike evaluating the X=0 assignment first.
The second arm is daglike evaluating Y=1 first. So, we get the answer you wanted:
if (COND) { X=0; Y=X; } else { Y = 1; X = Y; }
So conventional transformations and knowledge about anyorder-if-daglike knowledges
seems to give the right effect.
I'm not sure what you do if COND is computed as a function of the cells.
I suspect the way to do this is to generate a dependency graph of computations with
with conditionals on the dependencies. You probably have to propagate/group those conditionals over the arcs more as less as I did over the syntax.
Yes, literature exists, sorry I cannot quote any, I simply don't remember and would it just google up just as you can..
Basic algos for dependency and cycle analysis are really simple. I.e. detect symbols in the expression, build a set of expressions and dependencies in form:
inps expr outs
cell_A6, cell_B7 -> expr3 -> cell_A7
cell_A1, cell_B4 -> expr1 -> cell_A5
cell_A1, cell_A5 -> expr2 -> cell_A6
and then by comparing and iteratively expanding/replacing sets of inputs/outputs:
step0:
cell_A6, cell_B7 -> expr3 -> cell_A7
cell_A1, cell_B4 -> expr1 -> cell_A5 <--1 note that cell_A5 ~ (A1,B4)
cell_A1, cell_A5 -> expr2 -> cell_A6 <--1 apply that knowledge here
so dependency
cell_A1, cell_A5 -> expr2 -> cell_A6
morphs into
cell_A1, cell_B4 -> expr2 -> cell_A6 <--2 note that cell_A6 ~ (A1,B4) and so on
Finally, you will get either a set of full dependencies, where you can easily detect circular dependencies, like for example:
cell_A1, cell_D6, cell_F7 -> exprN -> cell_D6
or, if none found - you will be able to determine a safe, incremental order of the execution.
If the expressions contain branches or sideeffects other than the 'returned value', you can apply various transformations to reduce/expand the expressions into new ones, or into groups of new expressions that will be of the form above. For example:
B5 = { if(A5 + A3 > 0) A3-1 else A5+1 }
so
inps ... outs
A3, A5 -> theExpr -> B5
the condition can be 'lifted' and form two conditional rules:
A5 + A3 > 0 : A3 -> reducedexpr "A3-1" -> B5
A5 + A3 <= 0 : A5 -> reducedexpr "A5-1" -> B5
but now, your execution/analysis must also take care of the conditions before applying the rules. Lifting is only one of possible transformations.
However, you stil need something more than that, at least some an 'extension' for it. The hard part of your problem is that your expressions are complex, have branches, and you need to include user-random input to resolve branches to eliminate the dead branches and break dead dependencies.
Since the key is elimination of dead dependencies, you have to somehow detect dead branches. Conditions can be of any arbitrary complexity, and user-input is random, so you cannot work it out completely statically, really. After playing with transformations, you would still have to analyze the conditions and generate code accordingly. To do so, you would need to generate code for all possible combinations of the outcomes of the conditions, and all resulting branching and rule combinations, which is simply infeasible except for some trivial cases. With number of unknown the number of leafs can grow exponentially (2^N) which is a huge bloat after crossing some threshold.
Of course while analyzing conditions based on Bools, you can analyze, group and eliminate conflicting conditions like (a & b & !a)..
..but if your input values and conditions include NON-BOOL data, like integers or floating or strings, just imagine your condition is have a condition that executes some external weird statistical function and checks its result.. Ignore the 'weird' part and focus on 'external'. If you meet some expressions that use complex functions like AVG or MAX, you cannot chew through something like that statically(*). Even simple arithmetic is hard to analyze: (a+b)*(c+d) - you could derive a fact that c+d can be ignored when a+b==0, but this a really tough task to cover fully..
IIRC, doing a satisfiability analysis (SAT) for boolean expressions with basic operators is an NP-hard problem, not mentioning integers or floating points with all their math.. Calculating the result of expression is much easier than telling which values does it really depend on!!
So, since input values may be either hardcoded (cool) or user-supplied at runtime (doh!), your compler most probably will not be able to fully analyze it up front. Now link it with the fact marked as (*) and it's quite obvious that you can include some static analysis and try to eliminate some branches at 'compilation time', but still there might be some parts that must be delayed until the user provides the actual input.
So, if part of the analysis must be done at runtime, all the branch elimination is just an optional optimisation and I think you should focus on the runtime part now.
At minimal unoptimized version, your generated program could simply remember all the excel-expressions and wait for input data. Once the program is run and input is given, the program has to substitute the input in the expressions, and then try to iteratively reduce them to output values.
Writing such algo in imperative language is completely possible. Actually, you'd need to write it once, and later you'd just merge it with a different sets of rules derived from cell-formulas and done. Runtime part of the program would be the same, formulas would change.
You could then expand the 'compiler' side to try to help by i.e. preliminarily partially analyzing the dependencies and trying to reorder the rules so later they will be checked in a "better order", or by precalculating constants, or inlining some expressions and so on but as I said, it's all optimizations, not core feature.
Sadly, I cannot really tell you much anything serious about the "functional languages", but since usually their runtimes are 'very dynamic' and sometimes they even execute the code in terms of symbols and transformations, it could reduce the complexity of your 'compiler' and 'engine' part. The most valuable asset here is the dynamism. So, even a Ruby would do much better than C - but in no way it's a "compiled" language as you'd say.
For example, you could try to transform excel rules directly into functions:
def cell_A5 = expr1(cell_A1, cell_B4)
def cell_A7 = expr3(cell_A6, cell_B7)
def cell_A6 = expr2(cell_A1, cell_A5)
write it down as part of the program, then when at runtime when the user provides some values, you'd those would just redefine some of the parts of the program
cell_B7 = 11.2 // filling up undefined variable
cell_A1 = 23 // filling up undefined variable
cell_A5 = 13 // overwriting the function with a value
That's the power of dynamic platforms, nothing very 'functional' here. Dynamic platforms make it easy to fill/override bits. But then, once the user provided some bits and once the program has been "corrected on the fly", which one function would you call first?
The answer is somewhat sad.. You don't know.
If your dynamic language has some rule-engine built into it, you can try generating rules instead of functions and later rely on that engine to "fill up" everything that is possible to calculate.
But if it doesn't have rule engine, you are back to point one..
afterthought:
Hm.. sorry, I think I just wrote too much and too vaguely/chatty. If you think it's helpful, please drop me a comment. Otherwise I'll delete it after few days or a week.
If I have code for some function f (that takes in one input for simplicity), I need to decide if the input x affects the output f(x), i.e, if f is a constant function defined below.
Define f to be constant function if output of f is invariant w.r.t x. This should hold for ALL inputs. So for example, if we have f(x) = 0 power x, it may output 0 for all inputs except for x = 0, where it may output error. So f is not a constant function.
I can only do static analysis of the code and assume the code is Java source for simplicity.
Is this possible?
This is obviously at least as hard as solving the Halting Problem (proof left as an exercise), so the answer is "no", this is not possible.
It is almost certainly possible. In most cases. Where there aren't weird thing going on.
For normal functions, the ordinary, useful kind that actually return values rather than doing their own little thing, yes.
For a simple function, not recursive, no nastiness of that sort, doing it manually, I would probably make the static-analysis equivalent of a sign chart, where I examine the code and determine every value of x that might possibly be a boundary condition or such (e.g. the code has if (x < 0) somewhere in it, so I check the function for values of x near 0). If this sort of attempt is doomed to fail please tell me before I try to use it on something.
Using brute force to grind away at it could work, unless you are working with quadruple precision x values or something similarly-sized, because then brute force could take years. Although at that point its not really static-analysis anymore.
Static-analysis generally really means having a computer tell you by looking at the code, not you looking at it yourself (at least not very much). Algorithms exist for doing this in many languages, wikipedia has such a list, including some free or even open source.
The ultimate proof that something can be done is for it to have been done already.
Since you'd call a non-terminating function non-constant, here's the reduction from your problem to the halting problem:
void does_it_halt(...);
int f(int x) {
if(x == 1) {
does_it_halt();
}
return 0;
}
Asking if f is constant is equivalent to asking if does_it_halt halts. Therefore, what you're asking for is impossible, since the halting problem is undecidable.