Each week, we discuss a different topic about Clojure and functional programming.
If you have a question or topic you'd like us to discuss, tweet @clojuredesign, send an email to [email protected], or join the #clojuredesign-podcast
channel on the Clojurians Slack.
This week, the topic is: "pure data, pure simplicity". We loop back to our new approach and find more, and less, than we expected!
Let's get some Hammock Time. We're big fans of Hammock Time!
We make a function for each of those: think, do, assimilate. A function to figure out the next thing, a function to do it, and a function to integrate the results back into the context. ("determine-operation", "execute-operation", and "update-context".)
It's not a single point of failure! It's a single point of context.
Where you have a binding in a let
block, you have a key in a context map. There's a symmetry there.
You can make the operation map as big, fat, and thick as you want, so "execute-operation" has 100% of everything it needs for that operation.
The "determine-operation" function can decide anything because it has the full context—the full world at its disposal!
Clojure has structural sharing, so all the information is cheap. We can keep a bunch of references to it in memory. We're not going to run out of memory if we keep information about every step.
The "update-context" is a reducer, so we can make a series of fake results in our test and run through different scenarios using "determine-operation" and "update-context". We're able to test all of our logic in our test cases because we can just pass in different types of data.
Your tests are grounded in reality. They're grounded in what has happened.
We've aggressively minimized the side effects down to the tiniest thing possible!
Data is inert. Mocks are not. Mocks are behavior.
You can just literally copy from the exception and put it in your test. There's no need transform it. It is already useful.
It's very testable. It's very inspectable. It's very repeatable. It creates a really simple overall loop.
You want those I/O implementations so small and dumb that the only way to mess them up is if you're calling the wrong function or you're passing the wrong args. Once it works, it will always work, and you no longer have to test it.
We need to build into context every little bit of information we need to know to make a decision.
Context takes anything that is implicit and makes it 100% explicit, because you can't get data across the boundaries without putting it in the context. You have no option but to put everything in the context, so you know everything that's going on.
We're in this machine, and there's no exit. We're on the freeway, and there's no off-ramp. We're in the infinite loop! How do we know we're done?
How do we know we're done?