BosqueLanguage / BosqueCore

Other
140 stars 4 forks source link

Prefer named binders #73

Open kwangure opened 7 months ago

kwangure commented 7 months ago

Would you consider making binders named? It feels more explicit, intuitive and can allow narrowing of multiple values.

function flowit(x: Nat?, y: Nat?): Nat {
    //ITest for none as special
    if none (x) {
        return 0n;
    }
    else {
        if none (y) {
            return 0n;
        } 
        else {
            //ITest allows binder for $x and $y value of x and y (with type inference)
            return $x + $y;
        }
    }
}
BosqueLanguage commented 7 months ago

Hi, yes I have been thinking about this as well.

I like the idea of having a named binder, and it works well when the tests are on variables like in your example. However, I am not sure what to do in the case of other expressions:

if none (x.foo().g) {
    //what is the binder name here
}

One option would be to say that for non-variable expressions the binder name is just $ as it is now. Another option would be to add some syntax for naming the binder explicitly -- maybe:

if !none |calledg|(x.foo().g) {
    return $calledg;
}

I like that this is a more uniform way to do the binding names but I am not sure if the added complexity is worth it.

Interested to hear what ideas on this.

kwangure commented 7 months ago

Hmm. I hadn't thought about how to deal with expressions.

I did various prompts guiding ChatGPT4 to hallucinate its way to a syntax for expressions given the above suggested syntax for simple variables. It consistently either autocompleted with the assignment or a postfix before the curly brace as below:

// Alternative A
if $foo = none (x) {
    return $foo;
}

if $bar = none (x.foo().g) {
    return $bar;
}

// Alternative B
if none (x) $foo {
    return $foo;
}

if none (x.foo().g) $bar {
    return $bar;
}

For completeness, I'll also add a few suggestions from generated outputs that were more murky or I did not like:

if  none $foo (x.foo().g) {
    return $foo;
}

if none (x.foo().g) {
    return $x.foo().g;
}

if none (x.foo().g) {
    return x.foo().$g;
}

let temp = x.foo().g; // i.e. require an assignment variable
if none (temp) {
    return $x.foo().g;
}

I was initially hesitant about a naming syntax, but it has grown on me pretty quickly. I find that it's quite inline with Bosque's current goals and patterns, and it gives a nice visual cue to both human and LLM readers as to where the narrowed value is actually coming from (particularly with the assignment syntax).

BosqueLanguage commented 6 months ago

As a quick update on this. The current priority is to get the BSQON notation and parser code in a beta shippable state. Then the roadmap is to pivot to the core Bosque runtime. I'll include this feature as a revision in that work.

The proposed design will to:

As opposed to normal values we will allow binders to shadow (since this is a general requirement when we support them in other areas) so in following is valid (if weird) code where the definition of $x in the inner non test shadows not reassigns the definition of $x in the outer one.

function f(x: Int?, y: Int?, b: Bool): Int {
  if !none (x) {
    if(b) {
      return $x;
    }

    if $x = !none (y) {
      return $x;
    }
  }
}

f(2, 3, true)  //2
f(2, 3, false) //3
BosqueLanguage commented 3 weeks ago

Added support in Parser and Type Checker in PR #86. Needs more testing and such.

Modified the grammar a bit to put assignment inside parens: if ($x = y) ... and shift ITest to after expression + explicit choice to bind:

if(x)!none      %%ITest, no bind
if(x)@!none  %%ITest, bind $x
if($y = x)@!none %%ITest bind $y to x
if($y = x)!none    %%Error since no set of bind