TypL icon indicating copy to clipboard operation
TypL copied to clipboard

Why were tag functions chosen for the API?

Open kee-oth opened this issue 2 years ago • 5 comments

TypL looks extremely interesting! Thank you for putting so much work into it already!

I have kind of a surface level question: why were tag functions chosen for the API? I'm sure there are good reasons but from the outside-in perspective, it seems that normal function calls would be a little more ergonomic since there's a good bit less syntax:

Using tag functions:

function isEven(v = number) {
   return bool`${ v % 2 == 0 }`;
}

var x = number`42`;
var kind = bool`${ isEven(x) }`;

vs

Using "regular" functions:

function isEven(v = number) {
   return bool( v % 2 == 0);
}

var x = number(42);
var kind = bool( isEven(x) );

Thanks!

kee-oth avatar Mar 13 '22 19:03 kee-oth

TypL uses custom type syntax, which I did not want be invalid JS syntax. As such, it has to embed this custom type syntax inside `back-tick delimited template literals`. The purpose of the tag function attached to the template literal, as opposed to just functions with the template literal passed in, is because it saves two extra characters (the surrounding ( .. ) parens).

As an example of the custom syntax:

function doSomething(vals = array`< int[], str[] >`) {
   // ..
}

The < int[], str[] > type syntax is a tuple (2-element array) where the first element is an sub-array of integers, and the second element is sub-array of strings. That custom syntax is buried in a template string so as to not invalidate the overall JS syntax itself. That string is tagged with the array tag function, because that function knows to parse the type syntax in the template string, creating a validating function such that, at both compile-time and run-time, enforces that the vals argument must to match the shape of < int[], str[] >.

getify avatar Mar 13 '22 22:03 getify

Ah, ok. There's a little more to this than I gleaned from the documentation.

Your explanation makes sense. For primitive data types though, is it possible to use the the "regular" function syntax? At least as an alternative API?

So both of these would be valid:

var kind = bool`${ isEven(x) }`;

and

var kind = bool( isEven(x) );

Maybe TypL can handle transforming it under the hood? A naive implementation:

function bool (value) {
    var valueAsTypeSyntax = typeof value === 'string' ? value : `${value}`;
}

kee-oth avatar Mar 13 '22 22:03 kee-oth

is it possible to use the the "regular" function syntax?

In theory, I don't see why TypL absolutely couldn't support a simpler function-only syntax for primitives. And I can see why, in certain cases, it might look nicer in usage.

However, I'm not convinced that's a good idea. Perhaps it is, I'm not rejecting it outright, but my strong inclination at first is skepticism.

For posterity, let me list a few initial thoughts/concerns about the idea:

  1. I think it's important that TypL be pretty obvious and recognizable, from a readability perspective. I want it to look like something that stands out as a type annotation, rather than something that blends in as if it's just app code. I worry that bool( .. ) will just blend in, whereas bool`${ .. }` (though it's clearly a bit more unwieldy syntax wise) pretty intentionally stands out.

  2. By having two different approaches, it becomes less clear to those who are learning TypL which approach they should/must use in which circumstances. If for example you're using the function-oriented syntax for an annotation, and then realize you need to handle something more than a primitive, you then have to recognize the need to graduate to the other `-delimited syntax. I worry that overall, having this extra complexity will bring more mental overhead than the syntax shortcut provides, thus being a net-negative.

  3. Because of how tag functions are automatically called when tagging a template literal, your naive solution above won't actually work. A normal bool(..) function call and a bool`tag function call` have different signatures. It might be possible to overload the bool(..) util so it can handle being called in both ways -- I've attempted things like that before, and indeed there is some limited version of that in a few places in TypL already -- but bottom line: that's really tricky to do, and may not be worth the effort and potential corner-case bugs.

    As such, I might have to provide different functions to be used for each call style. That would mean you might have to do bool`whatever` and bool2(whatever). That would suck, obviously. And unfortunately I don't think transpile-renaming would be sufficient to avoid that, since a key goal of TypL is to work correctly even if you skip the build-step and only hit run-time behaviors.

    IOW, this could get really tricky to implement... I'm not sure quite how we'd do it. It might be possible, but it also might just be too complex to be worth it.

So... those thoughts said, it's worth keeping the idea around to consider for the future. Not promising anything, but... at least we can see what questions would need to be answered to lift this to a viable feature.

getify avatar Mar 13 '22 22:03 getify

Thanks for the detailed response!

I see what you mean. Thought No.2 was on my mind, I'm generally more in favor of One Way to Do It myself so I feel that any additional syntax does need a strong reason for existing. That said, I do feel that bool`${ .. }` is a bit clunky. Not unworkable but I'd be a tiny bit worried about developer adoption if they view it as clunky.

Maybe an option that solves No.3 and maybe No.1:

var kind = bool`${ isEven(x) }`;

// is functionally the same as

var kind = bool.of( isEven(x) );

It's the same number of characters but feels a bit more conventional/less mysterious which I hope would be more attractive to developers and increase adoption. This doesn't really help No.2 aside from maybe being harder to get confused between these two options (than my original idea since there's larger difference between these two syntax options).

kee-oth avatar Mar 13 '22 23:03 kee-oth

bool.of(..)

Heh, that's a reasonable option to consider for addressing the dual naming issue. I'd expect a lot of people to accidentally to do bool(..) accidentally when they should have done bool.of(..)... so I think the TypL compiler would have to identify the mistake and report an error.

Unfortunately, that means run-time usage could still fail. That's a downside for sure. But in balance it may not be as bad as the always-clunky-syntax.

getify avatar Mar 14 '22 00:03 getify