The Abstracted Negroni

This post is written in literate javascript. You can download it here and run it at the command line thus: cat negronis.litjs | egrep '^ {4}' | node

I was out last Friday at a bar where they had a “Negroni Tic-Tac-Toe” offer—you could custom-build your drink from a selection of 3 gins, 3 vermouths and 3 amari, and if you got “3 in a row” you’d get £5 off your bill. It’s a laughably stingy deal, but it got me thinking. About programming, I mean.

function Negroni ( gin , vermouth , amaro ) { this . gin = gin ; this . vermouth = vermouth ; this . amaro = amaro ; // Build over ice, stir well } Negroni . prototype . inspect = function () { return "Negroni(" + this . gin + ", " + this . vermouth + ", " + this . amaro + ")" ; } // Couple of shorthands we'll be using later function print () { console . log . apply ( console , arguments ); } function inspect ( thing ) { return thing . inspect ? thing . inspect () : thing . toString (); }

The Negroni is often described as a “manly” drink, but fuck your gender-essentialism—let’s just call it badass. Everything I ever needed to know about it, incidentally, I learned from Felix. I personally find Punt e Mes a shade too bitter when combined with Campari, but that’s just me.

print ( "Here's how I make it at home:" , new Negroni ( "Botanist" , "Antica Formula" , "Campari" )); // => Negroni(Botanist, Antica Formula, Campari)

Point is, there are 3×3×3 = 27 possible combinations at that bar, a delicious Rubik’s Cube of liver damage. How would you enumerate them all?

var assemble = function ( g , v , a ) { return g . map ( function ( gin ) { return v . map ( function ( vermouth ) { return a . map ( function ( amaro ) { return new Negroni ( gin , vermouth , amaro ); }); }); }); }; var allGins = [ "Botanist" , "Bombay Sapphire" , "Blackwoods 60%" ]; var allVermouths = [ "Antica Formula" , "Cocchi" , "Punt e Mes" ]; var allAmari = [ "Campari" , "Aperol" , "Cynar" ]; print ( assemble ( allGins , allVermouths , allAmari )); // => [ [ [ Negroni(Botanist, Antica Formula, Campari), // Negroni(Botanist, Antica Formula, Aperol), // Negroni(Botanist, Antica Formula, Cynar) ], // [ Negroni(Botanist, Cocchi, Campari), // Negroni(Botanist, Cocchi, Aperol), // Negroni(Botanist, Cocchi, Cynar) ], // ...] // ...]

Hmmm. It’s a start, but that’s altogether too much nesting. We’ll never get anything done if we’re spending all our time unwrapping.

Array . prototype . unwrap = function () { return this . reduce ( function ( a , b ) { return a . concat ( b ) }, []); } assemble = function ( g , v , a ) { return g . map ( function ( gin ) { return v . map ( function ( vermouth ) { return a . map ( function ( amaro ) { return new Negroni ( gin , vermouth , amaro ); }); }). unwrap (); }). unwrap (); };

The outer two map s are followed by an unwrap , which turns an array-of-arrays into a single array. (The innermost map gives us an array of Negronis, which is what we want, so unwrapping is uncalled for—and would fail).

print ( assemble ( allGins , allVermouths , allAmari )); // => [ Negroni(Botanist, Antica Formula, Campari), // Negroni(Botanist, Antica Formula, Aperol), // Negroni(Botanist, Antica Formula, Cynar), // Negroni(Botanist, Cocchi, Campari), // Negroni(Botanist, Cocchi, Aperol), // Negroni(Botanist, Cocchi, Cynar), // Negroni(Botanist, Punt e Mes, Campari), // ...]

Much better.

Of course, it’s almost never a good idea to make 27 cocktails. Let’s row back a bit and consider a much more common scenario: you’ve got a guest round and they’d fancy just one Negroni. They don’t much mind what gin, vermouth or amaro it’s made with—you’re more worried about whether you have them at all. Can you satisfy them?

We need to represent the potential presence or absence of a thing.

var yep = function ( thing ) { return { inspect : function () { return "yep(" + inspect ( thing ) + ")" } } }; var nope = { inspect : function () { return "nope" } }; print ( "Here's something:" , yep ( "Gin" )); print ( "Here's nothing:" , nope );

So, time to write a new assemble method, right? To check individually for the presence or absence of each of our ingredients, and only return a yep() if we’ve got all 3?

Nope. assemble can stay as it is. We just need to implement map and unwrap .

yep = function ( thing ) { return { inspect : function () { return "yep(" + inspect ( thing ) + ")" }, // Apply f to our thing map : function ( f ) { return yep ( f ( thing )) }, // Turn a yep(yep(...)) into a yep(...) unwrap : function () { return thing } } }; nope = { inspect : function () { return "nope" }, map : function ( f ) { return nope }, unwrap : function () { return nope } }; print ( "If we have no ingredients:" , assemble ( nope , nope , nope )); // => nope print ( "If we have only gin and amaro:" , assemble ( yep ( "Gin" ), nope , yep ( "Amaro" ))); // => nope print ( "If we have gin, vermouth and amaro:" , assemble ( yep ( "Gin" ), yep ( "Vermouth" ), yep ( "Amaro" ))); // => yep(Negroni(Gin, Vermouth, Amaro))

Magic. assemble will accept anything that supports those two methods. All it expresses is that you need all 3 ingredients to make a Negroni—not how to get them, or how many to make. That logic lives entirely in the definitions of map and unwrap : for arrays, all possible combinations of elements are enumerated; for yep and nope , we only ever have zero or one of something, and we short-circuit as soon as the first zero (a.k.a. nope ) is encountered.

Sadly, it turns out we don’t have any of the ingredients to hand. You can order them online, though! Indeed, you have 3 fast-delivery specialist suppliers (one per ingredient—they’re highly specialised) bookmarked for this very purpose. Within a few moments, each one has promised that a bottle is on its way to you, and you can thus pass on the promise of a Negroni to your patient, thirsty guest.

var Promise = require ( "promise" ); Promise . prototype . map = function ( fn ) { return this . then ( fn ) }; Promise . prototype . unwrap = function () { return this }; var order = function ( name ) { return new Promise ( function ( resolve ) { setTimeout ( function (){ resolve ( name ) }, 1000 ); }); }; var promisedGin = order ( "Gin" ); var promisedVermouth = order ( "Vermouth" ); var promisedAmaro = order ( "Amaro" ); var promisedNegroni = assemble ( promisedGin , promisedVermouth , promisedAmaro ); print ( "Your Negroni is on its way. Any second now..." ); promisedNegroni . then ( function ( negroni ) { print ( "Ah! It's here:" , negroni ); // => Negroni(Gin, Vermouth, Amaro) });

Once again, we didn’t have to change assemble —just implement map and unwrap .

I think it’s kind of incredible that we can write code like assemble , which doesn’t care how its pieces work—just that they fit together right—and plug any kind of delivery mechanism we want into it: arrays-of-things, maybe-things-maybe-nots, promises-of-things. This is a huge step up from just writing views and not caring where the models came from. This is writing operations and not caring how they’re sequenced or executed.

If you think that’s cool too, we’re probably going to get along.

I’m not the first to point out that promises are the monad of asynchronous programming, but the topic has become pertinent, because the Promises/A+ spec is still in formation. If programmers can rely on a monadic interface for promises, this opens up the possibility of writing code which will work with any monad, not just promises—as I’ve tried to demonstrate with assemble above.

There are two equivalent ways to define a monad. You can define map and unwrap , as I have, or you can define chain (which is equivalent to map followed by unwrap ), and of (which takes a plain value and wraps it—so Array.of(1) would return [1] ).

The problem is that a promise’s then method, as specified by Promises/A+, treats the return value of the passed-in function differently depending on whether it’s a promise—essentially, it tries to intelligently determine whether to be map or chain . This means that the result of calling then is always a flattened promise, which is why the Promise.prototype.unwrap defined above just returns this . It’s impossible to call then and get back a promise of a promise.

Besides the inherent unreliability of trying to determine whether or not something is a promise, such overloading is likely to lead to difficult-to-predict bugs and difficult-to-reason-about code when promises are treated like well-behaved monads, but aren’t. We got away with it this time, but I wouldn’t feel confident writing more complex code, knowing that “smart” logic was there.

There has been some discussion on this topic in the last few days. Sadly, the authors have so far been difficult to convince. This is understandable—the theory is difficult to explain, examples of the potential problems are non-obvious, and it’s easy to get led astray by the question of why you would ever want to have a promise of a promise.

A more directly appealing argument was made by Reg:

A long time ago in a company far, far away, there was a young engineer given a herculean task within an impossible deadline. He created LiveScript. One of the decisions he made was to make functions first-class values rather than have them be “something else” like the other popular languages of the day. Another was to use prototypical inheritance rather than classical inheritance, like the other popular languages of the day. It would take years for the programming community to embrace the power of functions as first-class values in his language. Most people agree that Crockford got the ball rolling, followed by Oliver Steele and you can trace a direct line down to Jeremy Ashkenas and Underscore from there. Today, it is unthinkable to imagine a JavaScript without functions that can return functions or apply functions. Conversely, prototypical inheritance hasn’t reached a tipping point. The vast majority of programmers simply emulate classical inheritance and do not exploit its power in any way. What does this history tell me about promises? I think that promises-that-are-monads are in the same category as these two ideas I cited. If we embrace the idea of promises being first-class monads, we will likely have a lot of “meh” for a year or maybe three. Then someone will write a library or give a talk and the light will go on. It is had to imagine what wonderful thing will be created when that happens.

This speaks to me. The power of first-class functions had already been amply demonstrated when LiveScript was designed—just not in the mainstream. Similarly, the power of the monad abstraction has been amply demonstrated in functional languages, but not yet in the mainstream.

It may be too late for then —too much code already relies on its current behaviour, and besides, it’s useful. A concurrent spec for monads, specifying chain and of , has been started, and we could just hope that most promise implementors choose to support that too… but I strongly suspect the majority won’t bother, even though it’d be trivial.

To have chain and of specified and required in Promises/A+ would be a much bigger win.