Why do you pass objects instead of primitives?
This is a type of question I would expect on programmers@se; nevertheless..
We pass objects instead of primitives because we seek to establish a clear context, distinguish a certain point of view, and express a clear intent.
Choosing to pass primitives instead of full-fledged, rich, contextual objects lowers the degree of abstraction and widens the scope of the function. Sometimes those are goals, but normally they are things to avoid. Low cohesion is a liability, not an asset.
Instead of operating on related things through a full-fledged, rich object, with primitives we now operate on nothing more than arbitrary values, which may or may not be strictly related to each other. With no defined context, we do not know if the combination of primitives is valid together at any time, let alone right now in the current state of the system. Any protections a rich context object would have provided are lost (or duplicated in the wrong place) when we chose primitives at the function definition when we could have chosen a rich object. Further, any events or signals we normally would raise, observe, and act on by changes to any values in the rich context object are harder to raise and trace at the right time they are relevant when working with simple primitives.
Using objects over primitives fosters cohesion. Cohesion is a goal. Things that belong together stay together. Respecting the natural dependency of the function on that grouping, ordering, and interaction of the parameters through a clear context is a good thing.
Using objects over primitives does not necessarily increase the kind of coupling we are worry most about. The kind of coupling we should worry most about is the kind of coupling that occurs when external callers dictate the shape and sequence of messaging, and we further sell-out to their prescribed rules as the only way to play.
Instead of going all in, we should note a clear 'tell' when we see it. Middleware vendors and service providers definitely want us to go all in, and tightly integrate their system to ours. They want a firm dependency, tightly interwoven with our own code, so we keep coming back. However, we are smarter than that. Short of being smart, we may at least be experienced enough, having been down that road to recognize what is coming. We do not up the bid by allowing elements of the vendors code to invade every nook and cranny, knowing that we cannot buy the hand since they are sitting on a pile of chips, and to be honest, our hand just is not that good. Instead, we say this is what I am going to do with your middleware, and we lay down a limited, adaptive interface that allows play to continue, but does not bet the farm on that single hand. We do this because during the next hand we may face off against a different middleware vendor or service provider.
Ad hoc poker metaphor aside, the idea of running away from coupling whenever it presents itself is going to cost you. Running from the most interwoven and costly coupling is probably a smart thing to do if you intend to stay in the game for the long haul, and have an inclination that you will play with other vendors or providers or devices that you can exert little control.
There is a great deal more I could say on the relationship of context objects versus use of primitives, for example in providing meaningful, flexible tests. Instead, I would suggest particular readings about coding style from authors like Uncle Bob Martin, but I will omit them for now.