I would like to learn how the following would be done in point-free:
withinBounds :: [Int] -> Bool
withinBounds xs = (all (>= 0) xs) && (all (<= 8) xs)
I understand that it is superior to write it this way for readability/sanity's sake, but I'd like to learn more about how I can compose functions. I've been scratching my head as to how I can do this. The whole (expanded?) type signature is
[Int] -> ([Int] -> Bool) -> ([Int] -> Bool) -> (Bool -> Bool -> Bool) -> Bool
The type signature of the composition I'm trying to get to is
(a -> b) -> (a -> c) -> (b -> c -> d) -> (a -> d)
I wrote the following as notes in a bastard-lambda form. If there is a way to somewhat simplify the problem with the lambda calculus, it'd be great if that could be explained too:
\L@[] -> \f1@([] -> Bool) -> \f2@([] -> Bool) -> \f3@(Bool -> Bool -> Bool) -> f3.(f1.L).(f2.L)
In the above, .
is application, @
is capturing (so f3 is another name for (Bool -> Bool -> Bool)).
Many thanks.
Edit: I know this is not the most optimal or reusable code, and I know turning this into point-free makes it worse in terms of readability etc. To clarify, I am asking how I can turn it into point-free because I want to learn more about haskell and composition.
Doing an end-run around the whole question, I think I would probably write it this way:
import Data.Ix
withinBounds = all (inRange (0, 8))
Of course, that's punting a bit, since then one would naturally ask how to implement inRange
in a pointfree way. If you absolutely couldn't use inRange
, then I would implement it inline this way:
withinBounds = all (liftA2 (&&) (>=0) (<=8))
This uses the reader applicative to supply a single argument to two functions. liftA2
is your requested combining function, though with arguments flipped:
requested :: (a -> b) -> (a -> c) -> (b -> c -> d) -> (a -> d)
liftA2 :: (b -> c -> d) -> (a -> b) -> (a -> c) -> (a -> d)