
From time to time, I've wanted to have a more pleasant way of writing point-free compositions of curried functions. One might want to transform both the first and second arguments, for instance, or only the third argument, of a curried function. I've never known a (non-cryptic) way to do this.
For example, given: f :: a -> b -> c g :: a1 -> a h :: b2 -> b I'd like to be able to write something like: \ x y -> f (g x) (h y) in a way that is both point-free and applicative. In other words, I'd like to apply a function to "pipes" or transformers rather than to values. Recent posts by Conal Elliott [1,2] got me thinking about this again, and I've found a simple solution. I use two of Conal's combinators: argument = flip (.) result = (.) And now we define: infixr 2 ~> f ~> g = argument f . result g infixl 1 $. ($.) = flip ($) Which lets us write: -- transform both arguments f $. g ~> h ~> id -- transform just the second argument f $. id ~> h ~> id The name ($.) is chosen to indicate that this is (roughly) a composition disguised as an application. The transformer spec to the right of ($.) looks like the type of the function to the left, and consists of a transformer for each argument and one for the result type. Of course, (~>) is right associative, and id can match the entire tail "in bulk", so we can also write something like: f $. g ~> id And of course, each transformer can be a pipeline, so assuming proper types, we can do things like: f $. id ~> (length.snd.unWrap) ~> wrap More details here: http://matt.immute.net/content/pointless-fun Questions: 1. Is there already another well-known way to do this? It seems a common enough problem... 2. Do these particular combinators already exist somewhere with other names? 3. Are there better names for these functions? Thanks! Matt [1] http://conal.net/blog/posts/semantic-editor-combinators/ [2] http://conal.net/blog/posts/prettier-functions-for-wrapping-and-wrapping/

On Wed, Dec 3, 2008 at 10:17 AM, Matt Hellige
From time to time, I've wanted to have a more pleasant way of writing point-free compositions of curried functions.
I'd like to be able to write something like: \ x y -> f (g x) (h y)
This particular composition of f with g and h is an example of composition in what mathematicians call an operad. I think operads glue things just how you want. Unfortunately (1) I don't think mathematicians have great notation for it either and (2) it's hard to combine operads with currying because they rely on having a well defined notion of the number of arguments of a function. -- Dan

On Thu, Dec 4, 2008 at 11:19 AM, Dan Piponi
On Wed, Dec 3, 2008 at 10:17 AM, Matt Hellige
wrote: From time to time, I've wanted to have a more pleasant way of writing point-free compositions of curried functions.
I'd like to be able to write something like: \ x y -> f (g x) (h y)
This particular composition of f with g and h is an example of composition in what mathematicians call an operad. I think operads glue things just how you want. Unfortunately (1) I don't think mathematicians have great notation for it either and (2) it's hard to combine operads with currying because they rely on having a well defined notion of the number of arguments of a function.
Yes, of course I should have mentioned operads! I've thought of operads in connection with this puzzle before (and I seem to remember a recent post from you on the subject), but this time around, it slipped my mind... Thanks for the reminder! Something like operadic composition for curried functions is exactly what I want to define, and I agree that your (1) and (2) are exactly the sticking points. I'm relatively happy with what I've come up with so far, but I do still wonder if it's possible to do better. An alternative approach would be to define an Operad type class, and attempt to coax curried functions into and out of an instance as needed. I hesitate to propose that it's impossible, but I guess it would take type class hacking beyond my abilities in any case. We could imagine being able to write something like: f [g, h] and I think this is the route you took in your blog post? But of course your way only works for uncurried functions, right? And there's a bit of boilerplate? I also believe that this approach would leave at least some arity checking to runtime, which I would consider a shortcoming. Perhaps it would be possible to overcome this with length-indexed lists? Finally, there's a further disadvantage to both of these approaches: it seems to me that neither approach allows us to "cross pipes". For instance, we can't define flip using these techniques, or any "deep flip": \ x y z -> f x z y Is it true in general that operads cannot express pipe-crossing compositions? Or is it just a shortcoming of this particular implementation? Of course this is getting farther afield, and I don't believe that my approach can express this either. Anyway I'd still love to know the answers the questions in my last email, as well as the new questions posed in this one. ;) Thanks for taking the time! Matt

