When you have a function where not every possible input value can be mapped to one of the possible output values, you have three options: Allow fewer inputs, allow more outputs, or declare that your function is partial rather than total and just isn't well-behaved sometimes.
The first is arguably the most flexible, but often involves some awkward re-shaping ...
To address your title question ("Is it wrong..."), no, I wouldn't say that it's wrong. Using more top-level functions is nearly always a great way to decompose your problem into smaller, easier to digest bits that are simpler to solve. That said the split that you chose to make doesn't meet that goal. Your functions are tightly coupled in that ...
I'm a little out of practice with haskell, but I'm quite fond of it. I want to address your "extra" question first.
wich one of the 2 version is more readable for an haskell programmer, and why ? [sic]
Haskell programmers aren't a special magic kind of people. They tend to take the benefits of brevity more seriously than folks in love with python,...
I think your use of do notation in the splitWord function is bad and I think your code is even correct by accident. Let me add parentheses to highlight it:
splitWord s = do
let rest = splitWord (tail s)
let first = (head s) : (head rest)
(return first) ++ tail rest
Most people, including me, will assume the last return statement in a do ...
I wouldn't worry about using mutability here. Sometimes it pays to just be straightforward and stick closely to something that you know works.
You're not using ScopedTypeVariables.
As far as the interface, Costs has no reason to be a data type. It is clearer from the user's perspective to say simply
type Costs a = Operation a -> Int
-- replacements for ...
I would use an interleave helper:
interleave :: [a] -> [a] -> [a]
interleave (x:xs) ys = x : interleave ys xs
binaryRule :: [Integer]
binaryRule = interleave [0,0..] (map (+ 1) binaryRule)
Or golfed: let(a:b)#c=a:c#b;a=[0,0..]#map(+1)a in a
First things first: great work on adding a type signature on every function. Now, let's see how we can improve the code.
Use interesting case first, others later
When we pattern match in a binding or in a case expression, it's often easier to start with the interesting case first and then handle the Error cases:
decodeTeacher :: PersonDecoder
Is it intended that insertAt 999  [1..10] inserts 999 at index 1, but insertAt 999  [1..10] does nothing. Shouldn't it insert a value at index 0?
Also, it looks like certain other insertions don't work correctly:
> insertAt 999 [2,5,6,7] [1..10]
Note that this inserts only at indices 2, 5, and 7 and appears to ...