10

Many functions can be reduced to point free form - but is this true for all of them?

E.g. I don't see how it could be done for:

apply2 f x = f x x 
Petr
  • 62,528
  • 13
  • 153
  • 317
Cubic
  • 14,902
  • 5
  • 47
  • 92
  • 2
    Find and install GOA a.k.a. "GHC On Acid", then from within GHC type `:pl \f x -> f x x` (pl stands for "point-less", which is a less reverent term for point-free). – n. m. could be an AI Nov 01 '12 at 20:10
  • 2
    related: http://stackoverflow.com/questions/9169046/writing-in-pointfree-style-f-x-g-x-x/11050971#11050971 Your `apply2` is what's known as `W` combinator, `_W = join -- _W f x = f x x = CSI = SS(KI) = SS(SK)`. `C` is `flip`; `I` is `id`; `S` is `(<*>)`, `K` is `const`. Check it out. – Will Ness Nov 02 '12 at 15:23
  • 2
    `Control.Applicative> (<*> id) (,) 4` ==> `(4,4)`. – Will Ness Nov 02 '12 at 15:45
  • [The Theory of Concatenative Combinators](http://tunes.org/~iepos/joy.html) offers an alternate combinatorial basis, in which your function might be expressed as `[dup] dip i` or `dup dig2 i`, depending on order of arguments. In either case, `dup` duplicates the value and `i` applies the function to it. – Jon Purdy Oct 31 '13 at 07:00

3 Answers3

11

Logical combinators (i.e. the S, K, I combinators) are essentially point-free forms of functions, and the lambda-calculus is equivalent to combinatory logic, so I think this suggests that the answer is yes.

The combinator for your apply2 function is (if I am reading things correctly):

((S((S(KS))K))(K((S((SK)K))((SK)K))))

also known as the "Lark", from Raymond Smullyan's Combinatory Birds page.

(edit-in:) Turns out1 the above is equivalent to \f x -> f (x x). According to the comments by "@gereeter" here below it is indeed known as the "Lark", whereas the function \f x -> f x x requested in the question is the "Warbler" from the aforementioned book (a.k.a. the "W" combinator), W f x = S(S(K(S(KS)K))S)(KK)SI f x = S(S(KB)S)(KK)SI f x = CSI f x = SfIx = f x x.


1 here:

((S((S(KS))K))(K((S((SK)K))((SK)K)))) f x =
  S( S(KS) K) (K( S( SK K) ( SK K)))  f x =   -- SKK    == I
  S (S(KS) K) (K( S  I       I    ))  f x =   -- S(KS)K == B
  S B         (K( S  I       I    ))  f x =
  Bf (K(SII)f) x = Bf (SII) x = f (SII x) = f (x x)
Community
  • 1
  • 1
ErikR
  • 51,541
  • 9
  • 73
  • 124
  • For a start, not all functions are curried; tuples are magic-encoded! You can express them without the syntax, but there aren't functions to do this for large tuples. Also, Haskell is not purely the lambda calculus! – AndrewC Nov 01 '12 at 20:25
  • 4
    @AndrewC While it's true that data is a bit outside the realm of the lambda calculus, it's not *that* far out: there are various encodings of data as functions that can simulate sums, products, and recursive types. – Daniel Wagner Nov 01 '12 at 21:21
  • @DanielWagner but in Haskell we don't restrict ourselves to just data encoded as functions. At a fairly low level, I think Haskell requires points for data deconstruction. How is 'either' implemented? Can it be implemented point-free, using only point-free functions? Sure, you can make it work for a function-encoded sum type, but that's not the same. – John L Nov 02 '12 at 00:53
  • 1
    @DanielWagner Obviously. However, the encoding of the tuples is built in and not available other than via the given syntax. Hence I'd happily withdraw my criticism if this answer said "...suggests the answer is yes, for curried functions" or similar restriction of scope. – AndrewC Nov 02 '12 at 00:55
  • 1
    I think any primitive selector for any primitive data constructor should be admitted to pointfree expressions. We admit `(+)` and `(:)` and `fst`, why not `snd3 (_,x,_) = x`? Or `tupsel_I_N (x_1, x_2, ..., x_i, ..., x_n)=x_i` just the same?? – Will Ness Nov 02 '12 at 15:32
  • I agree that it is possible for any function; they used to compile functional programs to combinator expressions once. BTW I tried your expression, but it didn't work. :) This did: `let _K=const; _S a b c =(<*>) a b c in _S _S (_S _K) (,) 4`. Or this: `(<*> id) (,) 4`. – Will Ness Nov 02 '12 at 16:06
  • @WillNess Two reasons: 1. It's shifting the goalposts to add combinators to base just to work round a counter-example, and 2. Because it would make the prelude infinitely long. If I'm allowed to invent my own infinitely long prelude it makes the question both trivially and meaninglessly yes. Please come back to the real world, with real Haskell and real base to answer this question. – AndrewC Nov 02 '12 at 16:23
  • 1
    That's why I qualified my answer because I wasn't sure if I was interpreting the lambda expressions correctly. – ErikR Nov 02 '12 at 16:24
  • @AndrewC I don't propose to add new combinators! That would be cheating. :) I'm talking only about primitive selectors/constructors for newly introduced primitive datatypes. What if there were no `snd` predefined in Haskell, would you contest it being a primitive op, even if defined by `\(a,b)->b`? I wouldn't. – Will Ness Nov 02 '12 at 16:26
  • @AndrewC and it doesn't have to be in the Prelude. `tupsel_i_n` could be interpreted directly by a compiler. – Will Ness Nov 02 '12 at 16:28
  • @WillNess I think we're answering different questions. I'm answering "Can any function be reduced to a point-free form?" under the [haskell] tag on StackOverflow. You're answering "Can any function in principle be reduced to a point-free form?" under the [functional-programming] tag on `http://cs.stackexchange.com/`. – AndrewC Nov 02 '12 at 16:30
  • @AndrewC if we're talking about "Real Haskell" than the answer is *equally trivially* "Not Possible"! `data X = X Int ; fun (X i) = i` is trivially irreducible. :) – Will Ness Nov 02 '12 at 16:37
  • @WillNess I think you've grasped my answer, yes. It's simple to find functions that aren't expressible in point free form. You call that trivial, I never claimed to have said anything profound, just a correct answer to the question. – AndrewC Nov 02 '12 at 16:43
  • @WillNess However I wouldn't have used that example, I'd prefer to use something from base, but the first thing that popped in my mind was long tuples. Maybe and Either have got plenty of higher-order functions and Applicative instances to work around the no-selector problem. – AndrewC Nov 02 '12 at 17:11
  • This function is actually the Warbler, not the Lark. The Lark is `\f x -> f (x x)`, while the Warbler is `\f x -> f x x`, which is `apply2`. – gereeter Nov 03 '12 at 12:38
  • @gereeter The answer has `S (S(KS)K) (K(SII)) = SB(K(SII))`. Testing, `SB(K(SII)) f x = Bf(K(SII)f) x = Bf(SII)x = f(SIIx) = f (x x)`. Confirmed, the expression in this answer is for `\f x -> f (x x)`. – Will Ness Nov 05 '12 at 20:46
  • I agree. However, `\f x -> f x x`, or `\f x -> (f x) x` was wanted, not `\f x -> f (x x)`. – gereeter Nov 05 '12 at 22:58
  • @WillNess Even shorter is `SS(KI)fx` = `Sf(KIf)x` = `fx(KIfx)` – Ingo Oct 31 '13 at 09:03
  • @Ingo `= fx(Ix) = fxx`. so `W = SS(KI)`. Nice! :) – Will Ness Oct 31 '13 at 09:12
  • 1
    @Ingo BW this appears in the link "(a.k.a. [the "**W**" combinator](http://stackoverflow.com/a/11050971/849891))" in the answer. It gives `SS(SK)` also. All seem to be typeable, using `<*>` for **S**. – Will Ness Oct 31 '13 at 09:22
  • @WillNess Indeed. (Looks like you always answer the same questions! :) I have the expression from a small program of mine that does the variable elimination and it looks like it worked correctly in this case. – Ingo Oct 31 '13 at 09:52
  • @Ingo it was a phase like that. :) – Will Ness Oct 31 '13 at 15:20
