How can I mentally infer the type of the composition of these functions?

a :: [[Char]] -> [([Char],Int,Int)]
b :: [Int] -> [(Int,Bool)]
c :: [([Char],Int,Int)] -> ([Char],Int)
d :: [(Int,Bool)] -> [[Char]]

That is, what is the type of:

c . a . d . b

The first thing I’d do is rename the tricky types:

a :: [String] -> [(String,Int,Int)]
b :: [Int] -> [(Int,Bool)]
c :: [(String,Int,Int)] -> (String,Int)
d :: [(Int,Bool)] -> [String]

and then introduce some type synonyms:

type A = [String]
type B = [(String,Int,Int)]
type C = [(Int,Bool)]

Now we can rename the complex types to a more symbolic form:

a :: A -> B
b :: [Int] -> C
c :: B -> (String,Int)
d :: C -> A

and now we can see how to combine the pieces. It’s a little jigsaw puzzle!

b :: [Int] -> C

so there’s only one option to compose with b, namely

b :: [Int] -> C
d :: C -> A

composing these, and we hide the intermediate result:

d . b :: [Int] -> A

now, we need something that takes an A:

a :: A -> B

So the result of something composed with a, will be a value of type B:

a . d . b :: [Int] -> B

and finally, something that takes a B:

c :: B -> (String,Int)