Idiomdrottning’s homepage

bind is all right

Let’s talk about what bind, a.k.a. >>=, looks like in Scheme terms!

Maybe you have some skeletons you wanna chop up with your sword function:

(sword skeletons)

or, if that’s too abstract, you have a number you wanna square:

(square 4)

or a cuss you wanna print:

(print 'darn)

Easy right? Just do it. Except… what about all those times when the expression as a whole is trapped in a dungeon and the sword (or the squarer or the printer) is in a locked chest in that dungeon!?

You’d want to first

(quest dungeon)

and you’d get (chest . skeletons) or (chest . 4) or (chest . 'darn).

You want to unlock the chest:

(unlock chest)

And then (sword skeletons) or (square 4) or (print 'darn), or, taking those two steps together, you want to:

((unlock chest) skeletons)

Taking it all together, you want to

(aif (quest dungeon) ((unlock (car it)) (cdr it)) #f)

Maybe you wanna do this often because maybe you are a video game character and you have lots of useful tools forgotten in chests in dungeons that you wanna use on the stuff in that dungeon.

So you make a function of this:

(define (quest-unlock-and-use dungeon)
  (aif (quest dungeon) ((unlock (car it)) (cdr it)) #f))

Works well:

(quest-unlock-and-use koholint)
⇒ Some Octoroks were chopped.
(quest-unlock-and-use sr388)
⇒ Some Metroids were frozen.
(quest-unlock-and-use birabuto)
⇒ Some Gao were superballed.

Until one day when you find yourself dealing with not a normal chest in a normal dungeon. You find yourself having to open a jar of plums in the company snack room and the can opener is hidden in the utensils drawer!

No worries, right? You just need to find-your-way in the snack-room and also untangle-drawer the utensils drawer and you’ll be on your jolly way to plum paradise!

Maybe this too becomes a common enough part of your routine for you to define it:

(define (find-untangle-and-use snack-room)
  (aif (find-your-way snack-room) ((untangle-drawer (car it)) (cdr it)) #f))

Huh… a little too similar to the other thing. Let’s refactor it so both are the same and use the same “bones”.

(define ((bind finder opener) place)
  (aif (finder place) ((opener (car it)) (cdr it)) #f))

It’s curried so you can just slot (bind quest unlock) in wherever you would’ve used quest-unlock-and-use in the past:

(for-each (bind quest unlock) (list castlevania tetris))
⇒ Some Draculas were axed. Some T-pieces were flipped.

This is awesome! You can also mix and match: now you can deal with chests in dungeons and in snack rooms, and you can deal with drawers in snack rooms and in dungeons.

Then one day you stumble upon an entire snack room inside a dungeon! The horror!

A normal chest unlock or drawer untangle-drawer is not enough, you need to deal with the whole snack room.

Don’t worry, baby:

((bind quest (bind find-your-way untangle-drawer)) dungeon-with-snack-room-inside)

This will quest the dungeon for the snack room, and then find-your-way in that snack room for the box opener or whatever the tool you need in that snack room is.

Because you can chain bind in sequence this way, it’s sometimes called a “monadic sequence operator”. But only by complete dorks, that’s not something you and I need to worry about, darling.♥︎

((bind quest (bind find-your-way (bind find-your-way untangle-drawer)))
 dungeon-with-snack-room-inside-of-another-snackroom-in-the-dungeon)

Acknowledgments

Thanks to Drew Crampsie and Moritz Heidkamp, whose implementations of bind are

(defun .bind (parser function)
  (lambda (input)
    (loop :for (value . input) :in (run parser input)
          :append (run (funcall function value) input))))

and

(define ((bind parser proc) input)
  (and-let* ((value (parser input)))
    ((proc (car value)) (cdr value))))

respectively. Drew’s version can handle multiple values, apparently. I’ve got to say that that’s awesome.♥︎

Drew has a more technical explanation.