Saturday, January 26, 2013

When is a programming feature 'helpful'?

There was a kerfuffle in my twitter feed this morning which highlighted some issues about language design. Don Stewart retweeted this conversation about funny JavaScript behavior. Reproduced in a JavaScript  console, it looks like this:
    > ['10','10','10','10','10'].map(parseInt)
    [10, NaN, 2, 3, 4]
It looks completely mysterious to a Haskeller, and by "mysterious" I mean "punishable by life in prison." I'd bet there's many a functional head listing on a sprained neck out there in Haskell-land,and that co-functional parents are explaining to their children what adult words are. I set about looking for an explanation, because I could not imagine how map could behave this way. I got the exact same answer in my browser, which made it unlikely that it was due to uninitialized memory. So first I tried using the identity function:
    > ['10','10','10','10','10'].map(function(x) {x;})
    [undefined, undefined, undefined, undefined, undefined]
Oh, right, JavaScript requires a return:
    > ['10','10','10','10','10'].map(function(x) {return x;})
    ["10", "10", "10", "10", "10"]
Great! Sensible behavior. Next, I looked up parseInt and found that it takes two arguments: parseInt(string, radix). JavaScript code is full of helpful functions that take variable numbers of arguments and guess what you mean based on their type, so this seemed a likely candidate for trouble.
    > ['10','10','10','10','10'].map(function(x) {return parseInt(x,10);} );
    [10, 10, 10, 10, 10]
Much better. Presumably then, the broken version supplies a second argument to parseInt that it pulled out of its ass. The tail of ..2,3,4] made me suspect there was some accumulation happening, so I tried this:
    > [1,2,3,4,5].map(function(x,y) {return (x+y);} )
    [1, 3, 5, 7, 9]
Prison's too good for them, I thought. Apparently this sums adjacent elements, except...wait, there would have to be a zero prepended for that to work. I observed that addition being commutative, it was projecting out valuable information, so I tried something a little more revealing:
    > [1,2,3,4,5].map(function(x,y) {return ('('+x+','+y+')');} )
    ["(1,0)", "(2,1)", "(3,2)", "(4,3)", "(5,4)"]
Still looks like adjacent sums with a magical zero. At this point I had some coffee and realized I was confused about the types. I had two lists of small integers, one from a mysterious source, not one list being zipped with itself.
    > [1,2,3,4,5].map(function(x) {return x*10;}).map(function(x,y) {return ('('+x+','+y+')');} )
    ["(10,0)", "(20,1)", "(30,2)", "(40,3)", "(50,4)"]
Okay. The second argument is clearly not a function of the array contents and now looks like the array index of the current argument. Going back to the original problem:
    > parseInt('10',0)
    > parseInt('10',1)
    > parseInt('10',2)
Great, this sort of makes sense now. A radix of zero is assumed to be 10, a very C-like idiom, and the second example fails correctly because '10' is not a unary number. So, parseInt is not the villain, nor really is map. It is perfectly reasonable to have a variant for arrays that would operate on an element and its index. For me, the villain here is the handling of optional arguments. The map function is overloaded silently and that's the kind of help I don't want.

'Helpful' means different things to different people. To a Haskeller, the definition is something like: publish your promises and keep them. Explicit type signatures, referential transparency, static type-checking are all there to make those promises come true. In JavaScript, optional arguments are considered helpful, because they let you reuse the same function name in slightly different contexts.

Steadily, monotonically, over the years, I have moved away from convenience in the specification of a problem whenever it introduces uncertainty. When I first learned C, I memorized the precedence of operators so that I could write expressions with a minimum number of parentheses. Then I learned other things and forgot the precedences and just started parenthesizing everything until it was unambiguous. A friend in graduate school used Modula-3, which had no automatic casting between numeric types. C programmers were horrified, but I felt it was a breath of fresh air.

These conveniences should be part of interactive programming environments, not programming language specifications. Fuzziness, forgetfulness, disorder are part of the human condition and should be acknowledged, but they should not make it into the code. Say what you mean to the programming environment, by all means, but have the resulting program be completely unambiguous.

Which reminds me, the JavaScript map actually accepts a function of three arguments...

Saturday, January 12, 2013

Falafel Waffles

I love falafel.  Used to eat it all the time in NY.  But it's not so common in San Diego.  I'd make it myself, but I hate deep frying. But Mark Bittman's love of freshly-cooked chickpeas (the the resulting broth, which he dubs 'gold') set me to cooking them myself.  Which made me want falafel.

So I thought about alternatives to deep-frying. The essence of deep-frying, I reasoned, is total immersion in high heat, where high means more than boiling, hot enough to brown.

Enter the waffle iron, a device that forms batter into a shape with a lot of surface area exposed to high heat.  Clearly, this is genius on two scales, I thought: it will work beautifully; it rhymes.

Trademark time!  Google search!  First entry: No, you aren't the first person to think of falafel waffles.  D'oh! Still, there aren't many falafel waffle recipes out there, and many included wheat flour, but I wanted both wheat- and gluten-free, so I set out on my own expedition.

I started with chickpea flour (Bob's Red Mill).  For seasoning I used garlic, scallion and some fresh herbs (various mixtures of dill, cilantro, mint and basil.)  The taste was great, but I had used very little water, just enough to bring the mixture to a paste, and that did not work so well in a waffle iron.  If I left it long enough to brown, my falafel turned into a jawbreaker.

So gradually I increased the liquids, water and some olive oil, until I got to a pour-able batter, just like flour waffles.  On the advice of waffle experts, I added baking powder, and where others added a little wheat flour for binding, I added arrowroot.  I haven't done controlled experiments on the effects of the arrowroot as a binding agent, so don't panic if you don't have any.

In addition to the baking powder, I borrowed the traditional wet/dry method from baking: two bowls, one for wet ingredients, one for dry, mix each thoroughly, then combine.  The wet and dry mixtures should be roughly equal in volume.  This recipe uses 1/2 Cup of each, resulting in about two waffles in a medium-sized circular waffle iron.

The result is spicy, savory waffles, light and crunchy, with a uniform texture.  They are different from traditional falafel, but close enough for my purposes.  I eat them as a standalone snack, or with chopped  cabbage (usually red) dressed with a sauce made of lemon and tahini (sesame paste).

To restore some of the feel of traditional falafel, I often add small quantities of mashed chickpeas, cooked brown rice, or both.  Both of these reduce the tensile strength of the waffle, but add a nice crumbly texture and nuttiness.

I put the wet ingredients in a measuring cup and puree with an immersion blender:

  • 2 scallions or 2 cloves of garlic, or mixture
  • a few sprigs of herbs
  • pinch of salt
  • a tablespoon or two of olive oil
  • enough water to bring the level to 1/2 Cup.
  • Optional: a tablespoon or two of chopped/mashed chickpeas and/or brown rice (cooked)

  • 1/2 Cup chickpea flour
  • pinches of salt, pepper, cayenne, cumin
  • 1 teaspoon of arrowroot
  • 1 teaspoon of baking powder
The resulting batter should pour easily. If not, add more water and mix until it does.  Bean flour takes some time to re-hydrate.  I let the batter rest for an hour; anything less than that seems to leave a hint of raw bean taste.  This recipe makes about two waffles in a circular waffle iron.