What happens in your Clojure example if another is also attacking rage9? You say that rage9 doesn't change and you've created a weakened-rage9 to capture it's new state, but what happens when multiple attacks occur? HP must drop based on it's current state and not it's original state. How does that get managed: could you show me how you would handle a series of attacks on rage9?
Let's pretend like we have a multi-threaded battle simulation that creates 100 humans and makes them duke it out with our attack/suffer functions until there is only one human remaining. The final champion is then printed to the screen and the program is finished.
Every new attack is quickly run in a new thread because obviously in a real battle humans don't just queue up like a Power Rangers fight sequence.
Your question of "What happens when multiple attackers attack the same human at the same time?" needs to be answered regardless of the language we use.
What Clojure brings to the table here is that it has first-class support for multi-threaded coordination/synchronization.
(edit: an example more true to my scenario would be if human was a hashmap {:hp 100 :shield nil}. What if, after the first attacker, the human picks a shield off the ground becoming {:hp 100 :shield "Thorny Buckler"} that would make her deal damage back to the next two attackers? You need to coordinate this. In Clojure, if you (send-off human attack), your attack function would just check the :shield value of the human whenever it's finally run).
In Ruby, you'd have to wrap the shared resource (the attacked human) by hand or use some other semantics. You have to ensure that the mutated value of the human will not surprise other threads that may hold a reference to that human. It's the classic surface area of problems that multi-threaded programming introduces.
In Clojure, threads hold an immutable state of a human at a point in time and you can replay the series of transformations against them to see how they died. The values won't change out from under them. If you have another thread that does nothing but print the battle scene (HP of every human on the field) every second but the process takes 3 seconds, then it's not an issue because it holds an immutable snapshot from that point in time. And if a thread needs the real-time value of a human, then it deliberately dereferences @human which is a thread-safe way to access the wrapped value of the agent.
This is a system you'd have to deliberately set up in memory-location-/place-orientated languages and systems. But Clojure's immutable-by-default paradigm offers this for free without having to constantly ensure you're not stepping on your toes.
So, to answer your question more succinctly: In Ruby, you tend to coordinate *mutations* against rage9's memory location. In Clojure, Clojure is coordinating *transformations* against a rage9 and mutating the memory location behind the scene for you.
(Not knowing Ruby) but could the Ruby example be handled better by the attack function returning an int and then rage9.hp = mattseh.attack(rage9)
That's essentially what we currently have. It might just be confounding because it's wrapped in two methods.
`rage9.suffer(10)` is basically `rage9.hp = damage`.
So `mattseh.attack(rage9)` is really just `rage9.hp -= mattseh.damage`.
The problem here is the same problem you asked me: What happens in the Ruby example when fraggler.attack(rage9) and mattseh.attack(rage9) happen at the same time (two different threads)?
In this case, there's a race to mutate rage9. And what if, in another thread, rage9.drink_flask also occurs at the same time? When you run the program, rage9 might chug his flask in time to heal before one of the attackers deals the killing blow. But the next time you run the program, perhaps both attack threads execute before rage9 has time to suck the magical nectar from his flask.
Of course, Ruby, Python, Java, etc. have semantics to deal with this. But it's opt-in and tedious.