Monkey C: Logical OR and AND behave inconsistently/incorrectly for Number types

o A Descriptive Title
Monkey C: Logical OR and AND behave inconsistently/incorrectly for Number types

o The Environment:
Windows 10
Eclipse Oxygen (4.7.0)
Connect IQ 2.3.1

o A detailed description of the issue
In Monkey C, Logical OR and logical AND operators are not applied to Numbers in a consistent manner. I assume that the same issue exists for Longs, although I have not tested it.

OR:
In Monkey C, logical OR applied to two Numbers seems to behave like JavaScript.
In JS, X || Y returns X if X is truthy, Y otherwise.
e.g. 5 || 12 equals 5.

AND:
In Monkey C, logical AND applied to Numbers seems to behave like bitwise AND, which is not expected or consistent. In Monkey C, 5 && 12 equals 4
In JS, X && Y returns Y if X is truthy, X otherwise.
e.g. 5 && 12 equals 12.


o Steps to reproduce the issue
Build and run the following test code in the simulator (e.g. FR230, FR935). Observe the console output.

Actual output:
x = 5
y = 12
x | y = 13
x || y = 5
x & y = 4
x && y = 4


Expected output (javascript-style behaviour)
x = 5
y = 12
x | y = 13
x || y = 5
x & y = 4
x && y = 12


