One simplification,
f_and = liftM2 (&&)
f_or = liftM2 (||)
or
= liftA2 (&&)
= liftA2 (||)
in the ((->) r) applicative functor.
Applicative version
Why? We have:
instance Applicative ((->) a) where
(<*>) f g x = f x (g x)
liftA2 f a b = f <$> a <*> b
(<$>) = fmap
instance Functor ((->) r) where
fmap = (.)
So:
\f g -> liftA2 (&&) f g
= \f g -> (&&) <$> f <*> g -- defn of liftA2
= \f g -> ((&&) . f) <*> g -- defn of <$>
= \f g x -> (((&&) . f) x) (g x) -- defn of <*> - (.) f g = \x -> f (g x)
= \f g x -> ((&&) (f x)) (g x) -- defn of (.)
= \f g x -> (f x) && (g x) -- infix (&&)
Monad version
Or for liftM2, we have:
instance Monad ((->) r) where
return = const
f >>= k = \ r -> k (f r) r
so:
\f g -> liftM2 (&&) f g
= \f g -> do { x1 <- f; x2 <- g; return ((&&) x1 x2) } -- defn of liftM2
= \f g -> f >>= \x1 -> g >>= \x2 -> return ((&&) x1 x2) -- by do notation
= \f g -> (\r -> (\x1 -> g >>= \x2 -> return ((&&) x1 x2)) (f r) r) -- defn of (>>=)
= \f g -> (\r -> (\x1 -> g >>= \x2 -> const ((&&) x1 x2)) (f r) r) -- defn of return
= \f g -> (\r -> (\x1 ->
(\r -> (\x2 -> const ((&&) x1 x2)) (g r) r)) (f r) r) -- defn of (>>=)
= \f g x -> (\r -> (\x2 -> const ((&&) (f x) x2)) (g r) r) x -- beta reduce
= \f g x -> (\x2 -> const ((&&) (f x) x2)) (g x) x -- beta reduce
= \f g x -> const ((&&) (f x) (g x)) x -- beta reduce
= \f g x -> ((&&) (f x) (g x)) -- defn of const
= \f g x -> (f x) && (g x) -- inline (&&)