Impureim sandwich by Mark Seemann
Pronounced 'impurium sandwich'.
Since January 2017 I've been singing the praise of the impure/pure/impure sandwich, but I've never published an article that defines the term. I intend this article to remedy the situation.
Functional architecture #
In a functional architecture pure functions can't call impure actions. On the other hand, as Simon Peyton Jones observed in a lecture, observing the result of pure computation is a side-effect. In practical terms, executing a pure function is also impure, because it happens non-deterministically. Thus, even for a piece of software written in a functional style, the entry point must be impure.
While pure functions can't call impure actions, there's no rule to prevent the obverse. Impure actions can call pure functions.
Therefore, the best we can ever hope to achieve is an impure entry point that calls pure code and impurely reports the result from the pure function.
The flow of code here goes from top to bottom:
- Gather data from impure sources.
- Call a pure function with that data.
- Change state (including user interface) based on return value from pure function.
Metaphor #
The reason I call this a sandwich is that I think that it looks like a sandwich, albeit, perhaps, a rather tall one. According to the myth of the sandwich, the 4th Earl of Sandwich was a notorious gambler. While playing cards, he'd order two slices of bread with meat in between. This enabled him to keep playing without greasing the cards. His compatriots would order the same as Sandwich, or simply a Sandwich, and the name stuck.
I like the sandwich as a metaphor. The bread is an affordance, in the spirit of Donald A. Norman. It enables you to handle the meat without getting your fingers greased. In the same way, I think, impure actions enable you to handle a pure function. They let you invoke and observe the result of it.
Examples #
One of the cleanest examples of an impureim sandwich remains my original article:
tryAcceptComposition :: Reservation -> IO (Maybe Int) tryAcceptComposition reservation = runMaybeT $ liftIO (DB.readReservations connectionString $ date reservation) >>= MaybeT . return . flip (tryAccept 10) reservation >>= liftIO . DB.createReservation connectionString
I've here repeated the code, but coloured the background of the impure, pure, and impure parts of the sandwich.
I've shown plenty of other examples of this sandwich architecture, recently, for example, while refactoring a registration flow in F#:
let sut pid r = async { let! validityOfProof = AsyncOption.traverse (twoFA.VerifyProof r.Mobile) pid let decision = completeRegistrationWorkflow r validityOfProof return! decision |> AsyncResult.traverseBoth db.CompleteRegistration twoFA.CreateProof |> AsyncResult.cata (fun () -> RegistrationCompleted) ProofRequired }
This last example looks as though the bottom part of the sandwich is larger then the rest of the composition. This can sometimes happen (and, in fact, last line of code is also pure). On the other hand, the pure part in the middle will typically look like just a single line of code, even when the invoked function performs work of significant complexity.
The sandwich is a pattern independent of language. You can also apply it in C#:
public async Task<IActionResult> Post(Reservation reservation) { return await Repository.ReadReservations(reservation.Date) .Select(rs => maîtreD.TryAccept(rs, reservation)) .SelectMany(m => m.Traverse(Repository.Create)) .Match(InternalServerError("Table unavailable"), Ok); }
Like in the previous F# example, the final Match
is most likely pure. In practice, you may not know, because a method like InternalServerError
or Ok
is an inherited base class method. Regardless, I don't think that it's architecturally important, because what's going on there is rather trivial.
Naming #
Since the metaphor occurred to me, I've been looking for a better name. The term impure/pure/impure sandwich seems too inconvenient, but nevertheless, people seem to have picked it up.
I want a more distinct name, but have had trouble coming up with one. I've been toying with various abbreviations of impure and pure, but have finally settled on impureim sandwich. It's a contraction of impure/pure/impure.
Why this particular contraction?
I've played with lots of alternatives:
- impureim: impure/pure/impure
- ipi: impure/pure/impure
- impi: impure/pure/impure
- impim: impure/pure/impure
I like impureim because the only anagram that I'm aware of is imperium. I therefore suggest that you pronounce it impurium sandwich. That'll work as a neologic shibboleth.
Summary #
Functional architecture prohibits pure functions from invoking impure actions. On the other hand, a pure function is useless if you can't observe its result. A functional architecture, thus, must have an impure entry point that invokes a pure function and uses another impure action to act on the result.
I suggest that we call such an impure/pure/impure interaction an impureim sandwich, and that we pronounce it an impurium sandwich.
Comments
I find this example slightly simplistic. What happens when the logic has to do cascade reads/validations as it is typically done? Then you get impureimpureim...? Or do you fetch all data upfront even though it might be...irrelevant? For example, you want to send a comment to a blog post, but that post has forbidden new comments? Wouldn't you want to validate first and then fetch blog post if necessary?
Toni, thank you for writing. As I write in another article,
On the other hand, I never claimed that you can always do this. The impureim sandwich is a design pattern. It gives a name to a general, reusable solution to a commonly occurring problem within a given context.In cases where you can't apply the impureim sandwich pattern, other patterns are available.
I like this idea and it gives a word to they pattern I have been trying to use but I do have some questions. In the C# example you have a field `maîtreD`. I am assuming that the value comes from dependency injection. Is that the case? And if so can it really be called a pure function? Is that tested in isolation and the test for the function in the example you test that the results from ReadReservations are passed to `maîtreD.TryAccept`? Or is there something else I am missing?
Flechto, thank you for writing. You don't have to assume anything about the code. If you following links in the article, you should be able to find the source code.
Conceptually, yes, the
maîtreD
class field is initialised via Constructor Injection. What makes you think that that makes it impure?