mavoweb / mavo

Create web applications entirely by writing HTML and CSS!
https://mavo.io
MIT License
2.83k stars 178 forks source link

Function composition operator #258

Open LeaVerou opened 6 years ago

LeaVerou commented 6 years ago

Currently, expressions can require a lot of nested parentheses, which is something even seasoned programmers often err on 1. One way to solve it would be to try to perform fixup and add forgotten parens or remove extra parens if expression evaluation fails (is this a good idea?).

Another way would be to design the syntax in such a way that unnecessary parens are eliminated. A lot of the time, the output of one function is the input of another, with no extra arguments. We could introduce a function composition operator that eliminates parentheses from such function calls, like to the one in Maths where g∘f(x) = g(f(x)). For example, with such an operator, an expression like readable(to(filename(image), '.')) would become readable ∘ to(filename(image), '.').

Obviously, we can’t use ∘ for the actual operator, since that would be a pain to type. We could use the actual word of, which is how such function calls are often read anyway. The example would then read readable of to(filename(image), '.'), which is not terribly readable (oh the irony), but is not too bad. Most importantly, it seems to really improve things in terms of parens: even by removing 1 pair of nested parens, it became much easier to balance them. Alternatively, it could be a non-word operator, e.g. <<. The example would then become readable << to(filename(image), '.').

Thoughts?

karger commented 6 years ago

I'm unsure how often this chain of single-function arguments occurs (especially in the data management context we are exploring) and don't know how we can find out (except to wait and see what functions get used).

Some other options include:

Note that with both of these the order of function application is left to right instead of the right to left; I think that might be more natural for non-programmers, to start with the "input" and then have the functions applied in the order you read them instead of having to remember to invert everything? On the other hand if they've done a lot of math they'll expect the opposite; a conundrum.

But I do wonder whether this kind of chaining of functions is good for non-programmers, or if they might be better off defining a sequence of intermediate variables/properties a=f(x), b=g(a), c=h(b) etc. This has the advantage, in our reactive model, of creating variables whose value can be inspected when they are trying to figure out what went wrong.

A lot of this depends on really understanding our target audience, and since there's actually more than one, it's a challenge!

On 8/5/2017 11:26 PM, Lea Verou wrote:

Currently, expressions can require a lot of nested parentheses, which is something even seasoned programmers often make err on 1 https://twitter.com/LeaVerou/status/892308056092037120. One way to solve it would be to try to perform fixup and add forgotten parens or remove extra parens if expression evaluation fails (is this a good idea?).

Another way would be to design the syntax in such a way that unnecessary parens are eliminated. A lot of the time, the output of one function is the input of another, with no extra arguments. We could introduce a function composition operator that eliminates parentheses from such function calls, akin to the one in Maths’ where g∘f(x) = g(f(x)). For example, with such an operator, an expression like |readable(to(filename(image), '.'))| would become |readable ∘ to(filename(image), '.')|. Obviously, we can’t use ∘ for the actual operator, since that would be a pain to type. We could use the actual word |of|, which is how such function calls are often read anyway. The example would then read |readable of to(filename(image), '.')|, which is not terribly readable (oh the irony), but is not too bad. Most importantly, it seems to really improve things in terms of parens: even by removing 1 pair of nested parens, it became much easier to balance them.

Thoughts?

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/mavoweb/mavo/issues/258, or mute the thread https://github.com/notifications/unsubscribe-auth/ABFpXgSPFttAFgo8LB88UYVYpqLa71_oks5sVTJWgaJpZM4OuoAI.

LeaVerou commented 6 years ago

Good ideas re: inverse order and pipe! Yet another reason for this operator: that order is much more natural. A bit concerned that pipe might be confused with OR by those that have used JS.

Now that the order would be reversed, what about then as the operator? That would read quite naturally! I quite like words as operators.

The operator could also work by currying, where x | a | b(5) would be equivalent to b(5, a(x)) or b(a(x), 5). That gives it more power without complicating simple usage.

On the other hand if they've done a lot of math they'll expect the opposite; a conundrum.

No conundrum, I think we can safely say that the population that has done a lot of maths is comparatively fairly small :P

But I do wonder whether this kind of chaining of functions is good for non-programmers, or if they might be better off defining a sequence of intermediate variables/properties a=f(x), b=g(a), c=h(b) etc. This has the advantage, in our reactive model, of creating variables whose value can be inspected when they are trying to figure out what went wrong.

Intermediate variables are already possible, by defining properties whose value is an expression. Few people used them in the user study.

LeaVerou commented 6 years ago

One issue with the pipe syntax is that it's not directional, nothing indicates flow, whereas something like >> would (or then, linguistically instead of schematically).

karger commented 6 years ago

Sorry; paper deadline. I'll try to keep up the discussions.

I like the then syntax; also makes it less likely users will consider it counterintuitively contradicting the normal a(b(c function application order. Note that the first function in the then chain can be allowed to take multiple arguments; (f then g then h)(x,y).

Also note potential ambiguity if some exprs return functions; e.g. f then g (x) (y) could mean ((f then g)(x))(y) or (f then g(x))(y)

As for intermediate values, I know they are possible; my point was that maybe they should be encouraged (preferentially over complex expressions), not least because they make debugging easier.

betaveros commented 5 years ago

As a little bit of prior art: F# and Elm (and maybe others) offer << and >> for function composition and <| and |> for function application.

LeaVerou commented 5 years ago

Some more prior art: VueJS and AngularJS support "filters" for formatting data, which are basically functions, and they are combined with a pipe (|). They are used without parentheses if no arguments, and with parentheses if arguments are desired.

See https://vuejs.org/v2/guide/filters.html