Monthly Archive for December, 2011

Consensus and Compromise

Working on a start-up involves many decisions — how features should work, how pages should look like, how advertising should be written… and making decisions is a difficult process when working as a team. Even in the tightest-knit team of two, disagreements happen.

Sometimes, on the less important decisions, you might get out of it by default — A disagrees with B, but doesn’t care enough about the topic to actually do something about it, so B proceeds anyway.

Sometimes, decisions will be dictated by competence. If A disagrees with a proposed solution because of objective technical or legal implications, well, that’s it.

Quite often, though, there is no such solution. This is where one must be aware of the dangerous tendency of consensus-based teams to devolve into compromise-based discussions.

Consensus-based discussions feel like this :

A: I’m going to frobnicate the thingamajig.
B: If you frobnicate the foobar instead, you would get a 10% increase in floogum output.
A: You’re right. Let’s frobnicate the foobar, then.

That is, initially differing opinions evolve through an argumentation process, so that everyone agrees that the final outcome is the best one.

Compromise-based discussions feel like this :

A: I’m going to frobnicate the thingamajig.
B: I would rather that we frobnicate the bazquux instead. Might get better results.
A: Actually, I have a hunch that the thingamajig is the better option.
B: Tell you what, let’s frobnicate 50% of the thingamajig and 50% of the bazquux. This way we have everything covered.
A: All right.

Here, initially differing opinions are settled through a negotiation process, so that everyone’s input is respected, but no one agrees the final outcome is really the best.

Compromise is a bad idea for several reasons.

  • Quite often, going 100% with any reasonable solution is better than investing only 50% into two different solutions. In more general terms, lack of commitment to a single strategy or objective is a dangerous thing to do, and often less effective than commiting to any strategy or objective one might come up with.
  • Compromise is not agreement, it’s negotiation : you give something and get something else in return until everyone agrees that it’s an even trade. People who are good negotiators tend to dictate their terms in such circumstances, and others might feel helpless and useless after a few unbalanced trades. It also leads to « I agreed with your idea yesterday so agree with mine today » or « You’ve already changed my idea enough, stop asking me to change it again, » both of which are content-free sentences that aim to make a decision based on interpersonal history instead of objective analysis.
  • The inability to come to a logical conclusion sometimes happens because there is not enough data available to decide. The first priority in such circumstances should be to actually search for the data (possibly accepting one of the proposed solutions as temporary until the data is collected), not settling for an arbitrary compromise.
  • Building consensus is hard when people have trouble putting their insight into words, no matter how convincing or true that insight might be. I’m objectively right, but I cannot seem to explain to you the reason for it. This can mean going for a compromise today instead of a consensus tomorrow — hasty decisions are seldom good. Always make sure everyone has had enough time to think the decision through, even if it means adjourning it until later.

Two-way bindings

Quick : how would you design a function that opens a file handle that auto-closes whenever execution leaves a certain scope, even if an exception happens ?

The C++ solution is quite straightforward : have a destructor that closes the file handle, and create the handle as an auto variable:

std::ofstream out = std::ofstream("out.txt");
out << whatever();
// file handle is always closed by the language

On the other hand, OCaml does not have destructors, but can simulate the RAII spirit using a closure: you provide a function that will be called with the file handle as its argument, and the file handle will be destroyed when the function returns or raises an exception.

BatFile.with_file_out "out.txt" begin fun out ->
  BatIO.nwrite out (whatever ())
  (* the file handle is always closed by the language *)
end

This is a special case of a more general principle.

Two-way binding

The standard let keyword performs a one-way binding: bind value to variable then evaluate expression. Two-way binding adds a post-processing step : when you’re done with the expression, do something else. Such a behavior has important consequences for writing concise and readable code.

In my OCaml code, two-way binding is performed with keyword let! that is preprocessed as follows :

let! pattern = value in expression
(* Is translated to *)
value (fun pattern -> expression)

For instance, the above file manipulation script would be written as:

let! out = BatFile.with_file_out "out.txt" in
BatIo.nwrite out (whatever ())

This syntax expresses the actual intent of the code better than the anonymous callback syntax did: bind the file handle to this variable, but don’t forget the post-processing steps.

Here are a few more examples of situations that may be improved by this syntax :

Events and reactive programming

Reactive programs can be constructed either using the typical “register this function to be called whenever this value changes or this event happens” semantics, or  by using binding semantics instead:

