December 05, 2016 2:42 PM
Copying Interfaces, Copying Code
Khoi Vinh wrote a short blog entry called The Underestimated Merits of Copying Someone Else's Work that reminds us how valuable copying others' work, a standard practice in the arts, can be. At the lowest level there is copying at the textual level. Sometimes, the value is mental or mechanical:
Hunter S. Thompson famously re-typed, word for word, F. Scott Fitzgerald's "The Great Gatsby" just to learn how it was done.
This made me think back to the days when people typed up code they found in Byte magazine and other periodicals. Of course, typing a program gave you more than practice typing or a sense of what it was like to type that much; it also gave you a working program that you could use and tinker with. I don't know if anyone would ever copy a short story or novel by hand so that they could morph it into something new, but we can do that meaningfully with code.
I missed the "copy code from Byte" phase of computing. My family never had a home computer, and by the time I got to college and changed my major to CS, I had plenty of new programs to write. I pulled ideas about chess-playing programs and other programs I wanted to write from books and magazines, but I never typed up an entire program's source code. (I mention one of my first personal projects in an old OOPSLA workshop report.)
I don't hear much these days about people copying code keystroke for keystroke. Zed Shaw has championed this idea in a series of introductory programming books such as Learn Python The Hard Way. There is probably something to be learned by copying code Hunter Thompson-style, feeling the rhythm of syntax and format by repetition, and soaking up semantics along the way.
Vinh has a more interesting sort of copying in mind, though: copying the interface of a software product:
It's odd then to realize that copying product interfaces is such an uncommon learning technique in design. ... it's even easier to re-create designs than it is to re-create other forms of art. With a painting or sculpture, it's often difficult to get access to the historically accurate tools and materials that were used to create the original. With today's product design, the tools are readily available; most of us already own the exact same software employed to create any of the most prominent product designs you could name.
This idea generalizes beyond interfaces to any program for which we don't have source code. We often talk about reverse engineering a program, but in my experience this usually refers to creating a program that behaves "just like" the original. Copying an interface pixel by pixel, like copying a program or novel character by character, requires the artist to attend to the smallest details -- to create an exact replica, not a similar work.
We cannot reverse engineer a program and arrive at identical source code, of course, but we can try to replicate behavior and interface exactly. Doing so might help a person appreciate the details of code more. Such a practice might even help a programmer learn the craft of programming in a different way.
November 27, 2016 10:26 AM
Good Teaching Is Grounded In Generosity Of Spirit
In My Writing Education: A Time Line, George Saunders recounts stories of his interactions with writing teachers over the years, first in the creative writing program at Syracuse and later as a writer and teacher himself. Along the way, he shows us some of the ways that our best teachers move us.
Here, the teacher gets a bad review:
Doug gets an unkind review. We are worried. Will one of us dopily bring it up in workshop? We don't. Doug does. Right off the bat. He wants to talk about it, because he feels there might be something in it for us. The talk he gives us is beautiful, honest, courageous, totally generous. He shows us where the reviewer was wrong -- but also where the reviewer might have gotten it right. Doug talks about the importance of being able to extract the useful bits from even a hurtful review: this is important, because it will make the next book better. He talks about the fact that it was hard for him to get up this morning after that review and write, but that he did it anyway. He's in it for the long haul, we can see.
I know some faculty who basically ignore student assessments of their teaching. They paid attention for a while at the beginning of their careers, but it hurt too much, so they stopped. Most of the good teachers I know, though, approach their student assessments the way that Doug approaches his bad review: they look for the truths in the reviews, take those truths seriously, and use them to get better. Yes, a bad set of assessments hurts. But if you are in it for the long haul, you get back to work.
Here, the teacher gives a bad review:
What Doug does for me in this meeting is respect me, by declining to hyperbolize my crap thesis. I don't remember what he said about it, but what he did not say was, you know: "Amazing, you did a great job, this is publishable, you rocked our world with this! Loved the elephant." There's this theory that self-esteem has to do with getting confirmation from the outside world that our perceptions are fundamentally accurate. What Doug does at this meeting is increase my self-esteem by confirming that my perception of the work I'd been doing is fundamentally accurate. The work I've been doing is bad. Or, worse: it's blah. This is uplifting -- liberating, even -- to have my unspoken opinion of my work confirmed. I don't have to pretend bad is good. This frees me to leave it behind and move on and try to do something better. The main thing I feel: respected.
Sometimes, students make their best effort but come up short. They deserve the respect of an honest review. Honest doesn't have to be harsh; there is a difference between being honest and being a jerk. Sometimes, students don't make their best effort, and they deserve the respect of an honest review, too. Again, being honest doesn't mean being harsh. In my experience, most students appreciate an honest, objective review of their work. They almost always know when they are coming up short, or when they aren't working hard enough. When a teacher confirms that knowledge, they are freed -- or motivated in a new way -- to move forward.
Here, the teacher reads student work:
I am teaching at Syracuse myself now. Toby, Arthur Flowers, and I are reading that year's admissions materials. Toby reads every page of every story in every application, even the ones we are almost certainly rejecting, and never fails to find a nice moment, even when it occurs on the last page of the last story of a doomed application. "Remember that beautiful description of a sailboat on around page 29 of the third piece?" he'll say. And Arthur and I will say: "Uh, yeah ... that was ... a really cool sailboat." Toby has a kind of photographic memory re stories, and such a love for the form that goodness, no matter where it's found or what it's surrounded by, seems to excite his enthusiasm. Again, that same lesson: good teaching is grounded in generosity of spirit.
It has taken me a long time as a teacher to learn to have Toby's mindset when reading student work, and I'm still learning. Over the last few years, I've noticed myself trying more deliberately to find the nice moments in students' programs, even the bad ones, and to tell students about them. That doesn't mean being dishonest about the quality of the overall program. But nice moments are worth celebrating, wherever they are found. Sometimes, those are precisely the elements students need to hear about, because they are the building blocks for getting better.
Finally, here is the teacher talking about his own craft:
During the Q&A someone asks what Toby would do if he couldn't be a writer.
A long, perplexed pause.
"I would be very sad", he finally says.
I like teaching computer science, but what has enabled me to stay in the classroom for so many years and given me the stamina to get better at teaching is that I like doing computer science. I like to program. I like to solve problems. I like to find abstractions and look for ways to solve other problems. There are many things I could do if I were not a computer scientist, but knowing what I know now, I would be a little sad.
November 23, 2016 3:17 PM
Patterns and Motives
This week, I read a cool article that covered a lot of ground: Feynman diagrams, experiments at the Large Hadron Collider, algebraic geometry, pendulums, and periods. This paragraph even made me think about software:
That same answer -- the unique thing at the center of all these cohomology theories -- was what Grothendieck called a "motive". "In music it means a recurring theme. For Grothendieck a motive was something which is coming again and again in different forms, but it's really the same," said Pierre Cartier, a mathematician at the Institute of Advanced Scientific Studies outside Paris and a former colleague of Grothendieck's.
Something that comes again and again in different forms but is really the same thing... That sounds like a design pattern. Software patterns are quite different than numeric periods in algebra and geometry, but the idea feels familiar. The analogy to music feels familiar, too.
The article then says:
Motives are in a sense the fundamental building blocks of polynomial equations, in the same way that prime factors are the elemental pieces of larger numbers.
This is how I have often thought of elementary design patterns in software: as the elemental particles out of which all software is constructed. I'd like to think these thoughts again more actively in my programming languages course, when my students and I begin to learn and do functional programming.
November 20, 2016 10:17 AM
Celebrating a Friend's Success
Last week, I read a blog entry by Ben Thompson that said Influence lives at intersections. Thompson was echoing a comment about Daniel Kahneman's career: "Intellectual influence is the ability to dissolve disciplinary boundaries." These were timely references for my week.
On Friday night, I had the pleasure of attending the Heritage Honours Awards, an annual awards dinner hosted by my university's alumni association. One of our alumni, Wade Arnold, received the Young Alumni Award for demonstrated success early in a career. I mentioned Wade in a blog entry several years ago, when he and I spoke together at a seminar on interactive digital technologies. That day, Wade talked about intersections:
It is difficult to be the best at any one thing, but if you are very good at two or three or five, then you can be the best in a particular market niche. The power of the intersection.
Wade built his company, Banno, by becoming very good at several things, including functional programming, computing infrastructure, web development, mobile development, and financial technology. He was foresightful and lucky enough to develop this combination of strengths before most other people did. Most important, though, he worked really hard to build his company: a company that people wanted to work with, and a company that people wanted to work for. As a result, he was able to grow a successful start-up in a small university town in the middle of the country.
It's been a delight for me to know Wade all these years and watch him do his thing. I'll bet he has some interesting ideas in store for the future.
The dinner also provided me with some unexpected feelings. Several times over the course of the evening, someone said, "Dr. Wallingford -- I feel like I know you." I had the pleasure of meeting Wade's parents, who said kind things about my influence on their son. Even his nine-year-old son said, "My dad was talking about you in the car on the drive over." No one was confused about whom we were there to honor Friday night, about who had done the considerable work to build himself into an admirable man and founder. That was all Wade. But my experience that night is a small reminder to all you teachers out there: you do have an effect on people. It was certainly a welcome reminder for me at the end of a trying semester.
November 03, 2016 3:53 PM
TIL Today: The Power of Limiting Random Choices
This morning I read this blog post by Dan Luu on the use of randomized algorithms in cache eviction. He mentions a paper by Mitzenmacher, Richa, and Sitaraman called The Power of Two Random Choices that explains a cool effect we see when we limit our options when choosing among multiple options. Luu summarizes the result:
The mathematical intuition is that if we (randomly) throw n balls into n bins, the maximum number of balls in any bin is O(log n / log log n) with high probability, which is pretty much just O(log n). But if (instead of choosing randomly) we choose the least loaded of k random bins, the maximum is O(log log n / log k) with high probability, i.e., even with two random choices, it's basically O(log log n) and each additional choice only reduces the load by a constant factor.
Luu used this result to create a cache eviction policy that outperforms random eviction across the board and competes closely with the traditional LRU policy. It chooses two pages at random and evicts the least-recently used of the two. This so-called 2-random algorithm slightly outperforms LRU in larger caches and slightly underperforms LRU in smaller caches. This trade-off may be worth making because, unlike LRU, random eviction policies degrade gracefully as loops get large.
The power of two random choices has potential application in any context that fits the balls-and-bins model, including load balancing. Luu mentions less obvious application areas, too, such as circuit routing.
Very cool indeed. I'll have to look at Mitzenmacher et al. to see how the math works, but first I may try the idea out in some programs. For me, the programming is even more fun...
November 01, 2016 4:04 PM
An Adventure with C++ Compilers
I am a regular reader of John Regehr's blog, which provides a steady diet of cool compiler conversation. One of Regehr's frequent topics is undefined behavior in programming languages, and what that means for implementing and testing compilers. A lot of those blog entries involve C and C++, which I don't use all that often any more, so reading them is more spectator sport than contact sport.
This week, I got see how capricious C++ compilers can feel up close.
My students are implementing a compiler for a simple subset of a Pascal-like language. We call the simplest program in this language print-one:
$ cat print-one.flr program main(); begin return 1 end.
One of the teams is writing their compiler in C++. The team completed its most recent version, a parser that validates its input or reports an error that renders its input invalid. They were excited that it finally worked:
$ g++ -std=c++14 compiler.cpp -o compiler $ compiler print-one.flr Valid flair program
They had tested their compiler on two platforms:
- a laptop running OS X v10.11.5 and gcc v4.9.3
- a desktop running Ubuntu v14.04 and gcc v4.8.4
I sat down at my desktop computer to exercise their compiler.
$ g++ compiler.cpp -o compiler In file included from compiler.cpp:7: In file included from ./parser.cpp:3: In file included from ./ast-utilities.cpp:4: ./ast-utilities.hpp:7:22: warning: in-class initialization of non-static data member is a C++11 extension [-Wc++11-extensions] std::string name = "Node"; ^ [...] 24 warnings generated.
Oops, I forgot the -std=c++14 flag. Still, it compiled, and all of the warnings come from a part of the code has no effect on program validation. So I tried the executable:
$ compiler print-one.flr ERROR at line #3 -- unexpected <invalid> 1 Invalid flair program
Hmm. The warnings are unrelated to part of the executable that I am testing, but maybe they are creating a problem. So I recompile with the flag:
$ g++ -std=c++14 compiler.cpp -o compiler error: invalid value 'c++14' in '-std=c++14'
What? I check my OS and compiler specs:
$ sw_vers -productVersion 10.9.5 $ g++ --version Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/usr/include/c++/4.2.1 Apple LLVM version 6.0 (clang-600.0.57) (based on LLVM 3.5svn) [...]
Oh, right, Apple doesn't ship gcc any more; it ships clang and link gcc to the clang exe. I know my OS is a bit old, but it still seems odd that the -std=c++14 flag isn't supported. I google for an answer (thanks, StackOverflow!) and find that that I need to use -std=c++1y. Okay:
$ g++ -std=c++1y compiler.cpp -o compiler $ compiler print-one.flr ERROR at line #3 -- unexpected <invalid> 1 Invalid flair program
Now the student compiler compiles but gives incorrect, or at least unintended, behavior. I'm surprised that both my clang and the students' gcc compile their compiler yet produce executables that give different answers. I know that gcc and clang aren't 100% compatible, but my students are using a relatively small part of C++. How can this be?
Maybe it has something to do with how clang processes the c++1y standard flag. So I backed up to the previous standard:
$ g++ -std=c++0x compiler.cpp -o compiler $ compiler print-one.flr ERROR at line #3 -- unexpected <invalid> 1 Invalid flair program
Yes, that's c++0x, not c++0y. The student compiler still compiles and still gives incorrect or unintended behavior. Maybe it is a clang problem? I upload their code to our student server, which runs Linux and gcc:
$ cat /etc/debian_version 8.1 $ g++ --version [...] gcc version 4.7.2 (Debian 4.7.2-5)
This version of gcc doesn't support either c++14 or c++1?, so I fell back to c++0x:
$ g++ -std=c++0x compiler.cpp -o compiler $ compiler print-one.flr Valid flair program
Hurray! I can test their code.
I'm curious. I have a Macbook Pro running a newer version of OS X. Maybe...
$ sw_vers -productVersion ProductName:Mac OS X ProductVersion:10.10.5 BuildVersion:14F2009 $ g++ --version Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/usr/include/c++/4.2.1 Apple LLVM version 7.0.2 (clang-700.1.81) [...]
$ g++ -std=c++14 compiler.cpp -o compiler $ compiler print-one.flr Valid flair program
Now, the c++14 flag works, and it produces a compiler that produces the correct behavior -- or at least the intended behavior.
I am curious about this anomaly, but not curious enough to research the differences between clang and gcc, the differences between the different versions of clang, or what Apple or Debian are doing. I'm also not curious enough to figure out which nook of C++ my students have stumbled into that could expose a rift in the behavior of these various C++ compilers, all of which are standard tools and pretty good.
At least now I remember what it's like to program in a language with undefined behavior and can read Regehr's blog posts with a knowing nod of the head.
October 30, 2016 9:25 AM
Which Part of Speech Am I?
I saw a passage attributed to Søren Kierkegaard that I might translate as:
The life of humanity could very well be conceived as a speech in which different people represented the various parts of speech [...]. How many people are merely adjectives, interjections, conjunctions, adverbs; how few are nouns, verbs; how many are copula?
This is a natural thing to ponder around my birthday. It's not a bad thing to ask myself more often: Which part of speech will I be today?
October 26, 2016 3:11 PM
One Way I'm Like Charles Darwin
According to Darwin himself, in his autobiography:
I have no great quickness of apprehension or wit which is so remarkable in some clever men, for instance, Huxley. I am therefore a poor critic: a paper or book, when first read, generally excites my admiration, and it is only after considerable reflection that I perceive the weak points.
If you read my blog, you know this about me. Either you enjoy my occasionally uncritical admiration, or at least you tolerate it.
October 25, 2016 3:33 PM
How to Write Code That Doesn't Get in the Way
Last week some tweeted a link to Write code that is easy to delete, not easy to extend, an old blog entry by @tef from last February. When I read it yesterday, I was nodding my head so hard that I almost fell off of the elliptical machine. I have done that before. Trust me, you don't want to do it. You don't really fall; the machine throws you. If you are moving fast, it throws you hard.
I don't gush over articles in my blog as much these days as I once did, but this one is worthy. If you write code, go read this article. Nothing I write hear will replace reading the entire piece. For my own joy and benefit, though, I record a few of my favorite passages here -- along with comments, as I am wont to do.
... if we wish to count lines of code, we should not regard them as "lines produced" but as "lines spent".
This is actually a quote from EWD 1036, one of Dijkstra's famous notes. I don't always agree with EWD, but this line is gold and a perfect tagline for @tef's entry
Building reusable code is easier to do in hindsight with a couple of examples of use in the code base, than foresight of ones you might want later.
When OO frameworks first became popular, perhaps the biggest mistake that developers made was to try to write a framework up front. Refactoring from multiple programs is still the best way for most of us mortals to create a framework. This advice also applies to cohesive libraries of functions.
Aside: Make a util directory and keep different utilities in different files. A single util file will always grow until it is too big and yet too hard to split apart. Using a single util file is unhygienic.
Golf clap. I have this pattern. I am glad to know that others do, too.
Boiler plate is a lot like copy-pasting, but you change some of the code in a different place each time, rather than the same bit over and over.
For some reason, reading this made me think of copy-and-paste as a common outcome of programming language design, if not its intended effect.
Boilerplate works best when libraries are expected to cater to all tastes, but sometimes there is just too much duplication. It's time to wrap your flexible library with one that has opinions on policy, workflow, and state. Building simple-to-use APIs is about turning your boilerplate into a library.
Again, notice the role refactoring plays here. Build lots of code that works, then factor out boilerplate or wrap it. The API you design will be informed by real uses of the functions you define.
It is not so much that we are hiding detail when we wrap one library in another, but we are separating concerns: requests is about popular http adventures; urllib3 is about giving you the tools to choose your own adventure.
One of the things I like about this blog entry is its theme of separating concerns. Some libraries are perfect when you are building a common application; others enable you to build your own tools when you need something different.
A lot of programming is exploratory, and it's quicker to get it wrong a few times and iterate than think to get it right first time.
Agile Development 101. Even when I know a domain well, if the domain affords me a lot of latitude when building apps, I like explore and iterate as a way to help me choose the right path for the current implementation.
[O]ne large mistake is easier to deploy than 20 tightly coupled ones.
And even more, as @tef emphasizes throughout: It's easier to delete, too.
Becoming a professional software developer is accumulating a back-catalogue of regrets and mistakes.
When we teach students to design programs in their first couple of years of CS, we often tell them that good design comes from experience, and experience comes from bad design. An important step in becoming a better programmer is to start writing code, as much as you can. (That's how you build your catalog of mistakes.) Then think about the results. (That's how you turn mistakes into experience.)
We are not building modules around being able to re-use them, but being able to change them.
This is one of the central lessons of software development. One of the things I loved about OO programming was that it gave me another way to create modules that isolated different concerns from one another. So many folks make the mistake of thinking that objects, classes, and even frameworks are about reuse. But reuse is not the key; separation of concerns is. Design your objects that create shearing layers within your program, which make it easier to change the code.
It isn't so much that you're iterating, but you have a feedback loop.
As I blogged recently, competence is about creating conditions that minimize mistakes but also help you to recognize mistakes quickly and correct them. You don't iterate for the sake of iterating. You iterate because that's how you feed learning back into the work.
The strategies I've talked about [...] are not about writing good software, but how to build software that can change over time.
This blog entry isn't a recipe for writing good code. It's a recipe for creating conditions in which you can write good code. I do claim, though, that all other things being reasonably equal, in most domains, code that you can change is better code than code you can't change.
Good code isn't about getting it right the first time. Good code is just legacy code that doesn't get in the way.
That is a Kent Beck-caliber witticism: Good code is just legacy code that doesn't get in the way.
This blog entry made me happy.
October 22, 2016 2:00 PM
Competence and Creating Conditions that Minimize Mistakes
I enjoyed this interview with Atul Gawande by Ezra Klein. When talking about making mistakes, Gawande notes that humans have enough knowledge to cut way down on errors in many disciplines, but we do not always use that knowledge effectively. Mistakes come naturally from the environments in which we work:
We're all set up for failure under the conditions of complexity.
Mistakes are often more a matter of discipline and attention to detail than a matter of knowledge or understanding. Klein captures the essence of Gawande's lesson in one of his questions:
We have this idea that competence is not making mistakes and getting everything right. [But really...] Competence is knowing you will make mistakes and setting up a context that will help reduce the possibility of error but also help deal with the aftermath of error.
In my experience, this is a hard lesson for computer science students to grok. It's okay to make mistakes, but create conditions where you make as few as possible and in which you can recognize and deal with the mistakes as quickly as possible. High-discipline practices such as test-first and pair programming, version control, and automated builds make a lot more sense when you see them from this perspective.