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.