Suggested println improvement

Former Member
Former Member
I think the only use of println (and print) is for debugging but it 'casts' the objects on output so you loose the type information. For example:

Sys.println ([1, '1', "1"]);

outputs

[1, 1, 1]

whereas it would be more useful if it output

[1, '1', "1"]

If you get an error like:

UnexpectedTypeException: Expected Number, given Char

and you print out what you think is the offending variable and you get

1

this doesn't really help you whereas if it printed 1 or '1' it would help you.

Dave.
  • Former Member
    Former Member over 8 years ago
    Agreed ..

    But probably much worse is that the simulator will not actually really check 100% of your code for mistypes (like misspelled variables) and often the error handling will not tell you at all which line the error occurred when the device crashes. Spending an hour trying to find that one character is annoying.
  • The println() function converts the parameter to String (by calling a.toString() given the parameter a). The list/dictionary collections will do this recursively for each of their arguments. While I understand your intention, it is not uncommon for developers to want to display strings without quotes, and implementing your proposed enhancement this would make that much more difficult.

    If you want to do this, it isn't that hard to do. As a matter of fact, it is one of the first things that I implemented with MonkeyC. I just updated it to avoid some debugger warnings and use new conventions.

    If you wanted, you could implement this entirely as a single recursive function. My code is nice in that it allows you to register your own functions for dumping your class objects.

    using Toybox.Lang as Lang;
    using Toybox.System as Sys;

    //
    // _dump* helper functions are used by Dump module. they are
    // global to avoid the Dump module holding references to self
    // and causing circular reference errors from the debugger.
    //

    //
    // Dump primitive objects that need special behavior to
    // do so. Excludes recursive data structures like Array
    // and Dictionary, which are handled by functions below.
    //
    function _dumpPrimitive(obj) {
    if (obj instanceof Lang.Boolean) {
    return obj.toString();
    }
    else if (obj instanceof Lang.Char) {
    return Lang.format("'$1$'", [ obj ]);
    }
    else if (obj instanceof Lang.Double) {
    return obj.toString();
    }
    else if (obj instanceof Lang.Float) {
    return obj.toString(); // Lang.format("$1$F", [ obj ]);
    }
    else if (obj instanceof Lang.Long) {
    return obj.toString(); // Lang.format("$1$L", [ obj ]);
    }
    else if (obj instanceof Lang.Number) {
    return obj.toString();
    }
    else if (obj instanceof Lang.String) {
    return Lang.format("\"$1$\"", [ obj ]);
    }

    return null;
    }

    //
    // Dump an Array.
    //
    function _dumpArray(obj)
    {
    if (obj instanceof Lang.Array) {
    if (obj.size()) {
    var r = "[ ";

    var n = obj.size() - 1;
    for (var i = 0; i < n; ++i) {
    r += $.dumps(obj);
    r += ", ";
    }

    r += $.dumps(obj[n]);
    r += " ]";

    return r;
    }
    else {
    return "[]";
    }
    }

    return null;
    }

    //
    // Dump a Dictionary.
    //
    function _dumpDictionary(obj)
    {
    if (obj instanceof Lang.Dictionary) {
    if (obj.size()) {
    var r = "{ ";

    var k = obj.keys();
    var v = obj.values();

    var n = obj.size() - 1;
    for (var i = 0; i < n; ++i) {
    r += $.dumps(k);
    r += ": ";
    r += $.dumps(v);
    r += ", ";
    }

    r += $.dumps(k[n]);
    r += ": ";
    r += $.dumps(v[n]);
    r += " }";

    return r;
    }
    else {
    return "{}";
    }
    }

    return null;
    }

    //
    // Dump anything that has a `toString' method that is
    // not handled by one of the other helpers above.
    //
    function _dumpToString(obj) {
    if (obj has :toString) {
    return obj.toString();
    }

    return null;
    }

    module Dump {

    hidden var callbacks;
    hidden var registered;

    hidden function lazy_initialize() {
    callbacks = new [5];
    registered = 0;

    //
    // first registered will be last called
    //

    callbacks[registered] = new Lang.Method($, :_dumpToString);
    registered += 1;

    callbacks[registered] = new Lang.Method($, :_dumpDictionary);
    registered += 1;

    callbacks[registered] = new Lang.Method($, :_dumpArray);
    registered += 1;

    callbacks[registered] = new Lang.Method($, :_dumpPrimitive);
    registered += 1;
    }

    //! register a callback for dumping user-defined types
    function register(callback) {

    if (callbacks == null) {
    lazy_initialize();
    }

    if (callbacks.size() == registered) {

    var buf = new [registered + 2];

    for (var i = 0; i < registered; ++i) {
    buf= callbacks;
    }

    callbacks = buf;
    }

    callbacks[registered] = callback;
    registered += 1;
    }

    //! return a string representation of `obj'
    function dumps(obj) {

    if (callbacks == null) {
    lazy_initialize();
    }

    // last registered function is tried first
    for (var i = registered; 0 < i; --i) {
    var s = callbacks[i - 1].invoke(obj);
    if (s != null) {
    return s;
    }
    }

    return null;
    }
    }

    //! Convert an object to a string representation.
    //!
    //! The built-in primitive types are supported. Any type with a `toString' method will
    //! also work. If you want to add a special handler for an additional type, you can do
    //! so like this.
    //!
    //! function myDumpFunc(obj) {
    //! if (obj instanceof MyClass) {
    //! return obj.id.toString(); // generate a string for your object type
    //! }
    //!
    //! // return null if this handler does not know how to dump the object
    //! return null;
    //! }
    //!
    //! // Make a Method wrapper for the function and register it
    //! Dump.register(new Lang.Method($, :myDumpFunc));
    //!
    //! @param [Object] The object to convert.
    //! @return String A string representation of the object
    //!
    function dumps(obj) {
    return Dump.dumps(obj);
    }

    //! Convert an object to string representation and output it, as if by calling
    //! to Sys.println(dumps(obj)).
    function dump(obj) {
    Sys.println(dumps(obj));
    }
    [/code]

    This code...

    Sys.println(dumps(true));
    Sys.println(dumps(1));
    Sys.println(dumps(2l));
    Sys.println(dumps(3.0f));
    Sys.println(dumps(4.0));
    Sys.println(dumps({ 'a' => "abc", 1 => 123 }));


    Produces this output...

    true
    1
    2
    3.000000
    4.000000
    { 1: 123, 'a': "abc" }


    Travis
  • Former Member
    Former Member over 8 years ago
    Thanks Travis for the comprehensive reply.

    I still think println is inconsistent - it adds class adornments for arrays and dictionaries so I don't see adding quotes for strings and chars is any different really. If it had any use other than debugging I wouldn't suggest it.

    The SDK should include a pretty print feature like you coded so everyone doesn't have to roll their own if they want something like this.

    Dave.
  • I still think println is inconsistent - it adds class adornments for arrays and dictionaries so I don't see adding quotes for strings and chars is any different really.

    I'm trying to point out why what you are asking for is not a good idea.

    If it had any use other than debugging I wouldn't suggest it.

    If you wrote debugging code like this...

    function f(p) {
    Sys.println("f");
    Sys.println("");
    Sys.println("Lang.format("f($1$)", [ p ]));
    Sys.println('\n');
    }


    And you called f(1), the current system would output...

    f

    f(1)



    But you are expecting the system to output..

    "f"
    ""
    "f(1)"
    "'
    '"


    Are you really sure that is what you want? You are saying that you want strings to be quoted when they are printed, and this is the result that you would get. I have doubts that many other developers would want that. This is not how the C function printf() (or java's println(), or python's print, ...) works, so why should MonkeyC do it that way?

    I suppose the implementation of print() and println() could be specially modified to not quote the outermost string, but to quote all of the inner strings, but that seems weird as well.

    Travis
  • I agree with Travis. The changes you're suggesting might actually be changes to how println() uses type specific toSting()'s and that could cause headaches. (toString() is likely very involved in println() internally)

    for example, if you had:
    var x=[1,'1',"1"];
    dc.drawString(x,y,font,x.toString(),justifcation);


    on the screen you planed for [1,1,1] (as it works today) and not [1,'1',"1"]. (weird example, I know, but I wanted to use your same values, but what if it was x='A'; You'd expect A and not 'A'

    If I needed to check types, I'd do something like Travis suggested. It can easily be removed if no longer needed and won't mess with the way things work now and people are used to.
  • Former Member
    Former Member over 8 years ago
    I'm trying to point out why what you are asking for is not a good idea.


    If you wrote debugging code like this...

    function f(p) {
    Sys.println("f");
    Sys.println("");
    Sys.println("Lang.format("f($1$)", [ p ]));
    Sys.println('\n');
    }


    And you called f(1), the current system would output...

    f

    f(1)



    But you are expecting the system to output..

    "f"
    ""
    "f(1)"
    "'
    '"

    Travis


    The last one should give '\n' in my suggestion.


    Are you really sure that is what you want? You are saying that you want strings to be quoted when they are printed, and this is the result that you would get. I have doubts that many other developers would want that. This is not how the C function printf() (or java's println(), or python's print, ...) works, so why should MonkeyC do it that way?

    Travis


    C, java, python and most other languages can use the console for UI as well as debugging so automatically injecting useful class information is a bad idea. MonkeyC, on the other hand, only communicates debug information via the console - all UI is done via the watch screen, hence println could do something different - such as the Ruby inspect method which is what I have based my suggestion on.

    Consider the case when you are debugging and suspicious of the values of an array. You use println and it gives:
    [1, 2, 3, 4]
    but is this [1, 2, 3, 4] or ['1', '2', '3', '4'], or ["1", "2", "3", "4"] or some combination of these?

    Sure, you can use something like the code you have already supplied to dissect and display the array identifying the class as shown.

    I cannot think of any time when I would care if debug output had all strings and characters quoted appropriately.


    Dave.
  • Former Member
    Former Member over 8 years ago
    I agree with Travis. The changes you're suggesting might actually be changes to how println() uses type specific toSting()'s and that could cause headaches. (toString() is likely very involved in println() internally)

    for example, if you had:
    var x=[1,'1',"1"];
    dc.drawString(x,y,font,x.toString(),justifcation);


    on the screen you planed for [1,1,1] (as it works today) and not [1,'1',"1"]. (weird example, I know, but I wanted to use your same values, but what if it was x='A'; You'd expect A and not 'A'

    .


    I never once suggested changing toString(). println () could be changed as I am suggesting without changing how toString in your example will be displayed.

    Ruby has a puts similar to println (except individual array elements are printed on separate lines and there is no surrounding []). For a more informed output you would use the inspect method.

    Dave.
  • My point was that internally, println() probably make use of the same code as toString(), and making the change for println() could impact toString()
  • I cannot think of any time when I would care if debug output had all strings and characters quoted appropriately.

    If you can't think of a time where you would care if they were quoted, then why are you suggesting it as an improvement?

    That said, python does something like you're asking...

    print({ 1: 123, 'a': "abc" })
    print("abc")


    Produces this...

    {1: 123, 'a': 'abc'}
    abc


    Note that a above is a string.
  • I never once suggested changing toString(). println () could be changed as I am suggesting without changing how toString in your example will be displayed.

    If you don't change toString() (or provide something like it), you run into this problem...

    function f(p) {
    Sys.println("Lang.format("f($1$)", [ p ])); // there is an implicit p.toString() call here
    }

    // somewhere else in the code
    f([ 1, '1', "1", [ 2, '2', "2" ], { 3 => "333" } ]);


    If you just fixed println(), the output would be formatted like this...

    f([ 1, 1, 1, [ 2, 2, 2 ], { 3 => 333 } ])


    But you're expecting this...

    f([ 1, '1', "1", [ 2, '2', "2" ], { 3 => "333" } ])


    The reason is that p is converted to a string before being printed, and once it is a string no special formatting can be applied. To get what you want, you'd have to write...

    Sys.print("f(");
    Sys.print([ 1, '1', "1", [ 2, '2', "2" ], { 3 => "333" } ]); // assuming Sys.print were fixed to do the right thing as you're suggesting
    Sys.println(")");


    Which nobody wants to do. If toString() were fixed (for Lang.Array and Lang.Dictionary), this all just falls into place. Unfortunately, that would break compatibility. Adding a special function to do it would likely have an effect on the amount of memory available to applications for something that most people aren't asking for and can be easily implemented outside of the SDK. It just doesn't make sense to me.