3 ms·
> They capture your calls to actual real-world interaction and turn them into a list of operations that can later be queried and verified The author's point is
by chriswarbo 11y ago
> They capture your calls to actual real-world interaction and turn them into a list of operations that can later be queried and verified
The author's point is to avoid interleaving logic and side-effects (as much as possible) in the first place. Yes, we can use dependency injection to send mocks deep into the bowels of our application where they get called. Alternatively, we can have the bowels of our application return values, describing what to call, back up to the top of our application. These are exactly dual.
The nice thing about the latter is that every action gets reified into a concrete value, on which we can perform any kind of computation we like. Dependency injection can't do that, since a method call is not a value (it can return a value, but it isn't itself a first-class value).
Mocks allow us to inspect which calls have been made, etc. but mocks are for testing. We can't use those interfaces in our real code. If we use a list-of-actions we can, for example, run optimisation passes before execution.
> they do all that without requiring you to create a new DSL for capturing and describing effects.
Mocks do require a new DSL for capturing and describing effects; it's normally called a "public interface", but it is a DSL (an algebra) for describing effects.
- pron 11y ago> The author's point is to avoid interleaving logic and side-effects But with mocks you're not really interleaving side effects. They are the same as generating "effect values", only they use the same API as the actual effect. > The nice thing about the latter is that every action gets reified into a concrete value, on which we can perform any kind of computation we like. That's what mocks do. They capture calls and turn them into reified values that can then be manipulated and queried. > Mocks do require a new DSL for capturing and describing effects; it's normally called a "public interface", but it is a DSL (an algebra) for describing effects. Except that API is identical to that of whatever effect API we're using. A mock is a mechanism for turning effects into values. It just works automatically by means of interfaces (in the language sense) and reflection. It is an imperative construct that achieves -- without extra work -- the very same functional effect the author is striving for.
- chriswarbo 11y ago> with mocks you're not really interleaving side effects. Yes, you are. Side effects aren't limited to filesystems, databases, etc. Here's the very first sentence of http://en.wikipedia.org/wiki/Side_effect_(computer_science) http://en.wikipedia.org/wiki/Side_effect_(computer_science) > In computer science, a function or expression is said to have a side effect if, in addition to returning a value, it also modifies some state or has an observable interaction with calling functions or the outside world. Every time we call a mock, we cause observable changes (ie. we alter the results of any queries made on the mock). Therefore, calling a mock is a side-effect. The point of mocking is to replace unwanted side-effects with harmless side-effects. The side-effects are still there, mixed in with the logic. > That's what mocks do. They capture calls and turn them into reified values that can then be manipulated and queried. But mocks are only used in tests! My application can't ask, say, a FileSystem object for a list of calls which have been made. Only my tests can do that, because they're using a FileSystemMock object instead. My application can't use a FileSystemMock since, by design, it can't do the filesystem manipulation that I need. Even if I add call-logging to the API of my FileSystem object, it doesn't give me any control, since they've already happened by the time they appear in my queries. For example, let's say my application ends up with a list of effect values [e1, e2, e3, etc.] and, after performing some logic, it chooses to only return the first one: [e1]. How would mocks help me do this? Firstly, since this is application code rather than test code, I can't use mocks at all. Secondly, even if I could perform a query to get [call1, call2, call3, etc.], there's no way to turn that into [call1]; I can remove the logs of call2, call3, etc. but I can't undo any side-effects they've caused. To achieve the same outcome as the effect value example, I must alter the control flow of the program such that call2, call3, etc. are never made. That has nothing to do with mocking. > Except that API is identical to that of whatever effect API we're using. Exactly! How is an "effect API" not a DSL for describing effects? They're the same thing. Mocks don't
- chriswarbo 11y agoOops, cut off my last sentence there and didn't spot it in time to edit. I meant to say, mocks (+ dependency injection) don't require more or less work than using "effect values"; certainly for applications written with that approach in mind. Yes, mocks use reflection to do meta-programming. "Effect values" also allow meta-programming; but we don't need reflection since our "programs" (eg. lists of side-effecting actions) are already first-class citizens. We can use any built-in or off-the-shelf list library to manipulate such "effect values"; which I would consider just as "automatic" as using a built-in or off-the-shelf reflection library. Taking an existing application and refactoring it to use one of these approaches would vary from project to project regarding which would take more effort. I think many people dismiss functional approaches like these "effect values" by thinking of them at too low a level, and imagining that they must involve an awful lot of work to use. That would be like dismissing linked lists as involving too much work, since we have to keep pushing and popping values in and out of the lists; so why not just use individual variables? Of course, in reality the whole point of lists is that we can ignore their contents and manipulate them as whole entities; mapping, slicing, merging, sorting, etc. The same applies to "effect values"; we don't just declare an enum and throw a load of switch statements everywhere. We can ignore the particular effects in a value and instead manipulate them as whole entities; mapping, slicing, merging, sorting, etc.