I want to understand Haskell better by trying to stretch possibilities of Functor, Applicative, and Monad as much as possible and study how they behave. So in Learn You a Haskell there's an exercise to generate all possible moves for a knight from a given position on chessboard:moveKnight :: KnightPos -> [KnightPos]  moveKnight (c,r) = filter onBoard      [(c+2,r-1),(c+2,r+1),(c-2,r-1),(c-2,r+1)      ,(c+1,r-2),(c+1,r+2),(c-1,r-2),(c-1,r+2)      ]      where onBoard (c,r) = c `elem` [1..8] && r `elem` [1..8]Now I thought to myself, wait a second, I can actually generate that list [(c+2,r-1),(c+2,r+1),(c-2,r-1),(c-2,r+1),(c+1,r-2),(c+1,r+2),(c-1,r-2),(c-1,r+2)] instead of hardcoding it. So this is how I did it with list comprehensions:invert x  | x == 1 = 2  | x == 2 = 1  | otherwise = 0moves = [(c `f` a, r `g` (invert a)) |         c <- [6],         r <- [2],         f <- [(+), (-)],         g <- [(+), (-)],         a <- [1, 2]]-- generates: [(7,4),(8,3),(7,0),(8,1),(5,4),(4,3),(5,0),(4,1)]So the above works correctly, but the code looks ugly. Because every list comprehension is equivalent to a lift, the above code could be rewritten with liftA5:liftA5 :: Applicative g => (a -> b -> c -> d -> e -> f) -> g a -> g b -> g c -> g d -> g e -> g fliftA5 f a b c d e = f <$> a <*> b <*> c <*> d <*> eliftA5 (\c r f g a -> (c `f` a, r `g` (invert a))) [6] [2] [(+), (-)] [(+), (-)] [1,2]My questions are:Are there any way to generate the above list in a more elegant way?How to generalize it to arbitrary possible permutations? (So that my code could parametrized and not fixed to just numbers 1 and 2, or just + and -, etc)What intuitions, insights and lessons can I get from this exercise?I know that this is a massively contrived situation, but they serve me as an exercise to understand the language better.
Applicative permutation to generate knight moves
haskell;combinatorics;chess;monads