1. 22

I do a lot of mentoring, which means convincing people to use unit tests and DRY their code. I also like writing about uncommon programming techniques which people think are interesting in theory but aren’t too sold on the practice. I’ve noticed that coming up with a good, instructive, and above-all persuasive examples is really, really hard. In order to be all of those, I think it has to at least be the following:

  • Simple enough to be understood by somebody who’s not sold yet (a lot of monad tutorials miss this)
  • Complex enough to be something you’d reasonable encounter, so you can’t just write it off as a toy problem (you see this missed a lot with TDD tutorials)
  • Demonstrates why this is better, or at least different, from what the reader is used to. (think “recursion is awesome! Let’s do factorial.”)

I think web frames what mostly figured this out wrt examples: many backend tutorials involve making a blogging or twitter platform, while a lot of frontend tutorials involve making a todo list or small game. That’s usually enough to get a basic feel for how it’s different and how it’s actually usable in the Real World.

Since finding good examples is hard, I wanted to share some of mine, and see if any of you had good ones I could use. Here are a few I think work well:

Unit Testing: “Given a dictionary where keys map to arrays of values, return the dictionary of values that map to arrays of keys.” IE if spies_on = {'kremlin': ['alice', 'bob'], 'nsa': ['alice']}, then invert(spies_on) == {'alice': ['kremlin', 'nsa'], 'bob': ['kremlin']}. Works because it’s really easy to write a simple function that works for a couple of base cases you immediately try, but there’s also a lot of edge cases. Fixing some often breaks others, and manually testing every possible case is really tedious, so it’s a good pitch for “write automated tests.”

Monads: Option types. End of.

Recursion: Factorial is pretty popular, but I don’t think it really makes a case for why recursion is useful, since you can write the exact same thing with a loop. Flattening a nested list seems like it would work better, but I haven’t tried using that as an example yet.

Formal Methods: This is a really hard one. “Guaranteeing safe bank transfers” is a decent example that usually gets people thinking “oh, this is kind of interesting”, but I’ve only really had success getting people excited by showing them work I’ve done with it at my job.

