4 ms·
I find it _much_ more likely that shoehorning `verb(noun1, ...)` to accept `noun2` will result in sub-optimal design than factoring out an interface (`verb(...)
by kingdomcome50 7y ago
I find it _much_ more likely that shoehorning `verb(noun1, ...)` to accept `noun2` will result in sub-optimal design than factoring out an interface (`verb(...)`) that both `noun1` and `noun2` will implement. A verb's meaning/implementation can shift dramatically depending on the noun to which it is attributed. E.g. `validate(user)` is materially different than `validate(order)`.
Any argument predicating on "massive rewrites" and "huge efforts" is a straw-man. A programmer can model dependencies incorrectly using either approach above. Expanding a discriminated union (to include `noun2`) will lead to the exact same amount of refactoring as extracting an interface. We know this because it _is_ a matter of semantics.
The discussion at hand is about comparing/contrasting the dependencies between object-methods (`noun.verb(...)`) and stand-alone functions (`verb(noun, ...)`). From the POV of the method/function, the dependencies are the same. Whether `noun` is explicitly passed as an argument or compiled into `this` does not change what information is available to the function body (i.e. it's dependencies).
- crimsonalucard 7y ago>I find it _much_ more likely that shoehorning `verb(noun1, ...)` to accept `noun2` will result in sub-optimal design than factoring out an interface (`verb(...)`) that both `noun1` and `noun2` will implement. I am not talking about the case you describe above. Get rid of Union types, that is a whole different thing. I am talking about a function that does not need to be shoe horned that can obviously be used for members of both noun1 and noun2 but due to a design decision the function is tied to noun1. Let me give you a concrete example that is trivial. The example is so trivial that it probably won't happen in the real world but you can see how for concepts that are more complex it can happen all the time. Imagine I have an app with a class in it called SortedListOfPeople. It would make sense to put a sort method on this class. Now imagine that this app runs for a bit then you add a feature which involves a class called GroupOfCats. The feature is very far away from and completely different from SortedListOfPeople. Then one day you get the task to turn GroupOfCats into SortedGroupOfCats. Suddenly you got a problem. How do you re-use the code from sorted list of people without the sorted list of people? How do you use it with a different context at all? Not being able to reuse code is NOT a semantic problem. It is an intrinsic design problem. Your genius solution above (Note how I call your solution genius instead of straw-man) is two implementations of the same concept. This is obviously more genius than one function. Sortable types is an obvious example, but think about how often this problem occurs in OOP for more complicated concepts. You end up implementing things multiple times all over the place. If the sort function was a stateless function operating on a sortable interface parameter you would not have this problem. The only thing you need to implement for GroupOfCats is to give it a sortable interface rather than reimplement sort for everything that needs to be sorted. Actually if you think really hard about OOP and the sort function. How would you make a generic sort function that can be reused across all contexts while following the rules of OOP? You cannot use static classes and the function cannot take parameters... as that would imply a stateless function. The sort function must operate on the classes own state. How can you then share the sort method across two or more classes in OOP? The answer is inheritance. Put the sort method in the base class. How would you share the sort function with classes that already have ancestors? Multiple inheritance. Too bad everybody hates these two concepts so they resort to garbage syntax that basically turns the class into a namespace so they can start using functions as if they weren't tied to some intrinsic state.
- kingdomcome50 7y agoYou have simply contrived a poor design, then pointed out its flaws. Not only that, you are _constraining_ the solution space (no static methods and must be parameter-less). That sounds like a straw-man to me. There is nothing inherent in OOP that mandates a developer choose your design. Furthermore, you are comparing a perfectly factored functional design to a poorly factored OOP design. I can just as easily assert that `sort(people)` cannot be factored into `sort(comparable)` because "clearly" the body of `sort(people)` is dependent on some property of `people` that is not present on `comparable`. Contravariance cannot just be assumed. Said another way, every function is tied to the entire public surface of the data to which it is provided (at least in theory). So unless `people` just happened to have exactly the same interface as `comparable` already, a new, more generic, `sort(comparable)` function would be necessary. The solutions are roughly equivalent in either paradigm: move the behavior into a more generic container. Where composition can be used instead of inheritance in tandem with double dispatch, for OOP, if the public surface of SortedGroupOfCats must include a `sort()` function.
- crimsonalucard 7y ago>You have simply contrived a poor design I contrived a poor design that is a reflection of what can happen. There is no design theory making all designs perfect therefore many isomorphic mistakes in my design happen all the time uncontrived. >Not only that, you are _constraining_ the solution space (no static methods and must be parameter-less). I constrain it to prevent you from creating a function instead of a method. A function is outside of OOP a method is 100% OOP. a static class without state is equivalent to a namespace holding a bunch of functions. If I didn't constrain you you'd be proving my point by switching to using a regular function instead of OOP. >There is nothing inherent in OOP that mandates a developer choose your design. That is the problem. There is something inherent in functional programming that mandates the developer to never be able to make the previous design choice. If all your functions are stateless there is NO way you can even contrive the poor design I contrived earlier. That is the secret. Less expressivity is less ways to screw up. Imagine a programming language where the syntax only allows you to design your programs in the best possible way... Functional programming is closer to this goal than OOP. >I can just as easily assert that `sort(people)` cannot be factored into `sort(comparable)` It's better to factor something to be more general than to write 10 flavors of the same thing. How hard it is doesn't matter here, rewriting sort to solve this problem is better than OOP where you have to write another version. That is all I am arguing for. >Contravariance cannot just be assumed. The entire essence of my argument is that you discover this property in two random places, but you cannot apply a function in both places because the function is tied to another context. >a new, more generic, `sort(comparable)` And this is better than two specific flavors of sort. Let's scale this up to an app that's been around for 5 years.... in this case One generic sort is better than 20 different flavors of sort. >The solutions are roughly equivalent in either paradigm: move the behavior into a more generic container. But like I've been saying you can't just move the method in OOP you have to change the object hierarchy. If the method is to be used in another unrelated object hierarchy than the hierarchies have to be merged via multiple inheritance. All I want is the function, why am I bringing the whole object ancestral structure with it? "I think the lack of reusability comes in object-oriented languages, not functional languages. Because the problem with object-oriented languages is they’ve got all this implicit environment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana and the entire jungle." -joe armstrong