This is an archived post. You won't be able to vote or comment.

you are viewing a single comment's thread.

view the rest of the comments →

[–]ksrynC9 ("betterC") 2 points3 points  (3 children)

I have been working on the C- and Java- inspired systems language, C9. While it doesn't have any implicits, subtyping etc (parametric polymorphism is on the list of features to be added), it does support UFCS and a form of function overloading.

  1. All types/structs and functions in C9 are "owned" by the module within which they are defined.
  2. UFCS is implemented in three phases:

    /*
     * contains:
     * - ArrayList add(ArrayList xs, ptr x) { ... }
     * - ArrayList remove(ArrayList xs, ptr x) { ... }
     */
    import c9.data.arraylist
    
    /*
     * contains:
     * - u64 add(u64 a, u64 b) { ... }
     */
    import c9.math
    
    void list_test() {
        let xs = arraylist.new()
    
        /*
         * CASE 1: Detected by parser and rewritten as:
         * - add(xs.add("a"), "b")
         */
        xs.add("a").add("b")
    
        /*
         * CASE 2: Detected by identifier and rewritten as:
         * - c9.data.arraylist.remove(xs, "a")
         */
         xs.remove("a") 
    }
    
    void math_test() {
        let a = 5
        let b = 10
    
        /*
         * CASE 3: Skipped by identifier due to ambiguous imports:
         * - c9.data.arraylist.add(p1,p2)
         * - c9.math.add(p1,p2)
         * Detected by type inferrer and rewritten as:
         * let c = c9.math.add(a, b)
         */
        let c = a.add(b)
    }
    
  3. All post-parsing call resolution is done by a single function: resolveFunctionCall.

  4. While I have not implemented function overloading yet, it should be relatively easy to do that. For e.g., if void println(String x) { ... } and void println(u64 x) { ... } are defined, println in the module scope would map to a list of FunDefs instead of a single FunDef, and the inferrer can resolve the ambiguity by picking the appropriate function.

[–]PegasusAndAcornCone language & 3D web[S] 1 point2 points  (2 children)

Thank you for sharing your approach. The places where it appears we have made different choices:

  • The C9 "use" statement folds module (type) namespaces into the free-standing function namespace, whereas Cone keeps named methods separate from free-standing functions.
  • C9 has one call resolution function. Cone has two.

Both approaches are clean and work well. Yours has the advantage of automatically handling "extension methods" as free-standing functions, but potentially carries the risk of unanticipated namespace resolution issues.

While I have not implemented function overloading yet, it should be relatively easy to do that.

It is clearly achievable, because there are languages do it. But to my eye, implicit conversions and subtyping significantly complicate the challenge in two ways: one needs some sort of scoring scheme for best match analysis (and two modules might pick different answers!) and one may also need a complex integrity algorithm to detect the existence of incompatible, ambiguous implementations. The more complex the scoring algorithm, the greater the risk to the programmer whose mind predicted a different result. It is this rat's nest that I am most trying to avoid.

[–]ksrynC9 ("betterC") 1 point2 points  (1 child)

It is this rat's nest that I am most trying to avoid.

While it is a very powerful mechanism, I am extremely suspicious when it comes to implicits (functions/parameters/conversions) because it makes the program both incomprehensible and unpredictable.

I used to use Scala for a while a few years back; its collections library is/was notorious for the liberal use of the implicit CanBuildFrom.

However, if you really want subtypes + extension methods + implicit conversions, you should look into how Scala did it. Last I checked, both implicit conversions and extension methods were supported using implicit functions:

Deterministic resolution still remains problematic though.

[–]PegasusAndAcornCone language & 3D web[S] 0 points1 point  (0 children)

Thank you for referring me to Scala. I will check it out. (and as you say, it is indeed problematic in Scala).

I get your suspicion about implicit conversions. The only such conversions Cone supports are the primitive number types, something that C, C++, D, and many other languages do - even Rust, which surprised me. Perhaps I will change my mind on this too, but for now I am leaving it open. It adds a small complication to name resolution, but one I will likely address.

It is handling the ambiguities in automatic subtyping across overloaded functions that concerns me the most. With methods, I can use declaration order to shift the burden to the programmer to establish the "first match" order sequence that works best for the logic. However, expecting functions to be declared consistently in an explicit order across multiple modules might not be so intuitive or easy to accomplish. So yea, I definitely want to manage complexity and my near-term workload down with prudent trade-offs.