11

S-K basis

As already mentioned, with a proper fixed set of combinators, any lambda term can be converted into a form that uses only those combinators and function application - no lambda abstraction (hence no variables). The most well known set of combinators is S and K. Se Combinatory Logic/Completeness of the S-K basis for the description of the procedure. The combinators are defined as

K x y    = x
S x y z  = (x z) (y z)

Sometimes the identity combinator I is included, but it's redundant as I = S K K.

A single combinator basis

Interestingly you can do that even with a single combinator. The Iota language uses

U f = (f S) K

and it can be shown that

I = (UU)
K = (U(U(UU)))
S = (U(U(U(UU))))

So we can convert any lambda term into a binary tree with no other information than it's shape (all the leaves contain U and and the nodes represent function application).

A more efficient basis S, K, I, B, C

However, if we want to be a bit efficient and get a reasonably sized conversion, it's helpful to use I and to introduce two more redundant combinators, which are called B and C:

C f x y  = f y x
B f g x  = f (g x)

Here C reverses the order of arguments of f and B is function composition.

This addtion reduces the length of the output significantly.

These combinators in Haskell

Actually Haskell already contains all those standard combinators in some form. In particular:

I = id
K = const
  = pure :: a -> (r -> a)
S = (<*>) :: (r -> a -> b) -> (r -> a) -> (r -> b)
B = (.)
  = (<$>) :: (a -> b) -> (r -> a) -> (r -> b)
