How to write this function in point-free style?

510 views Asked by At

How to rewrite the following function in point-free style, removing the parameter x from the definition completely (the other two may stay):

between min max x = (min < x) && (x < max)

This is not an assignment, just a question. I don't know how to proceed. I can turn it into a lambda function

between min max = \x -> (min < x) && (x < max)

but this is not point-free, as x is still there. Please help.

4

There are 4 answers

1
mschmidt On BEST ANSWER

Another solution (needs import of Control.Applicative):

between min max = liftA2 (&&) (min <) (max >)
0
justkris On

It can be done using the Reader applicative:

between min max = \x. (min < x) && (x < max)
              { Convert infix comparisons to sections }
                = \x. ((min <) x) && ((< max) x)
              { Move infix (&&) to applicative style }
                = \x. (&&) ((min <) x) ((< max) x)
              { Lift to applicative style using the applicative instance of `(->) a` }
                = \x. (pure (&&) <*> (min <) <*> (< max)) x
              { Eta-reduce }
                = pure (&&) <*> (min <) <*> (< max)
              { Optionally simplify for idiomatic style }
                = (&&) <$> (min <) <*> (< max)
5
chi On

Using Control.Arrow we can reach this nearly obfuscated code:

(min <) &&& (< max) >>> uncurry (&&)

This relies on the predefined >>> for left-to-right composition, f &&& g = \x -> (f x, g x), and uncurrying.


pointfree.io also suggests the following unreadable code:

between = (. flip (<)) . ap . ((&&) .) . (<)
0
Will Ness On

By operator sections transformation,

between min max x = (min < x) && (x < max)
                  = ((&&) . (min <)) x ((< max) x)

Now this fits a pattern for S-combinator, S f g x = (f x) (g x). There are many ways of encoding it in Haskell, but the main two are via Applicative and via Arrows:

    _S f g x = (f x) (g x)
             = (f <*> g) x
             = uncurry id . (f &&& g) $ x

The second gives us

between a z = uncurry (&&) . ((a <) &&& (< z))

And the first, even more fitting

between a z = (&&) <$> (a <) <*> (< z)
            = liftA2 (&&) (a <) (< z)
            = (a <) <^(&&)^> (< z)     -- nice and visual

(<^) = flip (<$>) 
(^>) = (<*>)

But we could also fiddle with other combinators, with much less satisfactory results though,

    _S f g x = f x (g x)
             = flip f (g x) x
             = (flip f . g) x x
             = join (flip f <$> g) x
             = (flip f =<< g) x 

or

             = (f x . g) x
             = (. g) (f x) x
             = ((. g) =<< f) x

which illustrates nicely the dangers of pointlessness in the pursuit of the pointfree.

There's one more possibility that makes sense (syntactically), which is

    _S f g x = (f x) (g x)
        --   = foldr1 ($) . sequence [f,g] $ x  -- not valid Haskell

        -- sequence [f,g] x = [f x,g x]

This is not a valid Haskell in general because of the typing issues, but in our specific case it does give rise to one more valid definition, which also does seem to follow the inner logic of it nicely,

between a z = -- foldr1 ($) . sequence [(&&).(a <), (< z)] -- not OK
            = foldr1 (&&) . sequence [(a <), (< z)]        -- OK
            = and . sequence [(a <), (> z)]

because (a <) and (> z) have the same type.