21 October 2014

The second draft of Effective Python done!

Thanks to Brett Cannon, Tavis Rudd, and Bear for their technical guidance.

Now: one last polish for the final draft.
"Learn to do something yourself first before having someone do it for you." -Paul McDonald

19 October 2014

c2

Why is it called a Mix-in? Because they had those at "Steve's Ice Cream, a favorite ice cream shop of MIT students". That and 1000 other answers are on c2. I never realized how important c2 is for new programmers to gain context.

07 October 2014

The Peripheral

Look what I scrounged up! A bit worn by the last reader, but totally worth it. Gibson's traveling around on his book tour now, so check your local bookstores to see if he's coming through your area. Can't wait to read it!

06 October 2014

Python is #8 in the TIOBE index (October 2014), #4 in the Redmonk ranking (June 2014), and #3 in the Gartner index (October 2014).

Go is #42, #21, and #24, respectively. Rust is only on the Redmonk list at #58. Swift is #19, #68, and #29. Dart is #17, #38, and #22.

The methodology of this is always questionable but it's still fun to see languages grow and others fade.

What'd be fun is a relative measure of rate of change. Then you'd at least have something to normalize to so you could compare the rates of change or direction of change across indexes.

02 October 2014

Armin Ronacher shares some of his experience with Rust.
Wow RAW is awesome! It's a wysiwyg UI in front of D3.

Custom faces for smart watches

After some initial misgivings about Android watches, I've been having a lot of fun with Facer, a $1 app that lets you customize your watch face. There's a built-in library of faces:



And Facer allows you to create your own. People are getting really deep into this. There's a whole site called FaceRepo dedicated to hosting people's creations. There's also an official and very active subreddit just for Facer users. People are doing silly things like recreating the Mickey Mouse watch and Rolexes.

Of course I had to try this out, even though I'm not a big watch person (this is the other watch I own). I made a bunch of middling faces to learn how to use the tool and figure out what's possible. For a few days there I was obsessed with tweaking my designs. It's pretty fun! Here's the best minimalist face I could come up with. It's called "Monoid". You can get it here.



Facer is totally worth the $1!

Trying an Android watch

I bought an LG G Watch after they were launched at the Google I/O conference this year (June 25th).



