You've used a common Haskell pattern here -- take a list, turn each element into a pair containing some extra information, process the "augmented" list, and strip off the extra information. Combining this pattern with point-free style, though, doesn't lead to particularly clean code because it's cluttered with constructs (like
map (___ . fst) and
filter (uncurry ___)) that aren't really part of the core algorithm but are still necessary to "interface" with your pair representation for the "augmented" elements.
I think you can get a more readable version by switching to list comprehension syntax, which let's you simultaneously simplify the higher-order function "sugar" (making
filter implicit) and conveniently pattern match on the pairs (avoiding
uncurry), so you get something like this:
captcha :: String -> Int
captcha xs =
sum [ digitToInt x | (x, y) <- zip xs (rotate xs), x == y ]
where rotate (x:xs) = xs ++ [x]
I think this is easier to puzzle through than the point-free version.
With respect to your specific questions about style, yes it's largely true that Haskell programmers tend to write chains of composed functions in right-to-left order of application:
foo . bar . baz $ xxx
foo $ bar $ baz $ xxx
rather than left-to-right:
xxx & (baz >>> bar >>> foo)
xxx & baz & bar & foo
On the other hand, chains of monadic or applicative actions are typically written in left to right order of action, and the styles are often mixed in the same code (e.g., monadic parsers written in applicative style), so I think Haskell programmers are used to seeing both styles.
Personally, I've found that writing a long chain of compositions in left-to-right order may make it easier to explain the algorithm in the comments:
= str & zip (drop 1 . cycle $ str) -- augment "d" w/ following digit
& filter (uncurry (==)) -- keep "d" when followed by "d"
& map (digitToInt . fst) -- convert "d" to int
& sum -- sum them all up
This particular example doesn't really benefit, but you can imagine other case where this step-by-step commenting style would be helpful.
As for using "divided code", as you call it, I think this can be quite helpful, but using very long variable names and elevating them to top-level definitions doesn't really do justice to the style. Also, pulling the
map into the helper function doesn't really aid readability, because those aren't the parts that are potentially hard to follow. Instead, I think something more like:
result' :: [Char] -> Int
result' str =
sum $ map value $ filter equalPair $ zip str (rotate str)
value (x,_) = digitToInt x
equalPair (x,y) = x == y
rotate (x:xs) = xs ++ [x]
might be reasonable, though it's probably excessive in this small example.
Also, note that while point-free style is popular among many Haskell programmers, definitions like your:
transformCharPairsToInts = map $ digitToInt . fst
keepPairsWithSameChars = filter $ uncurry (==)
might more typically be written in pointed style:
transformCharPairsToInts = map (\(x,_) -> digitToInt x)
keepPairsWithSameChars = filter (\(x,y) -> x == y)
For example, if you browse through the source of
Data.Foldable which is mostly pretty idiomatic, you'll see definitions like:
null = foldr (\_ _ -> False) True
length = foldl' (\c _ -> c+1) 0
null = foldr ((const . const) False) True
length = foldl' (flip (const succ)) 0