I made a slightly different algorithm:
def share(available, members):
# find across how many members we must distribute and what the total sum of those values is
total = available
for idx, member in enumerate(members):
total += member
count = idx+1
if (idx >= len(members)-1):
break
if (total / (idx+1) <= members[idx+1]):
break
# distribute the total value among 'count' first value
distr = []
for member in members[0:count]:
target = total//count
diff = target - member
distr.append(diff)
total -= target
count -= 1
return distr
It works in two steps. First step calculates across how many members the available value needs to be distributed along with total sum of all those values after being distributed.
In the second step, the differences are calculated based on value that would be if it was distributed.
But handling the edge cases complicates the whole algorithm.
The whole program is going to end up contained in the IO monad, basically.
That's the bit where I think you're not seeing it from the Haskellers' perspective. So we have a program like this:
module Main
main :: IO ()
main = do
xmlData <- readFile "input.xml"
let jsonData = convert xmlData
writeFile "output.json" jsonData
convert :: String -> String
convert xml = ...
I think a typical Haskeller's take on this would be that convert
, the pure part:
- Is probably the bulk of this program, and by far more complicated than the
IO
parts;
- Can be reasoned about and tested without having to deal with
IO
at all.
So they don't see this as convert
being "contained" in IO
, but rather, as it being isolated from IO
. From its type, whatever convert
does can never depend on anything that happens in an IO
action.
When the code is actually executed, everything after reading the file depends on that file’s state so will still suffer from the same state-related bugs as the imperative implementation, so have you actually gained anything, as a programmer who will maintain this?
I'd say that this splits into two things:
- When the program runs, the value of the argument to
convert
depends on the state of the file.
- But what the
convert
function does, that doesn't depend on the state of the file. convert
is always the same function, even if it is invoked with different arguments at different points.
This is a somewhat abstract point, but it's really key to what Haskellers mean when they talk about this. You want to write convert
in such a way that given any valid argument, it will produce a correct result for that argument. When you look at it like that, the fact that reading a file is a stateful operation doesn't enter into the equation; all that matters is that whatever argument is fed to it and wherever that may have come from, convert
must handle it correctly. And the fact that purity restricts what convert
can do with its input simplifies that reasoning.
So if convert
produces incorrect results from some arguments, and readFile
feeds it such an argument, we don't see that as a bug introduced by state. It's a bug in a pure function!
Best Answer
In terms of official documentation, per the Programming FAQ:
Elsewhere in the docs:
where the footnote adds:
This is consistent with the rest of Python's assignment model, for example:
is similar to:
in that the object assigned to the name
x
is also assigned to the namey
(albeit only withinsomefunc
in the former).