3 ms·
I like the definition “Simple means not tangled.” But that definition isn’t objective, it depends on what you think is tangled. The author claims “Simplicity i
by dahart 5y ago
I like the definition “Simple means not tangled.” But that definition isn’t objective, it depends on what you think is tangled.
The author claims “Simplicity is objective because - if we can distinguish them - we can count how many simpler elements are there, tangled together.” This admits that objectivity depends on distinguishing what the elements are, which is often subjective. So this claim can be seen as false in the sense that you can’t make something objective out of subjective parts, or true in the sense that it’s trying to punt by treating the whole as a separate thing from the elements that comprise it.
One good example of subjective elements is the discussion on state - “State is never simple because it complects - tangles - time and value.” I agree with this, but it’s important to note that state does not encode time explicitly. Time and state are byproducts of assignment. We can remove the notion of time (assignment) from some bit of code and make the code functional. But we can’t move the notion of time to an element of it’s own. And if we could, and then compose it with an element of value, we’d end up right back where we started with some state.
Generic discussions on state can be hard to parse too since there doesn’t exist a computer program without state. We move it around, organize it in various ways. We hide it with interpreters and compilers. But it’s always there. At a high level, there’s no way to detangle time and value when users and user input are involved. A feedback-response cycle often is state at a fundamental level (unless your program is idempotent, such as limited to database lookups). You can’t create anything in a stateless system.
The “What’s in your Toolkit” chart is pretty bad as a supporting argument for what “simple” means, IMO. Methods vs namespaces functions does not have a meaningful difference in this context; namespacing is an organizational tool, it doesn’t simplify things or protect you from complexity. Data vs Syntax is not a choice in any programming language that exists, all of them have both. Actors vs Queues doesn’t make sense to me, I don’t understand what they were trying to say, and suggesting queues are simpler than actors seems backwards from a game programming perspective. Consistency and Inconsistency are things you can have in any programming language at any level of complexity.
- greymalik 5y agoThe chart comes from Rich Hickey’s Simple Made Easy presentation. Watching that will provide missing context that may answer some of your questions. https://www.infoq.com/presentations/Simple-Made-Easy/ https://www.infoq.com/presentations/Simple-Made-Easy/
- raspasov 5y agoTangled is that which measurably interacts with something else for any practical purposes at the current level of analysis. If there's a function A that does not call function B (and vice versa), they are not tangled. They are separate (assuming the functions are pure, no global shared state, etc). Now we can go down the physics rabbit hole and start arguing that everything is tangled at some level but I don't think that's useful for a discussion at this level of analysis. I think we can stick to the definition that if function A does not call function B, they are completely separate for the purpose of this discussion.
- dgb23 5y agoAppreciate the answer! > But we can’t move the notion of time to an element of it’s own. And if we could, and then compose it with an element of value, we’d end up right back where we started with some state. I see this differently. If you compose a timestamp with a value, it is still a value. This is the essence of temporal data modelling and accounting. > You can’t create anything in a stateless system. Agreed. The idea is however to avoid state whenever possible, pushing it to the edges. > Methods vs namespaces functions does not have a meaningful difference in this context Depends on how you use them. Classes and methods can be used as quasi namespaces. The nice thing about namespaces is that they provide you with that one, "simple" thing, while classes and methods are overloaded with semantics and utility. > Data vs Syntax is not a choice in any programming language that exists, all of them have both. The question is the degree. There are languages with minimal syntax that express more via data and there are languages with differing degrees of homoiconicity. I agree with the rest of your critique generally, as in that the context and the assumptions around a given model are typically subjective, we objectively deduce the parts from these assumptions. Say for example from my point of view a given tool might be simple, because I only use it to do that one thing, but you have to use it for that other (maybe intended) purpose as well, so you perceive it as complected.
- dahart 5y ago> I see this differently. If you compose a timestamp with a value, it is still a value. This is the essence of temporal data modelling and accounting. I agree completely that composing a timestamp with a value is still a value. I think the concept of assignment (values changing over time) is not the same thing as a timestamp at all; a timestamp doesn't represent state in the same way that mutability does. > The idea is however to avoid state whenever possible, pushing it to the edges. Yeah, this is where I think the problem lies. Saying "avoid state" isn't particularly helpful, especially if there is a certain amount of state that is essential and necessary. What should I do with the state that is required? What does it mean to push state to the edges, and in what cases does pushing state to the edges simplify my code? What if pushing the state causes complications by having to introduce more complex communication or more complex ordering between the elements of a system? I feel like one of the implicit suggestions when someone says to avoid state is that there is unnecessary state hanging around. But in practice, while there is sometimes redundant state, most programmers I've worked with are pretty conscientious about duplicating data. Most of the imperative production code I've worked with doesn't have a lot of redundant or duplicated state, it is already designed to minimize the amount of state. I love writing functional code when I can, it does feel simpler and easy to reason about. The two reasons I can't always write functional code are: performance and necessary state. Performance for functional constructs in various languages is improving, but it's still extremely common for functional designs to be very slow compared to equivalent imperative stateful designs. (And I do a lot of high performance computing as part of my current job.) And we need a better philosophy than just "avoid it" when it comes to all the necessary, unavoidable state.
- ItsMonkk 5y agoI agree with your hard-to-parse comment. I think without having a face-to-face and showing code samples this can't really be communicated(which is further complicated that the code is in a form that we both won't be able to grok), but I'll try anyway. I think this is driving at the crux of the matter. We've been building our applications on top of our databases. We do stuff, we call APIs, those APIs access databases, they return. The database holds the state. Files hold state. Outside dependencies hold state. State, within a normally designed OOP-based program, is at the inner-most level. Within your logic you query the state, and you thus depend on this state and need to inject those dependencies. When you need to unit test, you need to mock out these dependencies to return fake values. The Hexagonal Architecture still has state, but that state lies exclusively in the outside layer. When you change the state you are changing it at the outer level. State changes then trigger calls to your domain, they trigger changes to your services. But all of your domains, all of your services are stateless. Given this input I translate this data structure to this output data structure. That does not change state, it merely produces a result. That result makes its way back up the stack until it reaches the outer-most layer. The outer-most layer can then decide to perform the next state change. None of your inner code has any dependency injection, because you refuse dependencies. The core of your application is now pure. They are now inherently unit testable. Does this input provide this output. You don't need to worry about any dependencies, since none of your logic relies on anything but itself. Mocks become obsolete.
- dahart 5y agoI've tried putting these ideas into practice in a couple of previous jobs doing web development. Some applications I've written & worked on use React along with Redux for state management. For a different application that didn't use React, I used a pub-sub model to communicate state changes. Both of these work really well for certain kinds of data, and both of them also fell apart for me when things got complicated. I haven't thought hard enough about the problems I ran into to be able to categorize them in any concrete or helpful way. But, I do think that the breakdown is related to having to communicate state between components and across the system. Sometimes state changes have a necessary dependency on ordering things which can get tricky if one side is trying hard to avoid state. Sometimes wiring up state changes to the components that need it just adds communication complexity. The other thing that seems to happen when you put state on the outside is that your data schema evolves toward your code structure. In some ways, you end up duplicating the hierarchy. This has caused me trouble when refactoring. Once you realize some sizeable portion of code needs to be rewritten, but you can't change the data schema to match the new code because the data is production live, all the cleanness of the design starts to break down.