There were a lot of reasons not to. At the time the reviews said the Samsung Galaxy Gear Live was better. Months before that, in March, the Moto 360 teaser went out and everyone got excited about round faces. The rumors of the Apple Watch release were already solidified (and indeed, they've since pre-announced it). And generally the whole ideal of a smart watch seems stupid, excessive, and wasteful.

But I wanted to give it a try! Wearable computing is supposed to be the future (?). I wore the watch for a few weeks.

What I liked:
  • I could stop checking my phone for notifications
  • I could send text messages by voice from my wrist at a stop light while biking
  • The watch could reliably tell time

What I didn't like:
  • It was illegible in the sun
  • Once you dismissed a notification there's no way to get it back
  • The microphone didn't deal well with wind or noise

I put it down for a couple months. The watch was reasonably useful, but I felt like it's still a version one product. I anticipated the Moto 360 would be awesome and the Apple Watch would redefine the category. Now both of those are here and they're not as good as I expected. The Moto 360 has a fatal flaw (the "flat tire" at the bottom of the screen) and it's round face causes status message to be cut off in dumb ways (like the HD TV effect).

The Apple Watch is absurdly complicated: it's got buttons; it's got a wheelie thing; the touch screen has a sense of pressure — Steve Jobs is rolling in his grave. I found that the Apple Watch and LG G are about the same size and thickness. The LG G Watch has no buttons, only a touch screen and contacts to charge it.

After seeing all this I put the LG G Watch back on. It's still a version one product, but at least it's improved since I last used it. The latest Android Wear software is better. Strava works well with the watch now. And there are more apps to try, such as Facer. I'll give it another month and see if it sticks.

01 October 2014

Metrics from the first draft

It took me 683 commits to write the first draft of Effective Python over the course of 132 days (~4 months). That includes all the work for tools I built, the website, notes, and code examples. I've been storing the content in a private GitHub repo. Here are some fun metrics from it.

Commit density by day:

The majority of my commits are on the weekends.



Commits per week:

I was offline quite a lot in September.



Commits by hour of day / day of week:

Clearly I have been staying up way too late working on this thing.



What will be interesting is how many commits it takes to do revisions and get the book into a publishable state.

30 September 2014

Microsoft launches Prediction Lab, a follow-up to their Xbox election work in 2012.
Cool example of Generics in Go that works by dynamically providing remote repositories for import. Quite a hack!

29 September 2014

The first draft of Effective Python is done. 55,000 words, 8 chapters, 58 items. Now begins the revision process. Hopefully I can polish this into something worth reading.

22 September 2014

Returning from a vacation

28 August 2014

24 August 2014

I want the Kardashian game, but starring Elon Musk.

How to Internet

  1. Read a cool blog post you find interesting
  2. Leave a supportive comment and link to related ideas
  3. Receive an email from the post's author saying your comment is spam
  4. They delete your comment

Ahh... wondrous delight. Just another day.

Tweet storms

1/ Tweets that are numbered are annoying. Please stop being lazy and use a blog.

2/ If it's not important enough for you to edit, it's not important enough for us to read.

Just stop! You're wasting everyone's time.

19 August 2014

The Data Triumvirate

There's a huge focus right now on building products and services that do data analysis. Developing these systems involves three distinct groups of stakeholders that have opposing viewpoints.

  • The product managers are trying to sell something. They want the data to show what they're selling is working for someone (themselves, customers, end-users). They want impact.
  • The statisticians are trying to ensure correctness. They want the data to be unbiased. They want the methodology for finding results in the data to be defensible to their peers.
  • The engineers are trying to ship the simplest thing possible. They want to minimize the complexity of analyzing the data. They want a data pipeline that is maintainable and extensible.

The tension between these roles is crucial. If one outlook dominates a joint effort you're setting yourself up for failure.

  • If the product managers always get their way you're letting a fox guard the hen house. They'll find significance in the data at the expense of bias and methodological validity. You'll be selling snake oil.
  • If the statisticians get their way you'll never ship your product. Compensating for every bias in a dataset is nearly impossible. You'll never have the 99% confidence they want for every measure.
  • If the engineers get their way your product will be too simplistic. The most maintainable implementation will undermine the statistical methods. The impact measured won't be compelling enough to sell.

If you're doing analysis, make sure you're part of a data triumvirate. It's similar to the relationship between product managers and tech leads. You need a balance of power to build the right thing.

16 August 2014

Damn! If only there were more static site generators to choose from.

07 August 2014

It's "Temps Universel Coordonné" or "Coordinated Universal Time" — Why do we call it UTC?

Python, C++, and Go as bicycles

I'm still trying to find the boundaries between Python, C++, and Go when building something new. The split between Python and C++ is a clear tradeoff of bare-metal performance for developer productivity. The split between Go and C++ is easy for me because of scars from templates and the difficulty of concurrent programming in C++.

What's been difficult is finding the dividing line between Python and Go. What I've been able to come up with so far is a trite analogy involving types of bicycles.

Python is a touring bicycle. It's approachable and easy for anyone to learn how to ride. It has features like a basket, fenders, lights, and a pump that make it practical for almost every situation. Its limited gears mean it's dependable, but slow unless you pedal hard.



C++ is a race bike. It's difficult to ride and easy to crash. It has multiple sets of handlebars and every other feature a bike could offer for maximum speed. Its fragility makes it impractical for simple riding, but it can get you there faster than anything else.



Go is a modern cyclo-cross bike. It's simpler and safer than a race bike, but still fast. It has most of the features you want (light-weight, aerodynamic) and some that are uniquely surprising (durability, knobby tires). It's a whole new category of riding.



Continuing with the bad joke, Java would be the bicycle factory factory. Okay — the analogy breaks down quickly. But it's not so awful.

For an unexpected adventure I'd choose the touring bike (Python): quickly building something new, handling all environments easily, everything I need built-in. For competing on a more well-defined course with treacherous obstacles I'd choose the modern cyclo-cross bike (Go): speed, versatility, safety. For a time trial on smooth roads with no obstacles I may even risk riding the race bike (C++).

That's the best assessment I've got for now. Where do you draw the line?

06 August 2014

from turtle import *

Did you know Python has turtle built-in?

05 August 2014

Quilla now uses HTTPS by default for everything.

04 August 2014

My last deploy of Quilla was 528 days, 0:39:22 ago. I have done zero maintenance in that time. This is the dream of PaaS.

From plastic 3D print to milled aluminum — Making a bicycle gear shifter

Earlier this year I spent 30 days designing and 3D-printing a bicycle gear shifter for the Shimano 8 speed internal hub. The goal was to make one for my Mission Bicycle. I wrote about the experience here. After posting that story I worked with the team to revise the design. Here's the result as a plastic prototype. I've been pedaling around with this for the past few months.



My next goal was to find a machinist to turn this part into 6061 aluminum for strength and durability. I searched around the SF Bay Area and found a few machine shops. A couple folks I contacted never responded. One replied but declined because they couldn't open my 3D design files. Another met me in person, but wasn't excited about the project (his primary business is building things that go into space!).

Finally, a friend of mine connected me to someone whose day job is running a machine shop. For the sake of privacy I'll refer to him as "the machinist". It's important to note that there's no way any of what follows would have happened without their work. I'm grateful for all of their effort on the project. I've just been along for the ride.


Design for manufacturing

The machinist also had problems opening my CAD files (from 123D Design — $10/month). The issue is the DWG files exported by one company's software can't always be imported by another company's software. There are tools out there for format conversions (like Teigha) but they aren't good enough. Professionals seem to use Solidworks (MSRP: $5000) over Autodesk. It's the Cadillac of CAD. I can't afford it.

Luckily, I found a free tool by the makers of Solidworks called Draftsight that can be used to open and annotate the DWG files from Autodesk products. As long as it could be opened in Draftsight it could also be opened in Solidworks. When it didn't work I knew something in my model was funky and needed to be fixed.

Once the machinist could open my files we went through another cycle of design tweaking. There were a lot of things I thought were simple to mill out of aluminum that turned out to be impossible. Machining is really an art form like blacksmithing. It's full of arcane knowledge. The only way to learn is to actually use the tools and gain experience from your mistakes. Here's the final design for manufacturing, which looks better than the original.




Milling the part

With the final design in hand I bought all of the parts to build and assemble a prototype. I got the screws, washers, and ball springs from McMaster-Carr. I bought aluminum from TCI Aluminum. Machinists call raw uncut aluminum blocks "blanks". A blank is the starting material that is cut down to the final part by the CNC machine. Here's what my blanks looked like.



You normally buy 12 foot long bars of aluminum that best fit the profile of your part. Then you cut it yourself to size. Here's what that process looks like. It's quite a saw.



Here is the inside of the Haas VF-2SSYT 3-axis mill (MSRP: $72,000 — as configured $100,000+) that we used.



The control panel on the right lets you modify the program before or while it runs. What you see in the middle on a spindle is the business end of the machine. Here's a picture of the 3 inch diameter facing endmill. It's hardened steel and extremely heavy. It has carbide inserts so it doesn't wear down too quickly.



While the mill is doing its work it needs to change tools for particular cuts. To do this it has an enormous cache of end mills that can be changed automatically by the machine. This lets the machine cut, drill, face, and tap without human intervention. The machine uses a robot arm and compressed air fittings to swap the bits. It takes less than a second to swap (insane!). Here's a picture of the revolving cache, about 10 feet off the ground.



The mill is a dumb machine like a computer. It runs programs in G-Code, which describe where to move the cutting bits in XYZ coordinates. The mill does little to ensure the G-Code is valid. If the G-Code said to bash the bit into the vise the machine would do it. It's similar to how a bad set of instructions can crash a computer program.

The G-Code comes from CAD software that's the machinist's equivalent of a compiler (Mastercam and HSMWorks are popular tools). The CAD lets you build a set of "operations". An operation is all of the various cuts you need to make in one side of an aluminum blank. This can include multiple passes by many different types of cutting tools. The software doesn't automatically convert your 3D model into operations. You need to manually look at the outlines of the part and come up with a series of "tool paths" for the cutting blades to follow. The hope is the cuts will result in your design by subtracting from the aluminum blank. Machinists call this conversion process "programming".

One of the coolest things the design software can do is simulate the mill in action. It's like a highly sophisticated interactive debugger. Here's what it looks like.



Once you have the G-Code you put it on a USB stick and upload it to the mill. Then you put the blank in the machine and clamp it down with a vise. You bang it with a dead blow hammer to make sure it's seated properly. Then you use the mill control panel and it's 1000 buttons to tell the machine to measure the XYZ coordinate of one of the blank's corners. This tells the mill where to start cutting so you get exactly the physical object that you expect. Here's one blank in the vise.



You can see aluminum shavings all over the inside of the mill. Those are the "chips" from the end mill. There's also this weird fluid on everything. That's lubricating coolant to ensure the end mills don't overheat while they're spinning at 8000+ RPM and cutting. When the machine's actually going you can't see anything because coolant is everywhere. But the cutting sound is extremely loud. Here's a video of the machine making a cut.



The bike shifter ended up being 7 operations in all. 3 were for the base that goes over the steering tube. 4 were for the knob that pulls the shifter cable. The design also required 3 different pairs of "soft jaws", which are essentially jigs that hold the aluminum blanks in place to handle mating surfaces that aren't square.

I was surprised by how important it is to hold the aluminum blank properly in the mill. It significantly affects how you choose the order of your operations. Each operation must leave on enough "fixture stock" to let you grip the part from the opposing side until everything is square or fits in a soft jaw. Here you can see each step in cutting the shifter's base piece and the various fixtures.







The shifter knob provided an additional challenge because it was important that all of the cuts for the center hole and ball springs were concentric. Here's what the blank looked like after each operation for the knob.








Built and biking

At last! It's all done. Here's the shifter actually mounted on my bicycle.





And here's what it looks like from the top when you're riding it.



For the next month we'll try these prototypes and see how they feel. We'll probably make some design revisions and update the G-Code programs accordingly. We'll get some anodized in black. Once that's done I'm hoping we'll do a manufacturing run at a local machine shop. I'd like to start putting these on Mission Bikes and selling them to anyone who likes the Nexus 8 hub. Let me know if you're interested in getting one!

03 August 2014

Don't fall in love with your tools.

31 July 2014

How I'm writing a programming book

I've been working on Effective Python for just over two months now. The plan is to have 8 chapters in total. I've written a first draft of 5 so far. Chapter 3, the first one I wrote, was the hardest for many reasons. I had to establish a consistent voice for talking about Python. I took on the most difficult subjects first (objects and metaclasses) to get that work out of the way. I also had to build tools to automate my workflow for writing.

Each chapter consists of a number of short items that are about 2-4 pages in length. The title of an item is the "what", the shortest possible description of its advice (e.g., "Prefer Generators to Returning Lists"). The text of the item is the "why", the explanation that justifies following the advice. It's important to make the core argument for each item using Python code. But it's also important to surround the code with detailed reasoning.

Before I started I read a nice retrospective on how one author wrote their programming book. They had separate source files for the example code and used special comments to stitch it into the book's text at build time. That's a great idea because it ensures the code that's in the book definitely compiles and runs. There's nothing worse in programming tutorials than typing in code from the text and having it barf.

I wanted to go a step further. I wanted my examples to be very short. I wanted to intermix code and prose more frequently, so the reader could focus on smaller pieces one step at a time. I wanted to avoid huge blocks of code followed by huge blocks of prose. I needed a different approach.


Writing workflow

After some experimenting what I landed on is a script that processes GitHub Flavored Markdown. It incrementally reads the input Markdown, finds blocks that are Python code, runs them dynamically, and inserts the output into a following block.

Here's an example of what the input Markdown looks like:

The basic form of the slicing syntax is `list[start:end]`
where `start` is inclusive and `end` is exclusive.

```python
a = [1, 2, 3, 4, 5, 6, 7, 8]
print('First four:', a[:4])
print('Last four: ', a[-4:])
print('Middle two:', a[3:-3])
```

```
First four: [1, 2, 3, 4]
Last four:  [5, 6, 7, 8]
Middle two: [4, 5]
```

When slicing from the start of a list you should leave
out the zero index to reduce visual noise.

```python
assert a[:5] == a[0:5]
```

I write the files in Sublime Text. When I press Command-B it builds the Markdown by running my script, which executes all the Python, inserts the output back into the text, and then overwrites the original file in-place. This makes it easy to develop the code examples at the same time I'm writing the explanatory prose. It feels like the read/eval/print loop of an interactive Python shell.

My favorite part is how I made Python treat the Markdown files as input source code. That means when there's an error in my examples and an exception is raised, I'll get a traceback into the Markdown file at exactly the line where the issue occurred.

Here's an example of what that looks like in the Sublime build output:

Traceback (most recent call last):
  File ".../Slicing.md", line 29, in 
    a[99]
IndexError: list index out of range

It's essentially iPython Notebook, but tuned for my specific needs and checked into a git repo as Markdown flat files. Update: A couple people mentioned that this is a variation of Knuth's Literate Programming. Indeed it is!


Publishing workflow

Unfortunately, my deliverable for each chapter must be a Microsoft Word document. As a supporter of open source software and open standards this requirement made me wince when I first heard it. But the justification is understandable. The publisher has a technical book making system that uses Word-based templates and formatting. They have their own workflow for editing and preparing the book for print. This is the reality of desktop publishing. More modern tools like O'Reilly Atlas exist, but they are new and still in beta.

There is no way I'm going to manually convert my Markdown files into Word files. The set of required paragraph and character styles is vast and complicated. These styles are part of why the published book will look good, but it's tedious work that's easy to get wrong. Sounds like the perfect job for automation!

I have a second script that reads the input Markdown (using mistune) and spits out a Word .docx file (using python-docx). The script has a bunch of rules to map Markdown syntax to Word formatting. The script also passes all of the Python code examples through the Python lexer to generate syntax highlighting in the resulting document.

The other important thing the publishing script does is post-process the source code. Often times in an example there are only two lines out of 20 I need to show to the reader to demonstrate my point. The other 18 lines are for setup and ensuring the example actually demonstrates the right thing (testing). So I have special directives in the code as comments that can hide lines or collapse them with ellipses.

Here's an example of what this looks like in the Markdown file:

```python
class MissingPropertyDB(object):
    def __getattr__(self, name):
        if name == 'missing':
            raise AttributeError('That property is missing!')
        # COMPRESS
        value = 'Value for %s' % name
        setattr(self, name, value)
        return value
        # END

data = MissingPropertyDB()
# HIDE
data.foo  # Test the success case
# END
try:
    data.missing
except AttributeError as e:
    pretty(e)
```

The actual output in the published book would look like this:

class MissingPropertyDB(object):
    def __getattr__(self, name):
        if name == 'missing':
            raise AttributeError('That property is missing!')
        # ...

data = MissingPropertyDB()
try:
    data.missing
except AttributeError as e:
    pretty(e)

>>>
AttributeError('That property is missing!',)


Conclusion

If you have any interest in using these tools let me know! Writing a book is already hard enough. Having a good workflow helps a lot. I'd like to save you the trouble. Otherwise, if you have any suggestions on what I should put in the book, please email me here.

17 July 2014

Inside the Gopher's Studio

A highlight from GopherCon earlier this year was "Inside the Gopher's Studio":



It's all good to watch, but especially this gem from Rob Pike at 3:49:

"Most people don't know that when I got to Bell Labs Bjarne Stroustrup was my officemate. Uh-- we didn't get along."

Set YouTube to 0.5 speed to make it sound like a drunken story at the bar (in vino veritas).
Somehow came across the Padovan sequence when looking up variations on coprimes.



Looks like a nautilus, which also happens to be my favorite magazine right now.

More examples of perceptual diffs

I've found a few more examples of people using perceptual diff tools to do visual regression testing.


Saw this week that Twitter's trying out dpxdt as well.

10 July 2014

Met self-proclaimed "senior software architect" who'd never heard of Go. Red flag. Curiosity is a defining trait of great programmers.

Generics in Go via "generate"

I'm less interested in generics in Go after reading this intro to Boost, the C++ template library. The resulting code is impenetrable to anyone but an expert. I don't think generalization is as important as computer scientists say it is. Approachability should be the biggest concern given that developing software is almost always a social problem, not a technical one.

Rob Pike's solution for generic programming in Go could be his proposal for the "generate" command in the Go toolchain. This addresses a whole range of requirements, including lexers, binary embedding, and protocol buffers. Templating for Go generics is just a natural consequence of the design. It'll be interesting to see if existing attempts at generics in Go move to use "generate" instead.

Python vs. Go at Dropbox

Dropbox rewrote some infrastructure and produced 200KLOC of Go. They open sourced their common Go libraries. That's great. Some folks are saying this means they've abandoned Python. I don't read it that way. Dropbox needs to reduce costs and scale up if they want to have an IPO. Moving to Go seems like a natural choice for a company heavily invested in Python. Brad says Go gives you "90% of the ease of scripting languages with 90% of the performance of systems languages." That sounds right to me, but it makes the choice between Python and Go even more murky. I'm still searching for the dividing line.

01 July 2014

This website is the (addictive) Amazon Prime equivalent for machine shops.
Current status: Requesting an estimate for 12 foot long, 43 pound bars of 6061 aluminum.

27 June 2014

Universal constructor

Related to visualizing algorithms, I rediscovered the Von Neumann universal constructor in cellular automata (like Conway's game of life). I had never appreciated the similarity of this machine's organization to DNA. From Wikipedia:

Von Neumann's crucial insight is that part of the replicator has a double use; being both an active component of the construction mechanism, and being the target of a passive copying process. This part is played by the tape of instructions in Von Neumann's combination of universal constructor plus instruction tape.

The combination of a universal constructor and a tape of instructions would i) allow self-replication, and also ii) guarantee that the open-ended complexity growth observed in biological organisms was possible. The image below illustrates this possibility.

