Monkey C: How to simulate abstract static methods?

A question for the Monkey C experts out there:

I'm looking for some advice on handling a design pattern issue in Monkey C.

Even though Monkey C doesn’t officially support abstract classes, I like to simulate them by defining "abstract" methods that simply throw an exception. This is especially handy when a method is supposed to return an object, and I don't want to create a dummy return value. With this approach, I can safely have code in the abstract class that calls the method, while ensuring that each subclass implements it properly.

This strategy works fine for instance methods.
However, I’m running into problems with static methods.

Here's the situation:

  • I have a static function A.doMore() in an abstract-like class A.

  • Inside doMore(), I want to call an abstract static method A.doSomething(), which would actually be implemented in each subclass, like B.doSomething().

  • So when I call B.doMore(), it should internally call B.doSomething() - not A.doSomething().

But when I try this:

  • Simply calling doSomething() inside A.doMore() just calls A.doSomething().

  • Using self.doSomething() doesn't solve it either.

  • And I can’t hard-code B.doSomething() in A, because I want it to work generically across multiple subclasses.

Is this just a limitation of Monkey C’s type system and static method handling, or is there some workaround I'm missing?

Thanks for any insight you can share!

Below is some sample code illustrating the issue:

class A {
    public static function doSomething() as Void {
        throw new AbstractMethodException( "A.doSomething" );
    }
    public static function doMore() as Void {
// !! This is the issue !!
        doSomething();
    }
}

class B extends A {
    public static function doSomething() as Void {
        // this is what should get done
    }
}

