Since you only use call the nonpartial case of `tlTrns` and only use it once, you can inline it. Pattern matching is better than `head`/`tail`/`fst`/`snd`. `Maybe`'s `fmap` can be used again. -- | Given a 2D list, select a list via index, -- transfer the last item of its immediate non blank -- left neighbour to its head. pull :: Int -- based 1 index -> [[a]] -- 2D list -> Maybe [[a]] -- result after operation pull _ [] = Nothing -- empty list / out of bounds pull 1 _ = Nothing -- no left neighbour pull 2 ([]:_) = Nothing -- empty left neighbour pull 2 (xs:ys:yss) = Just $ let (pl, pr) = splLst xs in pl:(pr:ys):yss -- base case take from left neighbour pull n (xs:[]:xss) = fmap (\(p:ps) -> p:[]:ps) (pull (n-1) (xs:xss)) -- carry over due to blank mid pull n (xs:xss) = fmap (xs:) (pull (n-1) xss) -- recurse tail You can make `splLst` nonpartial by using `Maybe`. Then it turns out that `Data.List.Extra` already implements that and calls it `unsnoc`. Using it, you can eliminate another case from pull. import Data.List.Extra (unsnoc) -- | Given a 2D list, select a list via index, -- transfer the last item of its immediate non blank -- left neighbour to its head. pull :: Int -- based 1 index -> [[a]] -- 2D list -> Maybe [[a]] -- result after operation pull _ [] = Nothing -- empty list / out of bounds pull 1 _ = Nothing -- no left neighbour pull 2 (xs:ys:yss) = fmap (\(pl,pr) -> pl:(pr:ys):yss) (unsnoc xs) -- base case take from left neighbour pull n (xs:[]:xss) = fmap (\(p:ps) -> p:[]:ps) (pull (n-1) (xs:xss)) -- carry over due to blank mid pull n (xs:xss) = fmap (xs:) (pull (n-1) xss) -- recurse tail