Expected output (C-style behaviour). (Provided to show that Monkey C's behaviour is consistent with neither javascript, C nor itself).
x = 5
y = 12
x | y = 13
x || y = 1
x & y = 4
x && y = 1




o Any applicable additional information
Related bug report: https://forums.garmin.com/forum/deve...and-long-types

As a side note, it seems inconsistent that logical operators can apply to Numbers stored in variables, but not constants. The latter produces a compilation error.

Also, it's a little disappointing that this kind of thing doesn't seem to be documented, either explicitly or through examples.

o A code sample that can reproduce the issue
Please replace square brackets with parens in the following code.
using Toybox.WatchUi as Ui;

class testdatafieldView extends Ui.SimpleDataField {

function initialize[] {
SimpleDataField.initialize[];
var x = 5;
var y = 12;
System.println["x = " + x];
System.println["y = " + y];
System.println["x | y = " + [x | y]]; // behaves as expected , prints 13
System.println["x || y = " + [x || y]]; // behaves like javascript, prints 5
System.println["x & y = " + [x & y]]; // behaves as expected, prints 4
System.println["x && y = " + [x && y]]; // behaves like bitwise AND, prints 4 [!] [javascript would print 12]
}

function compute[info] {
return 0.0;
}
  • Here is a test case for all of the logical operators for the integral types (Lang.Number, Long and Boolean)..

    using Toybox.Application as App;
    using Toybox.System as Sys;
    using Toybox.Lang as Lang;
    using Toybox.WatchUi as Ui;

    function do_test_logical_not_operator<[a, _f, _t, logger]> {

    var c = !a;
    var d = a ? _f : _t;

    if <[c != d]> {
    logger.error<[Lang.format<["!$1$ = $2$, expected $3$", [ a, c, d ]]>]>;
    return 1;
    }

    logger.debug<[Lang.format<["!$1$ = $2$", [ a, c ]]>]>;
    return 0;
    }

    function do_test_logical_and_operator<[a, b, _f, _t, logger]> {
    var c = a && b;
    var d = a ? <[b ? _t : _f]> : _f;

    if <[c != d]> {
    logger.error<[Lang.format<["$1$ && $2$ = $3$, expected $4$", [ a, b, c, d ]]>]>;
    return 1;
    }

    logger.debug<[Lang.format<["$1$ && $2$ = $3$", [ a, b, c ]]>]>;
    return 0;
    }

    function do_test_logical_or_operator<[a, b, _f, _t, logger]> {
    var c = a || b;
    var d = a ? _t : <[b ? _t : _f]>;

    if <[c != d]> {
    logger.error<[Lang.format<["$1$ || $2$ = $3$, expected $4$", [ a, b, c, d ]]>]>;
    return 1;
    }

    logger.debug<[Lang.format<["$1$ || $2$ = $3$", [ a, b, c ]]>]>;
    return 0;
    }


    function test_binary_logical_operator<[logger, a, symbol]> {
    var failures = 0;

    var func = new Lang.Method<[$, symbol]>;

    for <[var i = 0; i < a.size<[]>; ++i]> {
    for <[var j = 0; j < a.size<[]>; ++j]> {
    failures += func.invoke<[a, a[j], a[0], a[1], logger]>;
    }
    }

    return failures == 0;
    }

    function test_unary_logical_operator<[logger, a, symbol]> {
    var failures = 0;

    var func = new Lang.Method<[$, symbol]>;

    for <[var i = 0; i < a.size<[]>; ++i]> {
    failures += func.invoke<[a, a[0], a[1], logger]>;
    }

    return failures == 0;
    }


    <[:test]> function test_logical_and_boolean<[logger]> {
    return test_binary_logical_operator<[logger, [ false, true ], :do_test_logical_and_operator]>;
    }

    <[:test]> function test_logical_and_number<[logger]> {
    return test_binary_logical_operator<[logger, [ 0, 1, 2, 3 ], :do_test_logical_and_operator]>;
    }

    <[:test]> function test_logical_and_long<[logger]> {
    return test_binary_logical_operator<[logger, [ 0l, 1l, 2l, 3l ], :do_test_logical_and_operator]>;
    }

    <[:test]> function test_logical_or_boolean<[logger]> {
    return test_binary_logical_operator<[logger, [ false, true ], :do_test_logical_or_operator]>;
    }

    <[:test]> function test_logical_or_number<[logger]> {
    return test_binary_logical_operator<[logger, [ 0, 1, 2, 3 ], :do_test_logical_or_operator]>;
    }

    <[:test]> function test_logical_or_long<[logger]> {
    return test_binary_logical_operator<[logger, [ 0l, 1l, 2l, 3l ], :do_test_logical_or_operator]>;
    }


    <[:test]> function test_logical_not_boolean<[logger]> {
    return test_unary_logical_operator<[logger, [ false, true ], :do_test_logical_not_operator]>;
    }

    <[:test]> function test_logical_not_number<[logger]> {
    return test_unary_logical_operator<[logger, [ 0, 1, 2, 3 ], :do_test_logical_not_operator]>;
    }

    <[:test]> function test_logical_not_long<[logger]> {
    return test_unary_logical_operator<[logger, [ 0l, 1l, 2l, 3l ], :do_test_logical_not_operator]>;
    }

    class ZZZView extends Ui.View
    {
    function initialize<[]> {
    View.initialize<[]>;
    }
    }

    class ZZZApp extends App.AppBase
    {
    function initialize<[]> {
    AppBase.initialize<[]>;
    }

    function getInitialView<[]> {
    return null;
    }
    }
    [/code]

    Note that in the above code I've substituted <[ for ( and ]> for ) so the forums will accept it.

    Here is the output I get...

    ------------------------------------------------------------------------------
    Executing test test_logical_and_boolean...
    DEBUG (20:45): false && false = false
    DEBUG (20:45): false && true = false
    DEBUG (20:45): true && false = false
    DEBUG (20:46): true && true = true
    PASS
    ------------------------------------------------------------------------------
    Executing test test_logical_and_number...
    DEBUG (20:46): 0 && 0 = 0
    DEBUG (20:46): 0 && 1 = 0
    DEBUG (20:46): 0 && 2 = 0
    DEBUG (20:46): 0 && 3 = 0
    DEBUG (20:46): 1 && 0 = 0
    DEBUG (20:46): 1 && 1 = 1
    ERROR (20:46): 1 && 2 = 0, expected 1
    DEBUG (20:46): 1 && 3 = 1
    DEBUG (20:46): 2 && 0 = 0
    ERROR (20:46): 2 && 1 = 0, expected 1
    ERROR (20:46): 2 && 2 = 2, expected 1
    ERROR (20:46): 2 && 3 = 2, expected 1
    DEBUG (20:46): 3 && 0 = 0
    DEBUG (20:46): 3 && 1 = 1
    ERROR (20:46): 3 && 2 = 2, expected 1
    ERROR (20:46): 3 && 3 = 3, expected 1
    FAIL
    ------------------------------------------------------------------------------
    Executing test test_logical_and_long...
    DEBUG (20:46): 0 && 0 = 0
    DEBUG (20:46): 0 && 1 = 0
    DEBUG (20:46): 0 && 2 = 0
    DEBUG (20:46): 0 && 3 = 0
    DEBUG (20:46): 1 && 0 = 0
    DEBUG (20:46): 1 && 1 = 1
    ERROR (20:46): 1 && 2 = 0, expected 1
    DEBUG (20:46): 1 && 3 = 1
    DEBUG (20:46): 2 && 0 = 0
    ERROR (20:46): 2 && 1 = 0, expected 1
    ERROR (20:46): 2 && 2 = 2, expected 1
    ERROR (20:46): 2 && 3 = 2, expected 1
    DEBUG (20:46): 3 && 0 = 0
    DEBUG (20:46): 3 && 1 = 1
    ERROR (20:46): 3 && 2 = 2, expected 1
    ERROR (20:46): 3 && 3 = 3, expected 1
    FAIL
    ------------------------------------------------------------------------------
    Executing test test_logical_or_boolean...
    DEBUG (20:46): false || false = false
    DEBUG (20:46): false || true = true
    DEBUG (20:46): true || false = true
    DEBUG (20:46): true || true = true
    PASS
    ------------------------------------------------------------------------------
    Executing test test_logical_or_number...
    DEBUG (20:46): 0 || 0 = 0
    DEBUG (20:46): 0 || 1 = 1
    ERROR (20:46): 0 || 2 = 2, expected 1
    ERROR (20:46): 0 || 3 = 3, expected 1
    DEBUG (20:46): 1 || 0 = 1
    DEBUG (20:46): 1 || 1 = 1
    DEBUG (20:46): 1 || 2 = 1
    DEBUG (20:46): 1 || 3 = 1
    ERROR (20:46): 2 || 0 = 2, expected 1
    ERROR (20:46): 2 || 1 = 2, expected 1
    ERROR (20:46): 2 || 2 = 2, expected 1
    ERROR (20:46): 2 || 3 = 2, expected 1
    ERROR (20:46): 3 || 0 = 3, expected 1
    ERROR (20:46): 3 || 1 = 3, expected 1
    ERROR (20:46): 3 || 2 = 3, expected 1
    ERROR (20:46): 3 || 3 = 3, expected 1
    FAIL
    ------------------------------------------------------------------------------
    Executing test test_logical_or_long...
    DEBUG (20:46): 0 || 0 = 0
    DEBUG (20:46): 0 || 1 = 1
    ERROR (20:46): 0 || 2 = 2, expected 1
    ERROR (20:46): 0 || 3 = 3, expected 1
    DEBUG (20:46): 1 || 0 = 1
    DEBUG (20:46): 1 || 1 = 1
    DEBUG (20:46): 1 || 2 = 1
    DEBUG (20:46): 1 || 3 = 1
    ERROR (20:46): 2 || 0 = 2, expected 1
    ERROR (20:46): 2 || 1 = 2, expected 1
    ERROR (20:46): 2 || 2 = 2, expected 1
    ERROR (20:46): 2 || 3 = 2, expected 1
    ERROR (20:46): 3 || 0 = 3, expected 1
    ERROR (20:46): 3 || 1 = 3, expected 1
    ERROR (20:46): 3 || 2 = 3, expected 1
    ERROR (20:46): 3 || 3 = 3, expected 1
    FAIL
    ------------------------------------------------------------------------------
    Executing test test_logical_not_boolean...
    DEBUG (20:46): !false = true
    DEBUG (20:46): !true = false
    PASS
    ------------------------------------------------------------------------------
    Executing test test_logical_not_number...
    ERROR (20:46): !0 = -1, expected 1
    ERROR (20:46): !1 = -2, expected 0
    ERROR (20:46): !2 = -3, expected 0
    ERROR (20:46): !3 = -4, expected 0
    FAIL
    ------------------------------------------------------------------------------
    Executing test test_logical_not_long...
    ERROR (20:46): !0 = -1, expected 1
    ERROR (20:46): !1 = -2, expected 0
    ERROR (20:46): !2 = -3, expected 0
    ERROR (20:46): !3 = -4, expected 0
    FAIL

    ==============================================================================
    RESULTS
    Test: Status:
    test_logical_and_boolean PASS
    test_logical_and_number FAIL
    test_logical_and_long FAIL
    test_logical_or_boolean PASS
    test_logical_or_number FAIL
    test_logical_or_long FAIL
    test_logical_not_boolean PASS
    test_logical_not_number FAIL
    test_logical_not_long FAIL
    Ran 9 tests

    FAILED (passed=3, failed=6, errors=0)


    Based on that it is exactly the same as the bug I previously reported, it just extends the scope to include the binary logical operators.
  • I do agree with you though.. the documentation should call out what the expected behavior is. In cases where it doesn't, I typically assume it is supposed to behave like C.

    That said, with logical operators, it seems that it would be okay if the functions returned boolean values (0 || 3 => true). If my reading is correct, this is how Ruby behaves.
  • That said, with logical operators, it seems that it would be okay if the functions returned boolean values 0 || 3 => true. If my reading is correct, this is how Ruby behaves.


    I actually think it would make more sense to return true than to return 1 in this case, so I'd prefer the ruby implementation over the c implementation.

  • My main preference would be for it to be consistent and documented. The javascript style is more flexible and useful in certain cases, but returning true or false would be fine as well, and which actually work much better with Monkey C, since it doesn't auto-convert integers to booleans, not even if you apply "NOT" or "NOT NOT". e.g. "NOT 0" is invalid Monkey C. Although "var x = 0; ... NOT x" is valid. [Which also seems inconsistent to me.]

    Returning 1 or 0 wouldn't fit with Monkey C, for the same reason.

    [I typed NOT instead of exclamation marks because of the forum.]

    Sometimes turning logical ops into bitwise ops also doesn't make sense to me. If it was every time, at least it would be consistent.

    Maybe the best of both worlds would be:
    - Act like Javascript
    - Allow !!x to convert anything into a boolean [or preferably, auto-convert anything into a boolean]

    But maybe it would be simpler just to return true/false.

    My only concern would be that changing the language now could break lots of existing apps.
  • I think our goal is to behave like Javascript, but I'll need to confirm this. Python works similarly:

    https://docs.python.org/2/reference/expressions.html#boolean-operations

    '1' and '0' are special cases and I believe are treated as equivalent to 'true' and 'false'. This gives some confusing results in a comparison like '1 && 2', which is attempting to compare a Boolean value with a Number, and returns '0' (indicating 'false') rather than evaluating both as Number values. At least, that's the behavior I'm seeing, and is consistent with the tests Travis put together. Whatever the case, I think there's a bug here.

    Our documentation has been pretty thin regarding operator behavior, too, and that's being addressed. However, taking a look at the revisions we did, we still don't quite do enough. All of our examples in the revised docs compare '1' and '0'. It's a nice illustration, but it doesn't cover logical comparison of other things like Numbers. I'll address that, too.
  • To be clear, if the implementation is behaving like JavaScript/Python the implementation of || seems to be correct, but && and ! are still broken. I'll update my test above when I get home tonight.
  • "In JS, X && Y returns Y if X is truthy, X otherwise"

    so this means:
    4 && 12 = 12
    12 && 4 = 4

    I can't say I'm a fan of this javascript implementation of the &&, as that means that x && y != y && x, surely you can have strange effects due to that...

  • Well, I assumed it worked like C initially and that isn't the case either.

    The javascript implementation is useful for writing simpler code in some cases, because there are a lot of times you want to choose between the truthier of two values.

    Instead of (C):
    var config = X ? X : Y;


    You can write (JS)
    var config = X || Y;


    A common pattern in js is:
    function f(fnParam)
    {
    fnParam = fnParam || defaultValue;
    }

    Here fnParam is optional; if not specified, it's value is "undefined', and we want to set it to some default value in that case. Of course, this exact scenario doesn't apply to Monkey C, since there's no optional params or undefined values.

    It's more than syntactic sugar, because in the first example, X is evaluated twice (so if it were a function and not a variable, the function would be called twice). The second example (in JS) avoids any side effects associated with evaluating X twice.

    The first case would more properly be written like this, to make it equivalent to the second case, assuming that X and Y could be function calls or macros:
    var tmp = X;
    var config = tmp ? tmp: Y;



    Due to logical short-circuit behaviour in most C-style languages, X && Y is never equivalent to Y && X anyway, even if they always return true or false, unless X and Y are variables, instead of functions or macros.

    They may always evaluate to the same value in some languages, but if X and Y are functions or macros (with possible side effects), then changing the order of the operands affects:
    - The order that X and Y are evaluated (which would be the case even without short-circuit behaviour)
    - Whether X or Y get evaluated in the first place
    e.g. X && Y: If X is falsey, then Y will never be evaluated (and its side effects never happen).
    Similar argument applies to X || Y versus Y || X.

    Also, in JS, !! can be used to coerce anything into a boolean. So !!(12 && 4) would be equal to !!(4 && 12). C programmers often use !! to coerce ints to one or zero, especially when dealing with flags stored as individual bits.

    Unfortunately, !! doesn't work that way in monkey C right now.

    Like I said, it might just be simpler to have all logical operators return true or false.
  • I'm sorry, but "truthier" sound like a term Stephen Colbert uses, but not something that should be used in any real programing languages. Well, maybe in AI languages like Lisp, but that's about it.... :) Something is true or false, Numbers can be greater or less than each other. (Long time c guy here...)
  • With the current behavior the || shortcut can be useful when implementing checks to see if a application setting has a value...

    var setting = App.getApp<[]>.getProperty<["abc"]>;
    if <[setting != null]> {
    setting = 1.0;
    }


    You could simplify that down to this...

    var setting = App.getApp<[]>.getProperty<["abc"]> || 1.0;


    This is cleaner, but it doesn't read properly (i.e., it doesn't make sense to me). It is also potentially useful for map lookups...

    var setting = map[key] || dflt_value;


    Travis