4 ms·
> 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 sente
by 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.
- pron 11y ago> Side effects aren't limited to filesystems, databases, etc. As the author programs in Scala and Clojure, two impure languages that make liberal use of memory side effects, I think she was concerned mostly about IO (she even said so). > The side-effects are still there, mixed in with the logic. Imperative languages don't differentiate between memory side effects and logic. That is either an advantage or a disadvantage -- mostly depending on your personal preference. The strength of the imperative approach is that when computation is a combination of logic and memory effects, it yields a more powerful computational model than pure functions alone (that's why Turing machines can describe computations that lambda calculus cannot -- at least not directly; their equivalence mostly rests on LC's ability to be used as a meta-language that can simulate a Turing-machine language). It allows representing many algorithms that PFP simply cannot (in Haskell, many algorithms are implemented with unsafe). Finally, it yields better performance (computational complexity is a very vague notion in lambda calculus). > But mocks are only used in tests! I was referring to a mock as a concept, not a particular implementation. If you can't find a mocking library that replays operations, write one that does. It would fit much better and be much more idiomatic. See below for an explanation. > 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. I don't dismiss pure functional approaches at all if you happen to be using a pure functional language like Haskell. Imperative languages, however, already have similar mechanisms in place, and eschewing those in favor of PFP approaches is more work, and tends to lose the benefit the imperative approaches have (as those are more in line with the language), for example, the stack context. I will be giving a talk about this at the upcoming Curry On/ECOOP conference, where I'll show that virtually all monads already have imperative counterparts that are preferable (and more powerful) when writing in an imperative language. The recent trend of PFP constructs being adopted by impure languages is due to two things: 1. the false notion that the PFP approach is more "mathematical" or more verifiable, and 2. monadic composition is better than callbacks, and thus useful in avoiding blocking OS threads, which is expensive (although simply fixing threads -- as Erlang and Go have done -- is so much easier).