let () =
  let! user = User.on_change (#last_login) in
  if user # notify_login then
    Mail.send (user # email)
      ("Someone has logged in to your account at " ^ datetime (user # last_login))

The underlying signature of User.on_change (which registers a listener callback and returns unit) remains the same.

Retry semantics

CouchDB implements transactions with retry semantics: you read a document, compute some changes and try saving them back, and if the document was changed by someone else in the mean time, you will have to try again. It makes sense for the code inside the transaction to be 1° idempotent and 2° wrapped away in a function that 3° takes the latest version of the document as an argument :

let set_title article_id new_title =
  let! article = Database.transaction article_id in
  Database.write article_id { article with title = new_title }

In such a design, the write function would throw a specific exception if a collision occurs, and the transaction function would intercept that exception and try again until the transaction succeeded or a maximum number of retries happened.

Monads

Value binding in monads benefits from having a syntax that actually looks like binding.With the option monad, one can turn this :

match Files.get file_id with None -> None | Some file ->
  match file # owner with None -> None | Some user_id ->
    match Users.get user_id with None -> None | Some user ->
      Some (user # name)

Into a more straightforward version :

let  open BatOption.Monad in
let! file    = bind $ Files.get file_id in
let! user_id = bind $ file # owner in
let! user    = bind $ Users.get user_id in
return (user # name)

Also, one can deal with Lwt threads almost as well as the Lwt-specific syntax extension:

open Lwt
open Lwt_io

let process_lines channel process =
  let loop () =
    let! line_opt = bind $ read_line_opt channel in
    match line_opt with
      | None -> return ()
      | Some line -> loop () <&> process line
  in
  loop ()

Being Silly

let fold init list f = List.fold_left (fun acc x -> f (acc,x)) init list
let map list f       = List.map f list

let probabilities odds =
  let sum =
    let! accumulator, odd = fold 0. odds in
    accumulator +. float_of_int odd
  in
  let! odd = map odds in
  float_of_int odd /. sum

The Syntax Extension

In case you don’t know how to create it, this is the preprocessor file for this syntax extension :

open Camlp4.PreCast
open Syntax

EXTEND Gram
 GLOBAL: expr;

 expr: LEVEL "top"
 [
   [ "let"; "!"; p = patt ; "=" ; e = expr ; "in" ; e' = expr ->
     <:expr< (($e$) (fun $p$ -> $e'$)) >> ]
 ] ;

END;

By the way, I find that this extension has a significant advantage over the Lwt extension – it is readily compatible with syntax highlighting in most editors.

How to Annoy People

Here is a hypothetical situation :

The kingdom is in trouble, and the King must enact a new law. Of course, such a law will make many people happy for years, but some people will be annoyed by it for a few days. He has two possible choices :

Law A will make 20% of the people happy at the cost of annoying 1%.

Law B will make 90% of the people happy at the cost of annoying 10%.

Which law should the monarch enact ?

This is not the kind of problem that engineer types like me enjoy — no matter how much we try to abstract away the details and build a sound foundation for that decision, some ethics will inevitably seep in.  Is it right to annoy an additional 9% of the people so that 70% more people become happier? Where do we draw the line in term of percentage, and in terms of annoyance — a few days might be fine, but what about a few months or years?

As you might expect, this is not an entirely hypothetical exercise. It is, in fact, the very core of the opt-in versus opt-out debate. Above, law A is opt-in : only 21% of the population knows about its effects, but at least there will be very few complaints ; law B is opt-out : it ensures that 100% of the population knows about it, but it will annoy the 20% who will have to manually opt out.

My start-up hosts discussion forums for associations. Every one of our customers has faced the same decision : should they send an e-mail to their association members telling them « please come and sign up on our forums » or should they import their member directory and let naysayers unsubscribe ?

With the opt-in approach — please come and sign up — the initial e-mail is followed by a small number of sign ups, usually from the more active or dedicated members, and will be ignored by everyone else. If the number of initial adopters does not reach a critical mass, the forums will simply die off and be forgotten by everyone.

With the opt-out approach — import all the members — everyone will be notified every time a new message is posted, as would happen on a mailing-list. A forum message from a friend is a lot more interesting than a « please come and sign up » e-mail and drives more members to connect and participate. The critical mass is reached far easier (and faster!) and the forum becomes an essential part of the community. However, those who did not wish to participate will receive e-mail that is literally unsolicited, and they will complain about it — while the number of active users increases significantly, the number of complaints and unsubscription requests increases even faster. The delicious irony of it all is that the number of complaints is driven up because the communication tool helps those annoyed members find each other and speak up in unison.

Before continuing, let me fend off two possible problems.

First, the opt-out approach is not intended to be a sneaky trick — we always strongly advise customers to send a preliminary e-mail to all their members in advance, telling them about the plan to move to a new discussion system. Not only does it keep things civil and honest, but people who absolutely hate receiving messages from their community can ask to opt out before it is too late. Online communities have trouble blooming when 10% of the messages are complaints about the very existence of the community, so it is in everyone’s interests to keep naysayers out.

And if anything else fails, we can wipe out members from our system on demand.

The second problem will be familiar to readers of Seth Godin — permission. Through the eyes of a permission marketer, to import all  the members without their prior explicit opt-in consent is absolute heresy.

I find this view a bit too extreme. Well, it does make sense when trying to sell things that 99.99% of the people will not care about, such as viagra or cheap hotels in Bangkok. But members actually care about what is going on in their association, and — based on our experience — only a minority of members ever asks to be completely removed from the forums. Most members only unsubscribe from individual discussions that they do not care about, and choose to remain available on the forums as a whole.

It feels sad that so many people would miss out on a great experience just so a handful of curmudgeons can spare the effort of clicking an unsubscribe link.

Back to the point.

As far as I can tell, the approach that helps most members get involved in an online community is :

  1. In advance, send them an e-mail telling them that the association is about to move to another online community system — in our situation, it could be described as a mix between a forum (for those who wish to be very active) and a mailing-list (for the occasional participants) — and that each of them will receive those communications on an opt-out basis.
  2. You will receive messages saying « this is a bad idea » or « I don’t want to receive those communications » and you should take steps to make sure that no person who opted out at this stage is ever imported into the forums. Unless they ask for it later, anyway.
  3. After enough time has passed — at least 48 hours for large associations — import everyone into the forums, and write a welcome message there explicitly asking them to say hello when they reach it for the first time.
  4. If any other people request to be completely unsubscribed, you may simply remove them from your forums and they will stop receiving messages. If you need to absolutely make sure that all their data has been wiped from our database, drop us a line and we’ll take care of it for you.

We have had several customers build a thriving online community for their association with this approach, and have even seen a few « get me out of here » naysayers change their minds and come back online, once they understood everyone else was using it.

Where do you stand on the matter ?

Article image © Carlo Piana — Flickr



1342 feed subscribers
(readers who polled a feed this week)