C = flip
  = \k x -> k <*> pure x

where pure, <*> and <$> are functions from the Applicative functors type class which we here specialize for the reader monad (->) r.

So in your case, we could write

apply2 = (<*>) `flip` id

Why the reader monad?

In the process of abstraction elimination we try to convert a term of the form λx -> M :: r -> a (where r is the type of x and a is the type of M) into a form without x. We do this by recursively processing M and we subsequently convert each its sub-term of type b (possibly containing x) into a function of type r -> b (not containing x), and then we combine these sub-terms together. And that's just what the reader monad is designed for: To combine functions of type r -> something together.

For more details see The Monad Reader, Issue 17: The Reader Monad and Abstraction Elimination.

How about data structures?

For constructing data structures we simply use their constructors, there is no problem here.

For deconstructing them, we need some way to get rid of pattern matching. This is something a compiler has to do when compiling a functional program. Such a procedure is described in The Implementation of Functional Programming Languages Chapter 5: Efficient compilation of pattern matching. The idea is that for each data type we have one case function that describes how to deconstruct (fold) the data type. For example, for lists it foldr, for Either its either, and let's say for 4-tuples it'd be

caseTuple4 :: (a -> b -> c -> d -> r) -> (a,b,c,d) -> r
caseTuple4 f (a,b,c,d) = f a b c d

etc. So for each data type we add its constructors, its deconstructing case function, and compile patterns into this function.

As an example, let's express

map :: (a -> b) -> [a] -> [b]
map f []        = []   
map f (x : xs)  = f x : map f xs

this can be expressed using foldr:

map f = foldr (\x xs -> f x : xs) []

and then converted using the combinators we discussed earlier:

map = (foldr . ((.) (:))) `flip` []

You can verify that it indeed does what we want.

See also System F data structures which describes how data structures can be encoded directly as functions if we enable higher rank types.

Conclusion

Yes, we can construct a fixed set of combinators and then convert any function into point-free style that uses only these combinators and function application.

Petr
  • 62,528
  • 13
  • 153
  • 317
  • To my mind, the only real issue here is if data deconstructors are considered part of the set of "functions", i.e. is a data deconstructor a valid choice for "any function" as intended by the question? The answer follows pretty quickly from that. – John L Nov 03 '12 at 02:01
  • @JohnL btw, `fst = uncurry const` and `snd = uncurry (const id)`. But there isn't such stuff for `fst3` and `fstN` - because **there isn't `uncurry3` or `uncurryN`**. But why shouldn't there be? (I still contend that it is what it means to introduce a *primitive* type to a language: provide *primitive* means of selection from it. Or else it is unusable.) I like this answer! :) – Will Ness Nov 03 '12 at 07:04
  • @WillNess that only works because `uncurry` itself uses `fst` and `snd`, both of which are point-ful (at least in ghc). The problem is that `data` introduces a new type, and a new primitive means of extracting data from it, but that data extraction mechanism is *not* the case function as described above. So we have to write the case function ourselves, in a pointful form. I suppose you could say I'm disagreeing on a technicality, however as Haskell currently requires that we construct the "fixed set of combinators" manually in a pointful form, we cannot write every function pointfree. – John L Nov 05 '12 at 03:54
  • @JohnL that's just coincidental. It could have been defined the other way around just as well. Moreover, you make my point for me - apparently `fst` and `snd` are pointful but are widely used in pointfree rewrites nevertheless! Of course you're absolutely right if we limit our base to what comes pre-built in the "Real" Haskell; I already agreed on that somewhere else on this page (and I used your example of `data` in that argument). :) I see it as just a matter of convention - or personal choice. And for me, choosing otherwise (as e.g. in this answer) makes perfect sense too. – Will Ness Nov 05 '12 at 08:00
  • Just an idea, I've never used [generics](http://www.haskell.org/haskellwiki/Generics) but perhaps could this be a way how to fold arbitrary data structures without having a separate folding function for each data type? – Petr Nov 05 '12 at 19:49
5

There are a lot of functions that look like they might not be, but are expressible in a point free style, but to get one that isn't, you could quickly define one that works with extremely large tuples where there aren't any standard functions.

I think this sort of thing is less likely to be expressible point-free, not because of the complexity, but because there aren't many functions of tuples this size:

weird (a,b,c,d,e,f,g,h,i,j) = (a<*>b,c++d,e^f+a,g ()-h 4+e,j <*> take f i)

Your example:

apply2 :: (b -> b -> a) -> (b -> a)
apply2 = join

It's join in the reader monad ((->) b)

join :: Monad m => m (m a) -> m a

so in this case