This insight is all the more remarkable because it preceded the discovery of the structure of the DNA molecule by Watson and Crick, though it followed the Avery-MacLeod-McCarty experiment which identified DNA as the molecular carrier of genetic information in living organisms. The DNA molecule is processed by separate mechanisms that carry out its instructions and copy the DNA for insertion for the newly constructed cell. The ability to achieve open-ended evolution lies in the fact that, just as in nature, errors (mutations) in the copying of the genetic tape can lead to viable variants of the automaton, which can then evolve via natural selection.

Mike Bostock's huge page of visualizing algorithms is not to be missed.

25 June 2014

Effective Python

I'm overwhelmingly excited to be writing the Effective Python book, a follow-on to Scott Meyers' classic, Effective C++. I'm honored to have the opportunity to author this book. I first read Effective C++ when I was 15 years old. Scott's Effective books led to my 7 year obsession with C++ and my first job. At Google I learned Python. I've been building infrastructure and applications with it for the past 9 years. Hopefully I have valuable advice to share from my own experience and what I've learned from the Python community.

Like the original, my book will be ~50 specific pieces of advice, 2-4 pages each, on how to write better Python programs. It should be the second thing you read after wonderful introductory books like Alex Martelli's Python in a Nutshell (which is how I learned Python) and Zed Shaw's Learn Python the Hard Way. It will be a stepping stone towards Python mastery via more thorough references like Wesley Chun's Core Python books and the Python Cookbook.

