Fixing the Piping/Chaining Issue

Indeed, [1, 2, 3, 4] |> filter(_>2, _) isn’t bad. To me it’s still a bit hard to read, but that could be a matter of practice and familiarity (considering how strongly people are championing for the @chain macro!).

I don’t know if I will ever like |> because it’s still awkward to type, but maybe that can change if I have more motivation to use it. Or maybe -- can be alternate syntax for the pipe operator, so we’d say [1, 2, 3, 4]--filter(_>2, _); this both looks cleaner, and is faster and easier to type. -- is currently not a valid operator so we wouldn’t be stealing it from any packages.

Attempting to describe the behavior of /> and \> in operator(ish) terms:

It seems like it should have asymmetric operator precedence on left- versus right-sides, depending on what type of operator it’s competing against for arguments.

Specifically, it should have higher precedence than the function application $, except when a function application is to the immediate left of it, in which case it should have lower or equal precedence (or whatever, using the word “precedence” doesn’t much matter; just let function application win in the tug-of-war for an argument).

For example, if function application has precedence of 20, then /> and \> could have precedence of 21—except when a function application is to the immediate left of it, in which case its precedence could be 0.

Yeah, this isn’t the sort of thing you can express in a standard operator precedence table. It’s not “clean” as such. And in comparison, underscore placeholder syntax is beginning to sound like it could be comparatively “cleaner.”

This is too bad, because if they had gone forward with it the simple version of consuming a single function call we might have better autocomplete by now!

Autocomplete with Placeholder Syntax

When I think about it, the knowledge that you have the ability to chain seems like sufficient information for a decent autocomplete. We all know that you’re most likely to chain the object into the first argument anyway. And if not, probably the last. So do we really need operators which force you to do what you were already going to do? The autocomplete shouldn’t act dumb, it can look for methods which place the object in front or back.

Consider this scenario:

You type [1, 2, 3, 4] |>. Autocomplete already knows it should be looking for methods that take a Vector{Int64} as their first argument or last argument. The methods that specialize most tightly to this type, of course, should float to the top of the list.

So things like split and filter appear. You want filter at the moment, so you type fi<TAB> and it fills out filter(, _), reflecting the number of arguments this method of filter has—the method which specializes on a::AbstractArray as its second argument—with the cursor before the comma to enter the about-to-be-fixed argument. You type _>2 and now have filter(_>2, _) with the cursor still before the comma. You press <TAB> and the cursor is brought to the right of the closing parenthesis. You continue the chain, life is good, and you proceed to make babies live happily ever after.

It could be possible to have functions which take the object as a first argument appear below the cursor, and functions which take it as a last argument appear above. Or some other variation which allows tabbing through which argument it’s going to fill into.

A more sophisticated approach could be to build a script which scans all the GitHub repos and builds a statistical model of how frequently the various methods are used on objects of various types, and then use this data to statistically infer which methods you are likely to invoke. Maybe if you’ve included certain packages, it can infer based on the frequencies with which others who’ve loaded similar packages use certain methods. One could also have a model which factors in your unique style, as you might use some functions more than other people do; start with the GitHub repos as a prior, and conjugate it with personal use data observations to form a posterior (Dirichlet distribution maybe?).

Considering that the point of writing code is to do what hasn’t been done, you wouldn’t want the inference model to overfit to the data, so you’d want to play around with the optimal autocomplete strategy to find the sweet spot between auto-recommending the common versus the uncommon, but that’s a whole new conversation.

The point is, a good autocomplete really just needs two pieces of information: 1) what object type you’re hoping to call a function on, and 2) the fact that you can chain effectively. The rest of it is a matter of inference, which can be heuristic, statistical, or whatnot. Maybe even have competing autocomplete engines, who knows. Getting a respectable chaining syntax into the language is essentially a prerequisite to a respectable autocomplete, and underscore placeholder syntax could fit the bill.

Typed Partially Applied Functors

Recall that part of the motivation in the OP was to generalize Base.Fix1 and Base.Fix2. I think my proposal for FixFirst and FixLast does the trick.

But part of the proposal was for my syntax sugar to allow creation of these objects. I wonder if underscore syntax could do the same?

It seems obvious that this could create a FixFirst(f, x) object:

f(x, _...; _...)

and this a FixLast(f, x) object:

f(_..., x; _...)

but I wonder if these should:

f(x, _) # creates FixFirst(f, x) ?
f(_, x) # creates FixLast(f, x) ?

We run into the issue that while my fix operators fix only one argument (and thus are quite natural for FixFirst and FixLast), underscore placeholder syntax wants to fix every argument except for…, which makes it harder to make typed functors to describe its operation.

I guess the question arises… why do we need typed functors anyway? Who cares that Base.Fix1 and Base.Fix2 are types, instead of just anonymous functions? Some people seem to care, but should they really?

Because if they don’t, placeholder underscore syntax should satisfy them as-is.

And if they do… maybe some sort of Base.Fix{F, NTuple{N,Union{Int, Symbol}}, Tuple{N,DataType}} where {F,N} type could be constructed by underscore placeholder syntax to describe the partially applied function and the fixed argument indices and types…

1 Like