class C {
    public function doItAll() as Void {
        B.doMore();
    }
}
  • Is this just a limitation of Monkey C’s type system and static method handling, or is there some workaround I'm missing?

    This is how C++, Java, and C# work (where static methods are resolved *statically* - at compile time, not run time), so I'd say it's by design. Whether it's considered a "limitation" might be in the eye of the beholder.

    (I realize that what you're asking for is possible in js and python tho.)

    • Simply calling doSomething() inside A.doMore() just calls A.doSomething().

    • Using self.doSomething() doesn't solve it either.

    For me, if I write code similar to your example and call doSomething() or self.doSomething() in A.doMore(), I get a symbol not found error at run time (although the type checker is happy, even at the strictest level).

    Not sure why we're seeing different behaviour, unless your actual implementation of class C also has a function doSomething(), and it's actually C.doSomething() which is being called.

    I would consider the type checker thing a bug tho.

  • As for the implementation details/reasons behind this, if create an instance of C and call C.doItall(), and you print out the value of `self` (or `this`) in C.doItAll() and A.doMore(), you'll find that it has the same value in both cases (meaning it points to the instance of C in both cases).

    If you call A.doMore() from outside of a class instance (like a global function), you will find that trying to print the value of self causes a crash, with a "symbol not found" error.

    In other words, `self` only has meaning in the context of a non-static class method, so there's no way for A.doMore() to know that it was invoked as B.doMore().

    As for workarounds, you could change your static functions so that they take a "classDef" as an arg (this goes back to the other discussion about whether it's possible to do this "correctly" in the type system, regardless of the fact the Method.initialize() does it.

    Whenever you call a static function, you would pass the class itself as the first arg:

    import Toybox.Lang;
    import Toybox.System;
    
    class A {
        public static function doSomething() as Void {
            System.println("A");
        }
        public static function doMore(staticClass as A) as Void {
            // yes it's bad to type "staticClass" as A, because if
            // A or B is passed in, as intended, the type checker would
            // allow `staticClass.someInstanceMethodOrVariable` to be accessed,
            // which would cause a crash at runtime
            //
            // don't blame me, blame the type checker / language design
            staticClass.doSomething();
        }
        private static function getClass() as A {
            return A;
        }
    }
    
    class B extends A {
        function initialize() {
            A.initialize();
        }
        public static function doSomething() as Void {
            System.println("B");
        }
    }
    
    class C {
        public function doItAll() as Void {
            B.doMore(B);
        }
    }
    
    function test() as Void {
        new C().doItAll(); // prints "B"
    }

    Specifically, the problem is the type system seemingly doesn't really distinguish between the name of a class (or "classdef") and an instance of said class, for the purpose of variable / arg types.

  • Not sure why we're seeing different behaviour, unless your actual implementation of class C also has a function doSomething(), and it's actually C.doSomething() which is being called.
    As for the implementation details/reasons behind this, if create an instance of C and call C.doItall(), and you print out the value of `self` (or `this`) in C.doItAll() and A.doMore(), you'll find that it has the same value in both cases (meaning it points to the instance of C in both cases).

    To illustrate what I'm talking about:

    import Toybox.Lang;
    import Toybox.System;
    
    class A {
        public static function doSomething() as Void {
            System.println("A");
        }
        public static function doMore() as Void {
            // when called from instance of C, calls C.doSomething().
            // when called from global scope or some instance which doesn't
            // have doSomething, crashes with "symbol not found error"
            doSomething(); // [*] line 12
        }
        private static function getClass() as A {
            return A;
        }
    }
    
    class B extends A {
        function initialize() {
            A.initialize();
        }
        public static function doSomething() as Void {
            System.println("B");
        }
    }
    
    class C {
        public function doItAll() as Void {
            B.doMore();
        }
        public function doSomething() as Void {
            System.println("C");
        }
    }
    
    function test() as Void {
        new C().doItAll(); // prints "C" :/
        B.doMore(); // crash at line 12 [*]: "symbol not found error"
    }

    I don't claim that it's good design that a static function in A can somehow call a function in an instance of C, when an instance of C called A's static function in the first place - even the typechecker agrees with me, as it thinks A.doSomething() is actually being called.

  • Thanks for diving into this issue again!

    I modified by code for the to take the classdef, but when I call the static method from the classdef (line 15 in your example), I get an "Error: Not Enough Arguments Error". I am running it with SDK 8.1.1.

  • In my admittedly not-so-great example, only doMore() takes the classdef as an arg, not doSomething(). The example as written should compile and run tho.

    If you changed *all* static functions to take the classdef (which would be more consistent), then you would call doSomething() as follows:

    staticClass.doSomething(staticClass);

    Not sure if this workaround is a good idea tho.

  • Why do you need methods that are both static and abstract? As mentioned, this is not possible even in java. If you already have an object (i.e you have at least 1 non static method in the class) then maybe it's not that big of an overhead to turn the static method into non-static. Wouldn't that solve all the problems?

  • In my admittedly not-so-great example, only doMore() takes the classdef as an arg, not doSomething(). The example as written should compile and run tho.

    OK, I need to do some more testing to understand why this isn't working for me.

    Why do you need methods that are both static and abstract? As mentioned, this is not possible even in java. If you already have an object (i.e you have at least 1 non static method in the class) then maybe it's not that big of an overhead to turn the static method into non-static. Wouldn't that solve all the problems?

    I have static logic in an abstract class that is shared across all derived classes. However, it also needs to incorporate static behavior that differs depending on which derived class is calling the abstract class's static logic.

  • behavior that differs depending on which derived class is calling

    If it depends on the caller then you'll anyway need to send the caller. Or just use the somewhat strange behaviour of Monkey C that if I understood from this thread and the 2 bug reports kind of does exactly that.

    But I'll ask this again:

    1. what is the reason to use static methods?

    2. am I right in my assumption that if you decided to not use static methods then you wouldn't have these problems? Or maybe I am still missing something.

  • If it depends on the caller then you'll anyway need to send the caller. Or just use the somewhat strange behaviour of Monkey C that if I understood from this thread and the 2 bug reports kind of does exactly that.

    No, the bug/quirk in Monkey C is that if an instance method of a class X calls a static method of class Y (which is unrelated to X), then the "self" reference will point to X, meaning that any unqualified function calls in Y's static method can actually resolve to an instance method in X. This is not what the-ninth wants (or what any reasonable dev could possibly want).

    What the-ninth wants is:

    - given: class B that extends A

    - given: that a static method on B calls a static method on A, and A's static method calls another static method (call it M) which is defined on both A and B (in this case, it's called without using a qualifier, only using the name of the function - i.e. "M()")

    - then: M is resolved on B, not A

    This won't work for several reasons:

    1) In general, M can't be called without a qualifier. e.g. "M()" won't work, only "A.M()" or "B.M()". So a plain call to "M()" wouldn't be resolved to *either* A.M() or B.M().

    2) In the quirky case that "M()" could "work", it wouldn't be calling a *static* method on A or B, it would be calling an *instance* method on some other unrelated class

    The reason for 1) is that calling "M()" without a qualifier would only work if the self reference is set to either A or B. Even though the type checker believes that calling "M()" from another static method in A would cause it to be resolved to "A.M()", this not the case at runtime. (Surely there's room for improvement here for the compiler to resolve such a call at compile-time to "A.M()", but it still wouldn't achieve what the-ninth wants)

    The reason for 2) is the quirk which incorrectly retains the self pointer when an instance method of an class calls a static method of another unrelated class.

    EDIT: the one edge case that might fulfill the desired use case:

    - an *instance method* of B calls a static method in A, which calls a unqualified function which exists as a static method on both A and B

    While this would work, it requires an instance of B which defeats the whole purpose of having static methods in the first place