join :: ((->) b) ((->) b a) -> ((->) b) a
join :: ((->) b) (b -> a) -> (b -> a)
join :: (b -> (b -> a)) -> (b -> a)
join :: (b -> b -> a) -> (b -> a)

Many more functions than we expect have point-free versions, but some point-free expressions are a complete mess. Sometimes it's better to be explicit than terse.

AndrewC
  • 32,300
  • 7
  • 79
  • 115
  • 1
    This doesn't address the OP's underlying question, which is can **any** function be reduced to a point-free form...you've just debunked the example the OP thought might not. – NominSim Nov 01 '12 at 19:48
  • @NominSim you said that as I was editing to answer the general question. – AndrewC Nov 01 '12 at 20:14
  • 1
    Perhaps someone can try running :pl in GOA on your example? A difficult example without (an obvious) point-free rep is not a proof! – Andy Hayden Nov 01 '12 at 20:25
  • `moreObvious (a,b,c,d,e,f,g,h,i,j,k,l,m,n,o) = (d,e,f,a,g,b,n,o,m,f,e,g,a,a,c,n)`. My point is there aren't functions that deal with tuples that length to go point-free with. – AndrewC Nov 01 '12 at 20:28
  • I'm not so sure, the point-less function might exist but be incredibly long/more complex... perhaps you can build up to that function from smaller functions. Certainly it's not obvious either way (and needs a proof). – Andy Hayden Nov 01 '12 at 21:24
  • I think you've missed my point. My proof would consist of showing that in all of the base classes there are no functions that take a 14-tuple as an argument. I already checked that with hoogle. You can't build up to a 14-tuple using smaller tuples, it's not a list. – AndrewC Nov 02 '12 at 00:39
  • @AndrewC I think you're right, but perhaps this answer doesn't adequately explain your argument. In a simpler form, how can you write 'fst :: (a,b) -> a' point-free? It's not possible without built-in language support. When dealing with Haskell data, go down far enough and you'll need points. – John L Nov 02 '12 at 00:46
  • @JohnL very true, I agree with you, but for the purposes of the question, I'd say it's fair to say that f(x,y) = x+2 is expressible in a point free style as (+2).fst I'm not answering by saying there are no essentially point-free functions, rather that some functions are unexpressible in a point-free style. – AndrewC Nov 02 '12 at 01:08
  • @AndrewC I think this relies too much on a coincidental technicality of there being `fst3` defined for us, but not `fst17`. For the purposes of this Q, I think we *can* assume the existence of `nthOfN i n = (\(a1,a2, .... , ai, .... ,an) -> ai)` primitive functions for *any* `i` and `n`, to which we would try to reduce an expression, :) simply because it is *trivial* to define them, and the essence of point-free transformation is to reduce an expression to combinators and *trivial* accessors / selectors. – Will Ness Nov 02 '12 at 15:14
  • huh. I was so sure `fst3` is predefined. :) This just strengthens my point IMO - surely `fst3` should be admitted. – Will Ness Nov 02 '12 at 15:38
  • @WillNess Goalpost shifting alert! If we're allowed to additionally define any trivial selectors and combinators we like, the question becomes utterly utterly trivial. The example given in the question is one such trivial argument-shifting function. Surely there's no point in the question if you can get round it by defining a non-point-free helper function whenever there's something inconvenient in the way. That's why I only mentioned base in my previous comment. – AndrewC Nov 02 '12 at 16:10
  • @AndrewC no contest. That is what it means to introduce a primitive data type into language, IMO. Instead of Church numerals, we have `Int`s. That means we also have `(+)` etc. Same thing for `Either a b` - pattern matching on `Left` or `Right` should be considered a primitive operation. Same for any user-defined `data`. If we represent all of them as just tuples, that the least we can do is to provide primitive accessors/constructors for each tuple type. I think. :) – Will Ness Nov 02 '12 at 16:10
  • @WillNess You'd also need mkTuple15 a b c... = (a,b,c,...) for each N. (That's infinitely many functions you've just added to base.) – AndrewC Nov 02 '12 at 16:11
  • @AndrewC "no contest" was meant for your `mkTuple15` argument. And I'm only talking about primitive constr/selectors for newly introduced primitive types. This does not apply to `W`, IMO. – Will Ness Nov 02 '12 at 16:14
  • @AndrewC if you haven't yet had the opportunity to see my other coments (I apoogize for the amount) `W = CSI = SS(SK)`. So is definable. Everything is; they used to compile to combinators once! – Will Ness Nov 02 '12 at 16:17
  • @WillNess We seem to have accidentally continued this discussion under user5402's answer. – AndrewC Nov 02 '12 at 17:12
  • The idea of a balance between point-free and explicit representations is known as "tacid programming" and is really important in the realm of functional programming – GooseDeveloper Feb 12 '22 at 23:57