I agree with Xeo to the extent that <|> has the effect of ignoring the empty list in [] <|> [1,2,3], but under the hood the compiler might be concatenating the empty list with the non-empty list. I have ghci set up to automatically display the types of what it prints. Here are some results which suggest that, in the case of lists, <|> and ++ might be equivalent:
λ: [] <|> [3,4]
[3,4]
it :: Num a => [a]
λ: [] ++ [3,4]
[3,4]
it :: Num a => [a]
λ: [1,2] <|> [3,4]
[1,2,3,4]
it :: Num a => [a]
λ: [1,2] ++ [3,4]
[1,2,3,4]
it :: Num a => [a]
λ: :t (<|>) [1,2] [3,4]
(<|>) [1,2] [3,4] :: Num a => [a]
λ: :t (++) [1,2] [3,4]
(++) [1,2] [3,4] :: Num a => [a]
λ: [1,2] <|> [] <|> [3,4]
[1,2,3,4]
Well, <|> has the effect of ++ in the above examples, but there is more than that going on. At https://hackage.haskell.org/package/parsec-3.0.0/docs/Text-ParserCombinators-Parsec-Prim.html we read this about <|>:
"This combinator is defined equal to the mplus member of the MonadPlus class and the (Control.Applicative.<|>) member of Control.Applicative.Alternative."
Turning to the Hackage "Control.Monad" discussion we read:
"msum :: MonadPlus m => [m a] -> m a Source" followed by
"This generalizes the list-based concat function."
The answer to the question, "What does <|> do?", is rooted in understanding what Haskell compilers do when they encounter <|>, say in "[] <|> [1,2,3]"? Do they just ignore []? Do they execute the concatenation routine? There are steps involved in just figuring out what do do with this strangely overloaded operator. It's no wonder that programmers are mystified by it, and settle for knowing little more than the effect of employing it in special use cases.
<|> is useful for jumping out of recursive loops as soon as a condition is satisfied. Here's some code to suggest how this works:
λ: Nothing <|> Just 6 <|> Just 7
Just 6
it :: Num a => Maybe a
λ: Nothing <|> Just [1,2] <|> Nothing <|> Just [3,4]
Just [1,2]
it :: Num t => Maybe [t]