On Thu, Dec 4, 2008 at 12:07 PM, Matt Hellige
Finally, there's a further disadvantage to both of these approaches: it seems to me that neither approach allows us to "cross pipes". For instance, we can't define flip using these techniques, or any "deep flip": \ x y z -> f x z y Is it true in general that operads cannot express pipe-crossing compositions? Or is it just a shortcoming of this particular implementation? Of course this is getting farther afield, and I don't believe that my approach can express this either.
This is not exactly true. It's possible in my scheme to express a deep flip, as long as it's the last thing you want to do: \ f x y z -> f x z y == id ~> flip It's not clear to me whether your operad class can express this (or whether operads in general can express this), or whether my scheme can express more general permutations of arguments. Matt

On Thu, Dec 4, 2008 at 10:21 AM, Matt Hellige
\ f x y z -> f x z y == id ~> flip It's not clear to me whether your operad class can express this (or whether operads in general can express this)
There exists an operad that can (at the cost of even more notation), but you're right that the specific operad that I implemented can't. Actually, if you look at the papers, mathematicians do have a perfectly good notation for this, and it'd be cool if there were a programming language that supported it well: drawing diagrams! Johannes Waldmann said:
Well, there is Combinatory Logic.
But it's not known for its ease of use :-) -- Dan

On Thu, Dec 4, 2008 at 12:52 PM, Dan Piponi
On Thu, Dec 4, 2008 at 10:21 AM, Matt Hellige
wrote: \ f x y z -> f x z y == id ~> flip It's not clear to me whether your operad class can express this (or whether operads in general can express this)
There exists an operad that can (at the cost of even more notation), but you're right that the specific operad that I implemented can't.
Makes sense. It turns out that with one more definition, we can express this fairly easily in my approach: infixr 2 ~*> (~*>) = (.) Now we can use functions that inspect multiple arguments, without moving our "position" in the argument list: f w x y z = concat $ map show [w,x,y,z] ex1 = (f $. id ~> flip ~*> id ~> id ~> length ~> id) 1 2 3 [4,5,6,7] roll f x y z = f y z x ex2 = (f $. id ~> roll ~*> id ~> id ~> length ~> id) 1 [4,5,6,7] 3 2 However, I have grave doubts about the usefulness of this, and I think it more than sacrifices whatever we gained in clarity. I maintain that ($.) and (~>) are clear and useful, but this seems to be one step too far...
Actually, if you look at the papers, mathematicians do have a perfectly good notation for this, and it'd be cool if there were a programming language that supported it well: drawing diagrams!
Hear, hear!
Johannes Waldmann said:
Well, there is Combinatory Logic.
But it's not known for its ease of use :-)
And we'd also have to change the definitions of all of our functions. Ideally, we'd like the same definitions to work either way. Anyway, thanks for all your thoughts... Matt

I'd like to be able to write something like: \ x y -> f (g x) (h y)
I don't think mathematicians have great notation for it either
Well, there is Combinatory Logic. http://www.haskell.org/haskellwiki/Combinatory_logic J.W.

Hello Stefan, Friday, December 5, 2008, 8:35:18 PM, you wrote:
\ x y -> f (g x) (h y) [...] f $. g ~> h ~> id
I keep help wonder: other than a "5 chars", what is it we have gained?
Haskell programmers would be paid more :D -- Best regards, Bulat mailto:Bulat.Ziganshin@gmail.com

On Fri, Dec 5, 2008 at 11:35 AM, Stefan Monnier
\ x y -> f (g x) (h y) [...] f $. g ~> h ~> id
I keep help wonder: other than a "5 chars", what is it we have gained?
Certainly in such a simple case, there's no benefit. In more complex cases, especially where we're focused on just writing the combinator itself (i.e., in cases where we won't use ($.)), I think it might make sense (there is an example in the comments of Conal's recent post at [1]). But anyway I do mean "might"... I happily admit the possibility that this may be nothing more than a curiosity. I'm happy to have found a solution to my little puzzle, but I'm not sure how often I'll actually use it. Time will tell, I suppose. Matt
participants (5)
-
Bulat Ziganshin
-
Dan Piponi
-
Johannes Waldmann
-
Matt Hellige
-
Stefan Monnier