But let's get that out of the way. I appreciate your second reply here.
>In your example, logFilteredWithDateToFile accepts a file. That means, if a function wants to log an error, it needs to accept a file somewhere to pass to the log function (for example, we want the sockets/files to be opened once and be shared across several calls).
This is the intention of how the library works. It actually does promote the sharing of files and sockets. See trivial example below:
s = WrappedSocket(...)
logStringToSocket(s, "hello")
logStringToSocket(s, "world")
See. Socket is shared across several calls. The only thing that's a bit of the eyesore is that extra socket being passed around. But this is a problem that can't be gotten rid of. You either pass the socket all over your code or in the OOP example you pass an instance of a logging class around.The only benefit of OOP is that the logging class manages the lifetime of the socket. If calls socket.close() for you during destruction. That's it.
>So if function A depends on function B which depends on function C, then function A will need to explicitly state all the dependencies of B and C in its function signature.
No you don't. This is not mandatory. I expose only parameters that are needed by the user and the API. If I want to give the user the ability to filter I SHOULD expose two string parameters, s: str, match: str. because without it the user can't do anything. But I don't have to, see below:
logHelloWorldWithDate() = print(appendSuffix("hello world: ", time.date().to_string()))
appendSuffix has two parameters required. But I expose zero parameters.>We end up with a very complex function signature of A where we have to list all possible dependencies of every function it makes use of recursively
There's no recursion going on in my example and none of the logging functions manage any state. Again the function signature is as complex as you want it to be. If there are OTHER required parameters like "socket" and you want to reduce the complexity of the parameters coming in, you can wrap say a socket and a file in a single struct. Not a huge deal either way... Often this variable is called ctx or context, you see it in languages that tend to be more against OOP like golang.
For C++ you can have state managers. Often this exists in the form of things like ThreadPool or **Manager objects. These manager objects are more of a necessary evil though because they lack the huge compositional ability of my example due to the fact that they unionize state and function into a single primitive. Managers overall segregate state away from the rest of your program such that you can have huge compositional ability in the rest of your program as shown in the example above.
>As a client, all I wanted was to ask a function to do one simple thing but now I have to manage files, sockets and god knows what else just because somewhere deep inside someone uses them.
Yes as a client you do need to manage this. I mentioned this problem with the lifetime of socket or file. From the perspective of say a programmer who uses garbage collection it's the same amount of crap being passed around.
s1= socket()
s2=socket()
logWithSocket(s1, "hello")
logWithSocket(s2, "world")
versus the oop version l1 = LoggerThatOwnsASocket()
l2 = LoggerThatOwnsASocket()
l1.log("hello")
l2.log("world")
Either you're passing a socket everywhere or your passing the log manager everywhere. If you want something that logs to both socket and a file (multiple dependencies) simple package both of those dependencies in a single struct. This struct can be used for both the OOP version and the non-OOP version. So from the perspective of a programmer who uses garbage collection, the problem presented by both examples here is the SAME, but the 1st example is superior because it is MORE composable and modular.>As a client, all I wanted was to ask a function to do one simple thing but now I have to manage files, sockets and god knows what else just because somewhere deep inside someone uses them.
Either you're managing a bunch of LogManagers with all the dependencies, Or you're managing a struct with all the dependencies. The problem is the SAME.
>And then in a new version we decide we don't want to log to a file, we want to use a third-party library. Now we need to change signatures of A, B and C, and modify our code at every call site.
Explain this problem with an example. It's too vague. What is the nature of the third party library, what is it replacing?
>Is this what you are proposing?
I've hit all your points pretty thoroughly sentence by sentence. The only thing I haven't resolved is the third-party library thing. Feel free to explain that part and also offer counter arguments to my arguments.