4 ms·
Could you elaborate on '...when you write data structures, don't give them member function logic,' please?
by dragonker 6y ago
Could you elaborate on '...when you write data structures, don't give them member function logic,' please?
- mlthoughts2018 6y agoI mean heap_sort(my_array) is strictly better than my_array.heap_sort() The main reason is that fluent interfaces (the second format) makes it much harder and require more code to do patching / mocking / dependency injection, since you need to carefully patch around instance creation and ensure the patching only applies to specific instances during tests where some objects need patching and others don’t. The first interface above just needs the simple top level name “heap_sort” patched or mocked with easy control of the scope. The act of array construction and the knowledge of internal array data is completely isolated away from the act of sorting. As you start adding parameters to the mix and involve highly stateful internal operations, start mixing with inherited methods, class methods and static methods, and as you chain the fluent interface (eg foo().bar().baz()) all these effects get worse and test complexity becomes unberable. Basically it boils down to function composition. f(g(x)) is simply a better way to structure programs than x.g().f() And if you’re worried about writing f() and g() once and not rewriting an implementation, there are many ways to achieve this without classes - the best way being use of GADTs to define exhaustive pattern matching in f() or g() that allows custom implementations matched on basically simple record types or named tuples, instead of defining the custom implementation in overridden inheritance methods. You don’t need pure functional programming or compiler enforcement to do this either. It is how I’ve structured large C and Python programs in business settings for many years.
- rileymat2 6y agoThe promoters of oo would not disagree with this example. Clean Code goes into a lot of subtle detail about data structures v. objects. https://blog.cleancoder.com/uncle-bob/2019/06/16/ObjectsAndDataStructures.html https://blog.cleancoder.com/uncle-bob/2019/06/16/ObjectsAndD...
- andi999 6y agoExcellent pointsm What does GADT stand for though?
- kqr 6y agoGeneralised algebraic data types. You might come across it as an extension to the Haskell type system -- making it even more expressive.
- deleted 6y ago[deleted]
- kqr 6y agoBut if your language uses lexical scoping (like nearly all of them do) then the procedure call will be incredibly hard to mock in any code that isn't prepared by taking it as an argument. As an example I might want to test code that internally calls heap_sort but replace the heap_sort with a test double (sounds like a silly example but maybe the heap_sort calls out to a heap sorting microservice or something equally dumb.) With the procedure call and lexical scope, that's not going to happen. With the instance method, that might happen if I pass in an array with a different heap sorting implementation. ---- To be clear: I'm all for functional composition over mutation through interface methods. But it's not, primarily, about syntactic convenience. It's about immutability and referential transparency. The example you picked is bad for another reason too: heap_sort is not a fundamental method to arrays that should be part of its core definition. So no, clearly it shouldn't be an instance method, but for different reasons!
- mlthoughts2018 6y agoI think it’s the opposite. It’s not going to happen with the instance method version, because patching instance creation is much harder. Patching within the scope of heap_sort is much simpler. I actually gave a real example of this in another comment not long ago: https://news.ycombinator.com/item?id=23573358 https://news.ycombinator.com/item?id=23573358
- Izkata 6y ago> fluent interfaces (the second format) > and as you chain the fluent interface (eg foo().bar().baz()) Aside: A lone method call isn't a fluent interface (though I suppose it could be part of one), so the naming here is a bit misleading. A fluent interface is a particular type of method chaining, so the second example may or may not be one depending on what foo/bar/baz actually do.
- ivalm 6y agoSo would you say there is never a use case for something like builder pattern? I feel like at least for readability builder patterns can be very nice (Take blah then do a then do b then do c then do d then return the result which is if same type as blah). The functional approach basically moves “blah” all the way to the right. In terms of mocking builder methods are just as simple (since they return the class rather than modify in place). (Concretely I am think of something like transformations on dataset, especially if you do different transformations/in different order at different places in code)
- mlthoughts2018 6y agoThe trouble with builder pattern is that it basically introduces a new concept (the builder class and any class hierarchy of builders), purely for the sake of constructor functions. The constructors that live in builder classes can just be separate module functions, attached to no class, and use decorators or other non-object-oriented types of metaprogramming to add specializations. It’s very nice to encapsulate complex creation in a helper function, but builder pattern just takes this idea and adds unnecessary code bloat.
- tigershark 6y agoIn C# you can simply use the extensions methods to have the syntactic sugar of the fluent interface and the flexibility of the static functions.