The goal of Effective Python is to give readers a sense of the "right way" of writing Python code in general. It's not a language introduction, a cookbook, an encyclopedic reference, or a guide for a specific area like Django, NumPy, of Kivy. This book is for programmers who want to know the dirt, the real stuff, the guidance of hard-won experience. It should transcend specific problem domains. This is what made Effective C++ so awesome.

I'll do my best to follow Scott's advice in writing it. Of course I'm terrified of leaving anything out. If you have tips or ideas for things I shouldn't miss, please send me a note using this link. Thanks in advance!

23 June 2014

Programming is an obsession with esoteric knowledge.

22 June 2014

Impressive survey attempting to understand why Erlang has an adoption problem.
A team at Google released FlatBuffers, yet another data encoding and IDL. It makes the same important tradeoff as Cap'n Proto: There is no encoding or decoding; how it's serialized is the same as what's in memory. Having a distinct encode/decode step is the fatal flaw in Protocol Buffers.
Finally an official post with screenshots and detail about NY Times' CMS.
Happy to hear PyPy now supports Python3. I hope the next big milestone they reach is overcoming the GIL.

20 June 2014

Cool to see a proposal to provide the Android NDK for Go.

Runtime errors

Slogged through some Java today. It's fine. But I've never achieved Java nirvana. When I see a runtime error caused by tools like Guice and Dagger I want to delete my homedir and go on a silent retreat. People say they'll never use Python in production because of syntax errors at runtime. It seems like injection warnings are another dimension of the same problem.

18 June 2014

Facebook finally talks publicly about their custom networking gear called "Wedge". When Open Compute launched in April 2011 I thought it was overt that networking gear wasn't mentioned at all. In infrastructure the network is what matters most.

14 June 2014

The only defensible opinion is that your opinions may be indefensible.

11 June 2014

© 2009-2014 Brett Slatkin