Why you have to comment code: Kind of surprised this is one you have to teach people, but “comments are a code smell!” is becoming more of a thing these days. One example I’ve been working on is “what’s the difference between two dates, in days?” Simple until you have to account for leap years, which have really messy rules. And in the Hebrew calendar, you insert extra months during a leap year. Also, time discontinuities.

  1.  

  2. 10

    Sure, I’ll bite.

    One thing before I go into examples, though–when mentoring, I go by Angersock’s Rule of Communication:

    Communication is the act of projecting your ideas onto the bases of somebody else’s experience.

    Projecting here is in the linear algebra sense, and means that if you are trying to teach somebody something that they have no context for, they’ll not understand you.

    Sometimes, this means that certain classes of ideas (formal methods) are completely off-limits because they haven’t worked in systems that require them. More generally, teaching any kind of “best practices” requires very judicious choice of examples and a strong effort to adapt to your students.

    ~

    Recursion

    Beyond toy problems of the sort popular in algorithms textbooks, the real benefit from recursion comes when the cost of bookkeeping an iterative accumulator is higher than a recasting something as a recursive solution. For 1D lists, this cost is not high–and the performance much better–compared to a recursive approach (assuming you are in a boring language without TCO, which most people are).

    So, focus on things like trees. It’s really annoying to do the bookkeeping for an n-ary tree, but is a surprisingly common isomorphism in business. An example:

    Say that you have a list of companies. A company has child companies (perhaps themselves containing companies) and employees. The lineage of a company is the company that owns it, the company that owns that company, and so forth. What is the shortest code for finding if a person belongs to a company that is has more than three companies in its lineage?

    That’s a problem whose broad structure people will often face (replace “company” with “department” or “order”), and so it is easy to show that they should care about simpler solutions afforded by recursion–implementing an iterative solution is annoying enough to dissuade them if stepped through.

    Why you have to comment code:

    float famous_evil_math_hack( float number )
    {
        long i;
        float x2, y;
        const float threehalfs = 1.5F;
    
        x2 = number * 0.5F;
        y  = number;
        i  = * ( long * ) &y;                       // evil floating point bit level hacking
        i  = 0x5f3759df - ( i >> 1 );               // what the fuck? 
        y  = * ( float * ) &i;
        y  = y * ( threehalfs - ( x2 * y * y ) );   // 1st iteration
    //  y  = y * ( threehalfs - ( x2 * y * y ) );   // 2nd iteration, this can be removed
    
        return y;
    }
    

    It’s obvious how it does what it does (or is it?)…but what is it that this does? Has it been outperformed by hardware instructions now?

    Alternately, an obtuse SQL statement with bad variable names.

    Not being clever

    Sometimes it helps to give a problem that is actually really simple and doesn’t need something too involved. In Elixir, for example:

    defmodule ConferenceTalkCoding do
      def contained_in_range( x, min, max ) do
          min_percent = min |> Kernel.*(100) |> Kernel./(1)
          max_percent = max |> Kernel.*(100) |> Kernel./(1)
          x_percent = x |> Kernel.*(100) |> Kernel./(1)
          cond do
              x_percent  < min_percent -> false
              x_percent  > max_percent -> false
              true -> true
          end
      end
    end
    

    This is something that works, but why not just use the (vulgar):

    defmodule WorksForALiving do
      def contained_in_range( x, min, max ) do
        ( x  >= min ) && ( x <= max )
      end
    end
    

    Both do the same thing, but the latter doesn’t hide a math check inside of a bunch of redundant computation.

    Performance

    It’s hard to get people to care about performance these days, and odds are they aren’t in a field where it matters. That said, just stepping through a function and being like “okay, so, how many other functions are we calling? how fast are those functions? if we decide we need to make this run faster, what could we do?” can at least get the unenlightened to maybe start to think a little bit.

    1. 2

      “Sometimes, this means that certain classes of ideas (formal methods) are completely off-limits because they haven’t worked in systems that require them.”

      What do you mean by that? That formal methods didn’t produce results or that methods of teaching them didn’t?

      1. 4

        Oh, sorry–I meant that to be “e.g., formal methods”. I sometimes think faster than I type. :)

        Other examples would be things like:

        • the relevance of matvec/matmat math for implementing Conway’s Game of Life to a C programmer who only is used to normal if/then/switch code
        • curried functions to a Visual Basic programmer (perhaps a bad example…)
        • prolog-style pattern-matching to a JS developer
        • Unix-philosophy to heavy GUI users
        • proper memory allocation to die-hard Haskellers
        • using message-dispatch to allow for metaprogramming

        Basically, if your day-to-day means that you’ve never faced problems of a certain domain, it’s going to be really hard to convince you that they exist elsewhere, and that there is a solution worth learning.

        Until I was deeply stuck in Matlab, for example, I didn’t realize I could even consider treating generations of the game as matrices instead of just some clunky array with weird indexing and if-statements. Until I learned about method_missing I hadn’t seen just how easy it could be to make ergonomic APIs.

        Somebody trying to teach me those things would’ve had a hard time if I hadn’t at least had a problem in each of those cases that motivated me to look for a better answer.

        That’s why it’s important to always try to solve new problems!

        1. 2

          Alright. Appreciate the reply. That makes a lot of sense. It also illustrates the difficulty of adoption of solutions or recommendations that effectively require a huge, context switch. Finding ways to do that has occupied my mind in recent years more than before. It’s why I spent an hour or two on a way to bridge the gap between concepts and implementation in Rust after borrowing/ownership sections of docs gave me a headache. Sent it to Steve Klabnik. Recently, a Hamilton & Prolog fan vs market/evolutionary reality. I think we should put more effort into these efforts to bridge divides that make sense to the informed but seem nonsense to the rest. Something good might come of it.

    2. 2

      I think web frames what mostly figured this out

      “web frameworks”

      (I got confused for a minute there by the mobiletypeo)

      1. 2

        I like the layout of Fowler’s Refactoring book and the patterns within. He doesn’t try too hard to sell the values of the patterns, but shows the obective outcome of applying the patterns. To avoid the “I know best, do as I do”, he even provides patterns that are the opposite of each other.

        After doing that, you don’t really have to be explicit about the responsibility and consequences being the developer’s, not some all knowing author.

        I think you need to make the reader/student think and let them come up with the answers themselves, offering guidance and asking hard to answer questions, instead of pushing them into a certain belief. It is too easy to fall into religious arguments otherwise.

        1. 1

          Recursion: anything involving a tree, which has an inherently recursive structure. The most basic is counting the number of nodes in a tree, or determining whether a tree is binary. Factorial is not good because it is more naturally done with a loop.

          What work with formal methods have you done at your job? I feel like that is outside the experience of most working programmers. It feels a little out of place in the context of commenting in unit testing.