eclipse-archived/ceylon

Syntax for function composition

Closed this issue · 34 comments

[@gavinking] I would like an operator for composing functions, but I'm not sure what actual symbol to use. I suppose => and <= could work.

function times2plus1(Integer i) = 2.times => 1.plus;

or

function times2plus1(Integer i) = 1.plus <= 2.times;

Another possibility would be @ which sorta kinda looks a little bit like the traditional symbol in mathematics if you squint. But it doesn't make the direction of composition clear, and is less flexible than => and <= because you can't choose the direction of composition.

Any suggestions?

[Migrated from ceylon/ceylon-spec#123]

[@quintesse] Or the other way around so it retains the same order as if you would
writing it without composition?

function times2plus1(Integer i) = 1.plus(2.times(i))

function times2plus1(Integer i) = 1.plus -> 2.times;

Update: what I wrote above isn't correct of course.

NB shouldn't this really be:

function times2plus1 = 1.plus -> 2.times;

where the parameters are determined from the (composited) function's
parameters?

[@gavinking] Sorry, I screwed up the direction of my arrow. Fixed now.

[@quintesse] Well I was referring more to the fact that you're supplying a parameter list while this should depend on the functions themselves, right? I mean where did i go in your example?

[@gavinking]

Well I was referring more to the fact that you're supplying a parameter list

The name of a parameter (along with it's default value and whether is is a sequenced param) is important in Ceylon. Sure, you can always declare the above as:

value times2plus1 = 2.times => 1.plus;

But then you lose the ability to call it using a named argument invocation, along with the ability to specify default values, etc. You always have the choice in Ceylon between:

Callable<Integer,Integer> times2plus1 = 2.times => 1.plus;

and

Integer times2plus1(Integer i) = 2.times => 1.plus;

They mean approximately the same thing, but the second one is more readable and has more information in it.

[@gavinking] Even if we never add an operator for this, we still need a function to do it. FTR, here it is:

X compose<X,Y,Z>(X(Y) x, Y(Z) y)(Z z) => x(y(z));

There's a slight variation when the first function produces the argument list of the second function as a tuple:

X multicompose<X,Y,Z>(Callable<X,Y> x, Y(Z) y)(Z z)
         given Y satisfies Sequential<Void>
                 => x.invoke(y(z));

So what would be a good operator for composition? Well, now that => and @ are taken, I suppose (*) could work...

[@gavinking] Hrm, actually (*) doesn't look good at all. A diamond does look reasonable:

(f<>g)(x)

Or, alternatively, << works nicely:

(f<<g)(x)

[@chochos] But << is bitshift, already used... it probably wouldn't be confusing, as long as it can be parsed without any problems...

[@gavinking] A different approach would be to have a unary operator—probably a prefix * or ^, or perhaps a postfix !—that acts as an abbreviation for compose(f) given:

X compose<X,Y,Z>(X(Y) x)(Y(Z) y)(Z z) => x(y(z));

Then you could write:

^f(g)(x)

And:

^f(^g(h)))(x)

[@gavinking]

A different approach

Scratch that, the ^ would not work, because we can't infer the needed type parameters. Back to defining it as a binary operation...

[@gavinking] Here's a definition of compose() that handles functions with multiple parameters:

X comp<X,Y,Z>(X(Y) x, Y(Z) y)(Z z) => x(y(z));

shared Callable<X,Z> compose<X,Y,Z>(X(Y) x, Callable<Y,Z> y) 
        given Z satisfies Sequential<Void>
               => unspread(comp(x, y.invoke));

But this definition depends on the following function, which can't currently be defined outside of the language module:

Callable<X,Z> unspread<X,Z>(X(Z) x)
        given Z satisfies Sequential<Void> { 
    object result satisfies Callable<X,Z> {    
        shared actual X invoke(Z args) {
            return x(args);
        }
    }
    return result;
}

unspread(f) does the exact opposite of f.invoke.

[@RossTate] I believe |> is often used for piping.

[@chochos] IIRC, F# has something like that actually... You can pipe several functions like that, a|>b|>c|>d but I don't remember if the args go in a(x) or d(x)...

[@gavinking] Hrm. |> looks fine in the font github uses, but it looks terrible in the font Eclipse uses on Mac. :> looks better (in Eclipse at least, though not so good here) but for some reason it just doesn't appeal to me at all.

[@gavinking]

But this definition depends on the following function, which can't currently be defined outside of the language module:

Hrm, with the new stuff based on tuples instead of sequenced type parameters, the definition of curry() also depends on unspread() defined above.

Callable<Return,Rest> curry<Return,Element,First,Rest>
            (Callable<Return,Tuple<Element,First,Rest>> f)
            (First first)
        given Rest satisfies Sequential<Void> 
        given First satisfies Element 
        given Rest satisfies Sequential<Element> 
                => unspread((Rest args) f.call(Tuple(first, args)));

Of course, if I only cared about the case of two parameters, I would have been able to just write:

X curry<X,Y,Z>(X(Y,Z) f)(Y y)(Z z) => f(y, z);

Anyway, it's interesting how important unspread() is do be able to represent these basic operations.

[@gavinking] Unless someone else has some brilliant ideas, I don't think we need a special syntax for this in Ceylon 1.0. I think we can live with compose(g,f) and partial(f).

[@chochos] You can't be serious - your argument against an operator is the way it looks with the default font in Eclipse on Mac?

Yes please let's just use regular function names for now.

[@sirinath] If you want to pipe forward or compose multiple function multiple nested composes can become hariry:

E.g. (From F#)

data
|> ParStream.ofArray
|> ParStream.filter (fun x -> x % 2L = 0L)
|> ParStream.map (fun x -> x + 1L)
|> ParStream.sum

but alternatively compositionBuilder as a composition builder which can return a object which in turn can be used to this composes the results again forwardCompositionBuilder(f)(g)(h)(i).fun. You can choose a shorter name. Also pipeForward(value)(f)(g)(h)(i).result. Again you can choose some intuitive name.

As a variation on @gavinking’s initial => and <=, there’s >>> and <<< in Haskell. They may feel a bit strange and ASCII-arty, but OTOH they don’t conflate with traditional bit shift lexemes and with use of the fat arrow in definitions here.

@arseniiv actually, stuff like >> and >>> requires really major hacks to be able to lex correctly, since it's impossible to distinguish between >> and two > using a regular expression. Probably not a problem in Haskell because AFAIR < and > are not used for grouping.

@gavinking Yes, they aren’t. Oh, damn, I forgot about this problem, sorry. I heard about it regarding some C++ history a long time ago and then forgot.

If #6615 ever comes live, I would prefer using the same |> as other languages (F#, maybe JS...).
But then, in order to avoid ambiguity, a different token should be used for "prefix function parameter application" (i.e. ' |()' ):

        |("hello")                               \\ piped parameter application
        |> String.size                           \\  \
        |> (=> Integer.format(_, 16))            \\  |> function composition
        |> print;                                \\  /

@someth2say well, curiously, as long as there are two operators:

  • pipeline |>
  • compose ||>

And if ||> has a higher precedence than |>, then

a |> f |> g |> h

would mean effectively the same thing as

a |> f ||> g ||> h

which is interesting, I think.

So, curiously, you kinda get your syntax for free.

Question: which looks better:

string |> String.uppercased ||> trim ||> print

Or:

string |> String.uppercased |>> trim |>> print

Since it seems to me that, with the addition of the pipeline operator, we should use something visually similar for function composition.

A third option:

string |> String.uppercased .> trim .> print

@gavinking

So, curiously, you kinda get your syntax for free.

Yes, that's what I was trying to explain 😄 I would love to actually hide this difference from developers, using the same tokens for both cases. But also agree the ambiguity may drive to issues.

Question: which looks better:

I like the .>, and add my own proposal:

string |> String.uppercased +> trim +> print

as I see the composition like some kind of function "addiction" (without commutativity, indeed).

Question: Will we eventually consider "esoteric" compositions, like optional or spreading?
If so, even we are not targeting those right now, maybe it is worth having in mind a syntax allowing to add those (maybe +?> and +*>, but really make my ascii-art radar cry...)

Wait wait wait, isn’t it “obvious” that the correct syntax for composition, given that a pipe is written |> must clearly be:

>|>

Since composition has a function on both ends, whereas a pipe has a function only as the RHS.

And then reversed composition, if we need it, would be:

<|<

Will we eventually consider "esoteric" compositions, like optional or spreading

don't know about the best ascii syntax, but I'd like to have this feature.

@gavinking

Wait wait wait, isn’t it “obvious” that the correct syntax for composition, given that a pipe is written |> must clearly be:

I agree on the reason for using >|> for composition, but.... isn't it a bit "fishy"?

@davidfestal

If we could create Callables, ", we could leave "esoteric" compositions out of the compact syntax, and leave those to the verbose way, as in "ceylonChain":

value fun = ifExists(String.uppercased).to(trim).to(print); 
fun(string);

Or even without callables (but a bit less attractive):

value ch = chain(string).ifExists(String.uppercased).to(trim).to(print);
ch.do(); 

Now I see it, maybe it can be worked out to allow passing the value at the end...

I agree on the reason for using >|> for composition, but.... isn't it a bit "fishy"?

Sure, it's definitely a fish.

It's pretty trivial to implement this via the same trick I used to implement #6615, so might as well work in it now.

An implementation of this is now available on the 6615 branch.

I've merged this work to master. Still need to mention >|> in the spec.

Added to spec. Closing this issue, but please reopen if you run into any serious problem with this work.