Here's the offending code (also on lpaste.net):
module Data.Graph.Dijkstra
( dijkstra
, dijkstraPath
) where
-- Graph library import
import Data.Graph.Inductive hiding (dijkstra)
-- Priority queue import
import qualified Data.PQueue.Prio.Min as PQ
-- Standard imports
import Data.List (find)
import Data.Maybe (fromJust)
import Data.Monoid
-- Internal routine implementing Dijkstra's shortest paths
-- algorithm. Deemed internal because it needs to be kickstarted with
-- a singleton node queue. Based on FGL's current implementation of
-- Dijkstra.
dijkstraInternal ::
(Graph gr, Ord b, Monoid b) => gr a b -> PQ.MinPQueue b [Node] -> [[Node]]
dijkstraInternal g q
| PQ.null q = []
| otherwise =
case match v g of
(Just cxt,g') -> p:dijkstraInternal g' (PQ.unions (q' : expand cxt minDist p))
(Nothing, g') -> dijkstraInternal g' q'
where ((minDist,p#(v:_)), q') = PQ.deleteFindMin q
expand (_,_,_,s) dist pathToC =
map (\(edgeCost,n) -> PQ.singleton (dist `mappend` edgeCost) (n:pathToC)) s
-- Given a graph and a start node, returns a list of lists of nodes
-- corresponding to the shortest paths from the start to all other
-- nodes, where the edge costs are accumulated according to the Monoid
-- instance of the edge label type and costs are compared by the edge
-- label's Ord instance.
dijkstra :: (Graph gr, Ord b, Monoid b) => gr a b -> Node -> [[Node]]
dijkstra g start = dijkstraInternal g (PQ.singleton `mempty` [start]) -- !!!
dijkstraPath :: (Graph gr, Ord b, Monoid b) => gr a b -> Node -> Node -> [LNode a]
dijkstraPath g start goal =
let paths = dijkstra g start
pathNodes = find ((goal ==) . head) paths -- Can paths be empty?
in
case pathNodes of
Nothing -> []
Just ps -> reverse $ map (\n -> (n, fromJust $ lab g n)) ps
The weirdness is in line 39, marked with the -- !!! comment. This code compiles, but the runtime error is that no matter what, the PQ.singleton function returns an empty priority queue. I realized I had accidentally added backticks to mempty, so when I removed those the code compiled and worked as expected.
This however struck me as strange. How could the code have correctly compiled with backticks around mempty, which is not a binary function at all (mempty :: a)?
After some very generous help on #haskell, I found that it had something to do with the Monoid instance for functions:
instance Monoid b => Monoid (a -> b)
I now have an extremely vague understanding of why this error successfully typechecked, but I still feel somehow morally wronged. Can someone explain exactly how this happened?
Additionally, I'd also like to direct attention to the priority queue's singleton function that I'm using: according to the source, it doesn’t return an empty queue. However, at line 24, that same priority queue immediately gets evaluated as being empty. (I verified this with trace calls.)
So, in general, the code:
a `f` b
is just syntactic sugar for:
f a b
Therefore your code became:
mempty PQ.singleton [start]
So the type-checker inferred the type for that particular mempty:
mempty :: (k -> a -> PQ.MinPQueue k a) -> [Node] -> PQ.MinPQueue b [Node]
You correctly found the right instance that is the problem. Anything of type a -> b is a Monoid, provided that b is. So let's bracket that type above:
mempty :: (k -> a -> PQ.MinPQueue k a) -> ([Node] -> PQ.MinPQueue b [Node])
So, that type can be a Monoid if [Node] -> PQ.MinPQueue b [Node] is a Monoid. And by the same logic, [Node] -> PQ.MinPQueue b [Node] can be a Monoid if PQ.MinPQueue b [Node] is one. Which it is. So the type-checker is fine with this code.
Presumably the implementation of our troublesome instance is:
instance Monoid => Monoid (a -> b) where
mempty = const mempty
So overall, you get an empty priority queue. So really, I think it comes down to a question of whether it was wise for the designers to include this instance at all. Its net effect is that any function returning a monoid can be a monoid, which should allow you to combine the results. The more useful case here is mappend, which can append two a -> b functions by applying them both and using mappend to combine the results. For example:
extremes = (return . minimum) `mappend` (return . maximum)
rather than:
extremes xs = [minimum xs, maximum xs]
Hmmm, maybe someone else can produce a sensible terser example.
So backticks turn a binary function into an infix operator, making
x `op` y
equivalent to
op x y
So op needs to be of type a -> b -> c where x :: a and y :: b.
In your case, op was mempty, with the type Monoid m => m. But we know it to be of the form a -> b -> c, so substitute that and you get (this is no longer valid syntax) Monoid (a -> b -> c) => a -> b -> c, because we can substitute that m for anything as long as the constraint holds.
Now we know (due to the instance declaration) that any function of the form s -> t, where t is a Monoid, is a Monoid itself, and we also know that a -> b -> c is really a -> (b -> c), i.e. a function taking one argument and returning another function. So if we substitute a for s and (b -> c) for t, the we fulfill the Monoid instance, if t is a Monoid. Of course, t is (b -> c), so we can apply the same Monoid instance again (with s = b and t = c), so if c is a Monoid, we're good.
So what is c? The expression you had was
PQ.singleton `mempty` [start]
i.e.
mempty PQ.singleton [start]
The instance declaration for Monoid (a -> b) defines mempty _ = mempty, i.e. it's a function that ignores its argument and returns the empty element of the b Monoid. In other words, we can expand the call above to
mempty [start]
i.e. we ignore the argument and use mempty of the inner Monoid (which is b -> c). Then we repeat, ignoring the argument again:
mempty
So the expression you had is just equivalent to a single mempty, which has the type Monoid c => c, i.e. it can be any Monoid whatsoever.
In your case, the larger expression deduces c to be a PQ.MinPQueue. And MinPQueue is a Monoid instance with mempty being the empty queue.
This is how you end up with the result you're seeing.
You've had a couple good answers here already, I thought I would just post this since it's a bit simpler and helped me as I was puzzling this out in ghci.
mempty :: (a -> b) = mempty _ = mempty So it's essentially const mempty.
λ> :t mempty :: (a -> b)
<interactive>:1:1:
No instance for (Monoid b) arising from a use of `mempty'
So b has to be a Monoid since we're asking for the mempty of that type, makes sense.
λ> :t mempty :: (a -> [b])
mempty :: (a -> [b]) :: a -> [b]
λ> :t mempty :: (a -> c -> [b])
mempty :: (a -> c -> [b]) :: a -> c -> [b]
We can recursively chain these. Since (->) is right associative (a -> b) may represent (a -> c -> d) when b == (c -> d). So we can supply an arbitrary number of arguments and the mempty for functions will be recursively applied until it's consumed all arguments.
λ> import Data.Map
λ> (mempty :: (a -> c -> Map Int Int)) 4 5
fromList []
λ> (mempty :: (a -> c -> d -> Map Int Int)) 4 5 6
fromList []
So we see that applying the function mempty will throw away any arguments it's given and return the mempty for whatever type is expected at the position the expression is in.
Related
I'm trying to solve the same exercise as this other question in Chapter 15 of "Haskell Programming from First Principles". I've already made a Semigroup instance, and I'm having trouble writing the QuickCheck part of the exercise.
A Semigroup instance should satisfy:
a <> (b <> c) == (a <> b) <> c
where <> is the Semigroup mappend.
I have come up with the following:
import Data.Semigroup
import Test.QuickCheck
semigroupAssoc :: (Eq m, Semigroup m) => m -> m -> m -> Bool
semigroupAssoc a b c = (a <> (b <> c)) == ((a <> b) <> c)
newtype Combine a b = Combine { unCombine :: (a -> b) }
instance Semigroup b => Semigroup (Combine a b) where
(Combine f) <> (Combine g) = Combine (\x -> (f x) <> (g x))
instance CoArbitrary (Combine a b) where
coarbitrary (Combine f) = variant 0
instance (CoArbitrary a, Arbitrary b) => Arbitrary (Combine a b) where
arbitrary = do
f <- arbitrary
return $ Combine f
type CombineAssoc a b = Combine a b -> Combine a b -> Combine a b -> Bool
main :: IO ()
main = do
quickCheck (semigroupAssoc :: CombineAssoc Int Bool)
Everything compiles except for the quickCheck line, where it complains that there is No instance for (Eq (Combine Int Bool)) arising from a use of ‘semigroupAssoc’.
I don't think there is a way to test if two arbitrary function are equal (the functions wrapped up by Combine), but the exercise text suggests that such a thing is possible.
Any ideas on how I could make this work?
EDIT:
The authors give a hint for this exercise:
Hint: This function will eventually be applied to a single value
of type a. But you’ll have multiple functions that can produce a
value of type b. How do we combine multiple values so we have
a single b? This one will probably be tricky! Remember that the
type of the value inside of Combine is that of a function. If you
can’t figure out CoArbitrary, don’t worry about QuickChecking
this one.
#Li-yao Xia's answer seems to be the best answer. But shouldn't I use this CoArbitrary instance for something?
You can't decide whether two functions are equal. But you can test it!
Two functions are equal if and only if for any input they give the same output. This is a testable property: generate some inputs, compare the outputs. If they are different, you've got a counter-example.
-- Test.QuickCheck.(===) requires (Eq b, Show b)
-- but you can use (==) if you prefer.
funEquality :: (Arbitrary a, Show a, Eq b, Show b) => Combine a b -> Combine a b -> Property
funEquality (Combine f) (Combine g) =
property $ \a -> f a === g a
Notice that the Bool result in the type of "decidable equality" (==) :: X -> X -> Bool is replaced with Property in what we might call "testable equality" funEquality :: X -> X -> Property. It's actually not necessary to use property and convert the function a -> Property (or a -> Bool if you use (==)) to Property, but the types look neater that way.
We need to rewrite the function corresponding to the associativity property, since we no longer rely on Eq.
type CombineAssoc a b = Combine a b -> Combine a b -> Combine a b -> Property
combineAssoc :: (Arbitrary a, Show a, Eq b, Show b, Semigroup b) => CombineAssoc a b
combineAssoc f g h = ((f <> g) <> h) `funEquality` (f <> (g <> h))
Edit: at this point we're actually still missing a Show instance for Combine. QuickCheck provides a wrapper Fun to both generate and show functions as counterexamples.
main = quickCheck $ \(Fn f) (Fn g) (Fn h) ->
(combineAssoc :: CombineAssoc Int Bool) (Combine f) (Combine g) (Combine h)
Indeed it is not possible or at least not feasible, however you don't really need a test case with such a big argument type as Int!
For smaller types, e.g. Int16, you can just exhaustively try all possible arguments to determine equality. The universe package has a convenient class for that:
import Data.Universe
instance (Universe a, Eq b) => Eq (Combine a b) where
Combine f == Combine g = all (\x -> f x == g x) universe
Then your original check will work, albeit unacceptably slow; I'd recommend changing it to quickCheck (semigroupAssoc :: CombineAssoc Int16 Bool).
In Using the Maybe Monad in “reverse” acfoltzer nicely shows how to use mplus. I want to have a similar effect but with the list of functions as a parameter:
tryFuncs :: [a -> Maybe b] -> a -> Maybe b
...
so a call like
tryFuncs [f, g, h] x
would become possible and do the same as
(f x) `mplus` (g x) `mplus` (h x)
How can one achieve this?
The simplest is to use msum (a list version of mplus) together with map:
tryFuncs fs x = msum $ map ($ x) fs
(In the end, this solution will be identical to Ørjan Johansen's answer, since Maybes MonadPlus is equivalent to the First Monoid's behaviour. It's a neat little application of the a -> b monoid though, which is easily overlooked.)
Conceptually, the function you're looking for is ... mconcat!
tryFuncs' :: Monoid b => [a -> Maybe b] -> a -> Maybe b
tryFuncs' = mconcat
Unfortunately, the default Monoid instance for Maybe is not what you want here ("ignore Nothing, mappend Just contents"), otherwise that solution would have been truly neat.
But there's the First wrapper around Maybe that gives you the "retain first Just" behaviour, so that
-- newtype First a = First (Maybe a)
tryFuncsFirst :: [a -> First b] -> a -> First b
tryFuncsFirst = mconcat
What's left for you is to wrap/unwrap the Maybes to Firsts.
firstify :: (a -> Maybe b) -> (a -> First b)
firstify f = First . f
firstifyList :: [a -> Maybe b] -> [a -> First b]
firstifyList = map firstify
getFirst :: First a -> Maybe a -- Defined in Data.Monoid
So now you can recover your desired function by wrapping-mconcat-unwrapping,
[a -> Maybe b] -> a -> Maybe b
tryFuncs fs x = getFirst (mconcat (firstifyList xs) x)
But how does this work? Well, there are two monoids at work here, First a and Monoid b => (a -> b), and the latter one is where the magic happens. To spell the instance out a little, using <> for mappend,
(a <> b) x = a x <> b c
-- and therefore
mconcat [a,b,c] x = mconcat [a x, b x, c x] -- (1)
So now the above code can be understood:
First-wrap all the input functions to take them from a -> Maybe b to a -> First b, which is the same, but has a different Maybe Monoid instance.
mconcat the list of functions, this uses the Monoid b => (a -> b) instance I just mentioned. All functions in the created list are applied to x, leaving you with a list of First b, which is then concatenated again, just as in (1).
Extract the resulting Maybe value out of the First wrapper again.
New to Haskell, and am trying to figure out this Monad thing. The monadic bind operator -- >>= -- has a very peculiar type signature:
(>>=) :: Monad m => m a -> (a -> m b) -> m b
To simplify, let's substitute Maybe for m:
(>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
However, note that the definition could have been written in three different ways:
(>>=) :: Maybe a -> (Maybe a -> Maybe b) -> Maybe b
(>>=) :: Maybe a -> ( a -> Maybe b) -> Maybe b
(>>=) :: Maybe a -> ( a -> b) -> Maybe b
Of the three the one in the centre is the most asymmetric. However, I understand that the first one is kinda meaningless if we want to avoid (what LYAH calls boilerplate code). However, of the next two, I would prefer the last one. For Maybe, this would look like:
When this is defined as:
(>>=) :: Maybe a -> (a -> b) -> Maybe b
instance Monad Maybe where
Nothing >>= f = Nothing
(Just x) >>= f = return $ f x
Here, a -> b is an ordinary function. Also, I don't immediately see anything unsafe, because Nothing catches the exception before the function application, so the a -> b function will not be called unless a Just a is obtained.
So maybe there is something that isn't apparent to me which has caused the (>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b definition to be preferred over the much simpler (>>=) :: Maybe a -> (a -> b) -> Maybe b definition? Is there some inherent problem associated with the (what I think is a) simpler definition?
It's much more symmetric if you think in terms the following derived function (from Control.Monad):
(>=>) :: Monad m => (a -> m b) -> (b -> m c) -> (a -> m c)
(f >=> g) x = f x >>= g
The reason this function is significant is that it obeys three useful equations:
-- Associativity
(f >=> g) >=> h = f >=> (g >=> h)
-- Left identity
return >=> f = f
-- Right identity
f >=> return = f
These are category laws and if you translate them to use (>>=) instead of (>=>), you get the three monad laws:
(m >>= g) >>= h = m >>= \x -> (g x >>= h)
return x >>= f = f x
m >>= return = m
So it's really not (>>=) that is the elegant operator but rather (>=>) is the symmetric operator you are looking for. However, the reason we usually think in terms of (>>=) is because that is what do notation desugars to.
Let us consider one of the common uses of the Maybe monad: handling errors. Say I wanted to divide two numbers safely. I could write this function:
safeDiv :: Int -> Int -> Maybe Int
safeDiv _ 0 = Nothing
safeDiv n d = n `div` d
Then with the standard Maybe monad, I could do something like this:
foo :: Int -> Int -> Maybe Int
foo a b = do
c <- safeDiv 1000 b
d <- safeDiv a c -- These last two lines could be combined.
return d -- I am not doing so for clarity.
Note that at each step, safeDiv can fail, but at both steps, safeDiv takes Ints, not Maybe Ints. If >>= had this signature:
(>>=) :: Maybe a -> (a -> b) -> Maybe b
You could compose functions together, then give it either a Nothing or a Just, and either it would unwrap the Just, go through the whole pipeline, and re-wrap it in Just, or it would just pass the Nothing through essentially untouched. That might be useful, but it's not a monad. For it to be of any use, we have to be able to fail in the middle, and that's what this signature gives us:
(>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
By the way, something with the signature you devised does exist:
flip fmap :: Maybe a -> (a -> b) -> Maybe b
The more complicated function with a -> Maybe b is the more generic and more useful one and can be used to implement the simple one. That doesn't work the other way around.
You can build a a -> Maybe b function from a function f :: a -> b:
f' :: a -> Maybe b
f' x = Just (f x)
Or, in terms of return (which is Just for Maybe):
f' = return . f
The other way around is not necessarily possible. If you have a function g :: a -> Maybe b and want to use it with the "simple" bind, you would have to convert it into a function a -> b first. But this doesn't usually work, because g might return Nothing where the a -> b function needs to return a b value.
So generally the "simple" bind can be implemented in terms of the "complicated" one, but not the other way around. Additionally, the complicated bind is often useful and not having it would make many things impossible. So by using the more generic bind monads are applicable to more situations.
The problem with the alternative type signature for (>>=) is that it only accidently works for the Maybe monad, if you try it out with another monad (i.e. List monad) you'll see it breaks down at the type of b for the general case. The signature you provided doesn't describe a monadic bind and the monad laws can't don't hold with that definition.
import Prelude hiding (Monad, return)
-- assume monad was defined like this
class Monad m where
(>>=) :: m a -> (a -> b) -> m b
return :: a -> m a
instance Monad Maybe where
Nothing >>= f = Nothing
(Just x) >>= f = return $ f x
instance Monad [] where
m >>= f = concat (map f m)
return x = [x]
Fails with the type error:
Couldn't match type `b' with `[b]'
`b' is a rigid type variable bound by
the type signature for >>= :: [a] -> (a -> b) -> [b]
at monadfail.hs:12:3
Expected type: a -> [b]
Actual type: a -> b
In the first argument of `map', namely `f'
In the first argument of `concat', namely `(map f m)'
In the expression: concat (map f m)
The thing that makes a monad a monad is how 'join' works. Recall that join has the type:
join :: m (m a) -> m a
What 'join' does is "interpret" a monad action that returns a monad action in terms of a monad action. So, you can think of it peeling away a layer of the monad (or better yet, pulling the stuff in the inner layer out into the outer layer). This means that the 'm''s form a "stack", in the sense of a "call stack". Each 'm' represents a context, and 'join' lets us join contexts together, in order.
So, what does this have to do with bind? Recall:
(>>=) :: m a -> (a -> m b) -> m b
And now consider that for f :: a -> m b, and ma :: m a:
fmap f ma :: m (m b)
That is, the result of applying f directly to the a in ma is an (m (m b)). We can apply join to this, to get an m b. In short,
ma >>= f = join (fmap f ma)
I want to convert a function A -> IO B to IO (A -> B), knowing that there is only a finite number of possible values of A. At the moment I just do
convert :: (A -> IO B) -> IO (A -> B)
convert f = do
b1 <- f a1
b2 <- f a2
...
let f' a1 = b1
f' a2 = b2
...
return f'
However I'm not satisfied with the amount of code this requires.
A slightly souped-up version of Joachim's answer, that uses Data.Map to perform the lookup faster. I'll be using the TupleSections pragma as well.
{-# LANGUAGE TupleSections #-}
import Data.Map
import Control.Monad
For added neatness, assume that your Piece type can be given Ord, Bounded and Enum instances.
data Piece = Knight | Bishop | Rook deriving (Ord,Bounded,Enum,Show)
and define the useful enumerate function
enumerate :: (Bounded a, Enum a) => [a]
enumerate = [minBound..maxBound]
Now you can do
convert :: (Monad m, Bounded a, Enum a, Ord a) => (a -> m b) -> m (a -> b)
convert f = do
memo <- sequence [liftM (a,) (f a) | a <- enumerate]
return (fromList memo!)
If you have a list values :: [A], and A has an Eq-Instance, this would work:
convert :: (A -> IO B) -> IO (A -> B)
convert f = do
lookupTable <- sequence [ (\b -> (a,b)) `fmap` f a | a <- values]
return $ (\a -> fromJust (lookup a lookupTable))
As other have noted, if you don’t mind the additional type class requirements for A, you can use maps or hashmaps to speed up the lookup.
Also, from your use-case description, it seems that you are loading static data from a file that comes with your program. Depending on the environment where your final program runs (e.g. guaranteed that the files exist and are not changing), this might be a valid use for unsafePerformIO to simply define A -> B as a top-level function. Alternatively there are ways to include binary blobs in the compile source.
For the sake of completeness, I'll mention that the countable package on Hackage makes this possible by providing the Finite type class. You define something like
instance Finite Piece where
allValues = [Pawn, Knight, Bishop, Rook, Queen, King]
then you have
assemble :: (Finite a, Applicative f) => (a -> f b) -> f (a -> b)
which will specialise to precisely what you need.
Looking at the source, it seems that it uses an association list, so it would be slow if your type was large. Plus, it defines some orphan instances of Foldable and Traversable and Eq (!) for functions, which some may regard as distasteful.
You have function f :: A -> IO B and you have g :: IO A,
you use your convert function with Applicative <*> :: f (a -> b) -> f a -> f b as
fg :: IO a -> (a ->IO B) -> IO B
fg g f = (convert f) <*> g
But you can just use monad (>>=) :: m a -> (a -> m b) -> m b,
fg :: IO a -> (a ->IO B) -> IO B
fg g f = g >>= f
Your function signature permits any function a->m b on input, yet inside you assume a specific range of values. convert is not as polymorphic as the signature seems to declare.
What you have done is created a Map from a to b, then made a pure function that looks up a pure value in that map. Here's why:
What you are asking for is similar to implementing tensorial strength strength :: (Monad m) => (a, m b) -> m (a, b) for a monoidal category (C, ⊗, I) - given a binary relation ⊗ in category C and a monad m, convert a ⊗ m b to m (a ⊗ b). When this is possible for a binary relationship that meets certain requirements, the monad is strong. In Haskell all monads are strong, if tensorial product a ⊗ b is chosen to be a pair (a, b): strength (a, mb) = mb >>= return . (a,). Yet, here you are attempting to do the same for a binary relationship ->. Unfortunately, a -> b cannot be chosen to be a tensor product, because it is not a bi-functor - it is contravariant in a. So what you want cannot be accomplished for arbitrary functions.
What is different in your case, is that essentially you built all pairs (a,b). The amount of code, therefore, can be reduced if you explicitly enumerate all possible pairs of a and b, for example by building a m (Map a b). The others here offered nice sugars exposing "function-like" interfaces, but they are merely lookups in the map.
import Control.Applicative
import Control.Arrow
filter ((&&) <$> (>2) <*> (<7)) [1..10]
filter ((>2) &&& (<7) >>> uncurry (&&)) [1..10]
Both get the same result! However, it is VERY difficult for me to understand. Could someone here explain it in detail?
Let's start with the second, which is simpler. We have two mysterious operators here, with the following types:
(&&&) :: Arrow a => a b c -> a b c' -> a b (c, c')
(>>>) :: Category cat => cat a b -> cat b c -> cat a c
The Arrow and Category type classes are mostly about things that behave like functions, which of course includes functions themselves, and both instances here are just plain (->). So, rewriting the types to use that:
(&&&) :: (b -> c) -> (b -> c') -> (b -> (c, c'))
(>>>) :: (a -> b) -> (b -> c) -> (a -> c)
The second has a very similar type to (.), the familiar function composition operator; in fact, they're the same, just with arguments swapped. The first is more unfamiliar, but the types again tell you all you need to know--it takes two functions, both taking an argument of a common type, and produces a single function that gives the results from both combined into a tuple.
So, the expression (>2) &&& (<7) takes a single number and produces a pair of Bool values based on the comparisons. The result of this is then fed into uncurry (&&), which just takes a pair of Bools and ANDs them together. The resulting predicate is used to filter the list in the usual manner.
The first one is more cryptic. We have two mysterious operators, again, with the following types:
(<$>) :: Functor f => (a -> b) -> f a -> f b
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
Observe that the second argument of (<$>) in this case is (>2), which has type (Ord a, Num a) => a -> Bool, while the type of (<$>)'s argument has type f a. How are these compatible?
The answer is that, just as we could substitute (->) for a and cat in the earlier type signatures, we can think of a -> Bool as (->) a Bool, and substitute ((->) a) for the f. So, rewriting the types, using ((->) t) instead to avoid clashing with the other type variable a:
(<$>) :: (a -> b) -> ((->) t) a -> ((->) t) b
(<*>) :: ((->) t) (a -> b) -> ((->) t) a -> ((->) t) b
Now, putting things back in normal infix form:
(<$>) :: (a -> b) -> (t -> a) -> (t -> b)
(<*>) :: (t -> (a -> b)) -> (t -> a) -> (t -> b)
The first turns out to be function composition, as you can observe from the types. The second is more complicated, but once more the types tell you what you need--it takes two functions with an argument of a common type, one producing a function, the other producing an argument to pass to the function. In other words, something like \f g x -> f x (g x). (This function also happens to be known as the S combinator in combinatory logic, a subject explored extensively by the logician Haskell Curry, whose name no doubt seems strangely familiar!)
The combination of (<$>) and (<*>) sort of "extends" what (<$>) alone does, which in this case means taking a function with two arguments, two functions with a common argument type, applying a single value to the latter two, then applying the first function to the two results. So ((&&) <$> (>2) <*> (<7)) x simplifies to (&&) ((>2) x) ((<7) x), or using normal infix style, x > 2 && x < 7. As before, the compound expression is used to filter the list in the usual manner.
Also, note that while both functions are obfuscated to some degree, once you get used to the operators used, they're actually quite readable. The first abstracts over a compound expression doing multiple things to a single argument, while the second is a generalized form of the standard "pipeline" style of stringing things together with function composition.
Personally I actually find the first one perfectly readable. But I don't expect most people to agree!