Well, the answers @chi and @Willem Van Onsem are great for variety use only. I would like to mention that understanding lists is just syntactic sugar for this monadic work:
Prelude> [(x,y) | x <- [1,2], y <- ['a', 'b']] [(1,'a'),(1,'b'),(2,'a'),(2,'b')]
However, since lists are also instances of the application functor class in Haskell, another good way to do this without touching the monad instance would be:
Prelude> (,) <$> [1,2] <*> ['a','b'] [(1,'a'),(1,'b'),(2,'a'),(2,'b')]
I would like to clarify the latter a little,
(,) is actually a function that takes two arguments and with a type signature; a -> b -> (a,b) . Whereas <$> is an inline form of fmap . Therefore, applying (,) <$> [1,2] , we obtain an applicative functor (list of functions), such as [(,) 1, (,) 2] . Now we can apply the applicative operator <*> , which has a signature of type Applicative f => f (a -> b) -> fa -> fb In a signature of type f it should not be confused with a function. It denotes a functor, which is the type of [] (list) here. Which says that <*> parses the contained functions (functions) from the functor and applies them to the provided functor containing the value (s) to return the result of these applications to the same type of functor. Therefore, it is obvious that since list applications are defined by all elements one to one, the result is a list of tuples of all combinations.