Open gavinking opened 12 years ago
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?
Sorry, I screwed up the direction of my arrow. Fixed now.
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?
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.
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...
Hrm, actually (*)
doesn't look good at all. A diamond does look reasonable:
(f<>g)(x)
Or, alternatively, <<
works nicely:
(f<<g)(x)
But <<
is bitshift, already used... it probably wouldn't be confusing, as long as it can be parsed without any problems...
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)
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...
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
.
I believe |>
is often used for piping.
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)...
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.
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.
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)
.
Yes
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.
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.
I would like an operator for composing functions, but I'm not sure what actual symbol to use. I suppose
=>
and<=
could work.or
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?