$$ % Typography and symbols \newcommand{\msf}[1]{\mathsf{#1}} \newcommand{\ctx}{\Gamma} \newcommand{\qamp}{&\quad} \newcommand{\qqamp}{&&\quad} \newcommand{\Coloneqq}{::=} \newcommand{\proves}{\vdash} \newcommand{\star}[1]{#1^{*}} \newcommand{\eps}{\varepsilon} \newcommand{\nul}{\varnothing} \newcommand{\brc}[1]{\{{#1}\}} \newcommand{\binopm}[2]{#1~\bar{\oplus}~#2} \newcommand{\mag}[1]{|{#1}|} \newcommand{\aequiv}{\equiv_\alpha} \newcommand{\semi}[2]{{#1};~{#2}} % Untyped lambda calculus \newcommand{\fun}[2]{\lambda ~ {#1} ~ . ~ {#2}} \newcommand{\app}[2]{#1 ~ #2} \newcommand{\fix}[3]{\msf{fix}~({#1} : {#2}) ~ . ~ #3 } \newcommand{\truet}{\msf{true}} \newcommand{\falset}{\msf{false}} \newcommand{\define}[2]{{#1} \triangleq {#2}} % Typed lambda calculus - expressions \newcommand{\funt}[3]{\lambda ~ \left(#1 : #2\right) ~ . ~ #3} \newcommand{\lett}[4]{\msf{let} ~ \hasType{#1}{#2} = #3 ~ \msf{in} ~ #4} \newcommand{\letrec}[4]{\msf{letrec} ~ \hasType{#1}{#2} = #3 ~ \msf{in} ~ #4}a \newcommand{\ift}[3]{\msf{if} ~ {#1} ~ \msf{then} ~ {#2} ~ \msf{else} ~ {#3}} \newcommand{\rec}[5]{\msf{rec}(#1; ~ #2.#3.#4)(#5)} \newcommand{\case}[5]{\msf{case} ~ {#1} ~ \{ L(#2) \to #3 \mid R(#4) \to #5 \}} \newcommand{\pair}[2]{\left({#1},~{#2}\right)} \newcommand{\proj}[2]{#1 . #2} \newcommand{\inj}[3]{\msf{inj} ~ #1 = #2 ~ \msf{as} ~ #3} \newcommand{\letv}[3]{\msf{let} ~ {#1} = {#2} ~ \msf{in} ~ {#3}} \newcommand{\fold}[2]{\msf{fold}~{#1}~\msf{as}~{#2}} \newcommand{\unfold}[1]{\msf{unfold}~{#1}} \newcommand{\poly}[2]{\Lambda~{#1}~.~ #2} \newcommand{\polyapp}[2]{{#1}~\left[{#2}\right]} \newcommand{\export}[3]{\msf{export}~ #1 ~\msf{without}~{#2}~\msf{as}~ #3} \newcommand{\import}[4]{\msf{import} ~ ({#1}, {#2}) = {#3} ~ \msf{in} ~ #4} % Typed lambda calculus - types \newcommand{\tnum}{\msf{num}} \newcommand{\tstr}{\msf{string}} \newcommand{\tint}{\msf{int}} \newcommand{\tbool}{\msf{bool}} \newcommand{\tfun}[2]{#1 \rightarrow #2} \newcommand{\tprod}[2]{#1 \times #2} \newcommand{\tsum}[2]{#1 + #2} \newcommand{\trec}[2]{\mu~{#1}~.~{#2}} \newcommand{\tvoid}{\msf{void}} \newcommand{\tunit}{\msf{unit}} \newcommand{\tpoly}[2]{\forall~{#1}~.~{#2}} \newcommand{\tmod}[2]{\exists ~ {#1} ~ . ~ #2} % WebAssembly \newcommand{\wconst}[1]{\msf{i32.const}~{#1}} \newcommand{\wbinop}[1]{\msf{i32}.{#1}} \newcommand{\wgetlocal}[1]{\msf{get\_local}~{#1}} \newcommand{\wsetlocal}[1]{\msf{set\_local}~{#1}} \newcommand{\wgetglobal}[1]{\msf{get\_global}~{#1}} \newcommand{\wsetglobal}[1]{\msf{set\_global}~{#1}} \newcommand{\wload}{\msf{i32.load}} \newcommand{\wstore}{\msf{i32.store}} \newcommand{\wsize}{\msf{memory.size}} \newcommand{\wgrow}{\msf{memory.grow}} \newcommand{\wunreachable}{\msf{unreachable}} \newcommand{\wblock}[1]{\msf{block}~{#1}} \newcommand{\wloop}[1]{\msf{loop}~{#1}} \newcommand{\wbr}[1]{\msf{br}~{#1}} \newcommand{\wbrif}[1]{\msf{br\_if}~{#1}} \newcommand{\wreturn}{\msf{return}} \newcommand{\wcall}[1]{\msf{call}~{#1}} \newcommand{\wlabel}[2]{\msf{label}~\{#1\}~{#2}} \newcommand{\wframe}[2]{\msf{frame}~({#1}, {#2})} \newcommand{\wtrapping}{\msf{trapping}} \newcommand{\wbreaking}[1]{\msf{breaking}~{#1}} \newcommand{\wreturning}[1]{\msf{returning}~{#1}} \newcommand{\wconfig}[5]{\{\msf{module}{:}~{#1};~\msf{mem}{:}~{#2};~\msf{locals}{:}~{#3};~\msf{stack}{:}~{#4};~\msf{instrs}{:}~{#5}\}} \newcommand{\wfunc}[4]{\{\msf{params}{:}~{#1};~\msf{locals}{:}~{#2};~\msf{return}~{#3};~\msf{body}{:}~{#4}\}} \newcommand{\wmodule}[1]{\{\msf{funcs}{:}~{#1}\}} \newcommand{\wcg}{\msf{globals}} \newcommand{\wcf}{\msf{funcs}} \newcommand{\wci}{\msf{instrs}} \newcommand{\wcs}{\msf{stack}} \newcommand{\wcl}{\msf{locals}} \newcommand{\wclab}{\msf{labels}} \newcommand{\wcm}{\msf{mem}} \newcommand{\wcmod}{\msf{module}} \newcommand{\wsteps}[2]{\steps{\brc{#1}}{\brc{#2}}} \newcommand{\with}{\underline{\msf{with}}} \newcommand{\wvalid}[2]{{#1} \vdash {#2}~\msf{valid}} \newcommand{\wif}[2]{\msf{if}~{#1}~{\msf{else}}~{#2}} \newcommand{\wfor}[4]{\msf{for}~(\msf{init}~{#1})~(\msf{cond}~{#2})~(\msf{post}~{#3})~{#4}} % assign4.3 custom \newcommand{\wtry}[2]{\msf{try}~{#1}~\msf{catch}~{#2}} \newcommand{\wraise}{\msf{raise}} \newcommand{\wraising}[1]{\msf{raising}~{#1}} \newcommand{\wconst}[1]{\msf{i32.const}~{#1}} \newcommand{\wbinop}[1]{\msf{i32}.{#1}} \newcommand{\wgetlocal}[1]{\msf{get\_local}~{#1}} \newcommand{\wsetlocal}[1]{\msf{set\_local}~{#1}} \newcommand{\wgetglobal}[1]{\msf{get\_global}~{#1}} \newcommand{\wsetglobal}[1]{\msf{set\_global}~{#1}} \newcommand{\wload}{\msf{i32.load}} \newcommand{\wstore}{\msf{i32.store}} \newcommand{\wsize}{\msf{memory.size}} \newcommand{\wgrow}{\msf{memory.grow}} \newcommand{\wunreachable}{\msf{unreachable}} \newcommand{\wblock}[1]{\msf{block}~{#1}} \newcommand{\wloop}[1]{\msf{loop}~{#1}} \newcommand{\wbr}[1]{\msf{br}~{#1}} \newcommand{\wbrif}[1]{\msf{br\_if}~{#1}} \newcommand{\wreturn}{\msf{return}} \newcommand{\wcall}[1]{\msf{call}~{#1}} \newcommand{\wlabel}[2]{\msf{label}~\{#1\}~{#2}} \newcommand{\wframe}[2]{\msf{frame}~({#1}, {#2})} \newcommand{\wtrapping}{\msf{trapping}} \newcommand{\wbreaking}[1]{\msf{breaking}~{#1}} \newcommand{\wreturning}[1]{\msf{returning}~{#1}} \newcommand{\wconfig}[5]{\{\msf{module}{:}~{#1};~\msf{mem}{:}~{#2};~\msf{locals}{:}~{#3};~\msf{stack}{:}~{#4};~\msf{instrs}{:}~{#5}\}} \newcommand{\wfunc}[4]{\{\msf{params}{:}~{#1};~\msf{locals}{:}~{#2};~\msf{return}~{#3};~\msf{body}{:}~{#4}\}} \newcommand{\wmodule}[1]{\{\msf{funcs}{:}~{#1}\}} \newcommand{\wcg}{\msf{globals}} \newcommand{\wcf}{\msf{funcs}} \newcommand{\wci}{\msf{instrs}} \newcommand{\wcs}{\msf{stack}} \newcommand{\wcl}{\msf{locals}} \newcommand{\wcm}{\msf{mem}} \newcommand{\wcmod}{\msf{module}} \newcommand{\wsteps}[2]{\steps{\brc{#1}}{\brc{#2}}} \newcommand{\with}{\underline{\msf{with}}} \newcommand{\wvalid}[2]{{#1} \vdash {#2}~\msf{valid}} % assign4.3 custom \newcommand{\wtry}[2]{\msf{try}~{#1}~\msf{catch}~{#2}} \newcommand{\wraise}{\msf{raise}} \newcommand{\wraising}[1]{\msf{raising}~{#1}} \newcommand{\wif}[2]{\msf{if}~{#1}~{\msf{else}}~{#2}} \newcommand{\wfor}[4]{\msf{for}~(\msf{init}~{#1})~(\msf{cond}~{#2})~(\msf{post}~{#3})~{#4}} \newcommand{\windirect}[1]{\msf{call\_indirect}~{#1}} % session types \newcommand{\ssend}[2]{\msf{send}~{#1};~{#2}} \newcommand{\srecv}[2]{\msf{recv}~{#1};~{#2}} \newcommand{\soffer}[4]{\msf{offer}~\{{#1}\colon({#2})\mid{#3}\colon({#4})\}} \newcommand{\schoose}[4]{\msf{choose}~\{{#1}\colon({#2})\mid{#3}\colon({#4})\}} \newcommand{\srec}[1]{\msf{label};~{#1}} \newcommand{\sgoto}[1]{\msf{goto}~{#1}} \newcommand{\dual}[1]{\overline{#1}} % Inference rules \newcommand{\inferrule}[3][]{\cfrac{#2}{#3}\;{#1}} \newcommand{\ir}[3]{\inferrule[\text{(#1)}]{#2}{#3}} \newcommand{\s}{\hspace{1em}} \newcommand{\nl}{\\[2em]} \newcommand{\evalto}{\boldsymbol{\overset{*}{\mapsto}}} \newcommand{\steps}[2]{#1 \boldsymbol{\mapsto} #2} \newcommand{\evals}[2]{#1 \evalto #2} \newcommand{\subst}[3]{[#1 \rightarrow #2] ~ #3} \newcommand{\dynJ}[2]{#1 \proves #2} \newcommand{\dynJC}[1]{\dynJ{\ctx}{#1}} \newcommand{\typeJ}[3]{#1 \proves \hasType{#2}{#3}} \newcommand{\typeJC}[2]{\typeJ{\ctx}{#1}{#2}} \newcommand{\hasType}[2]{#1 : #2} \newcommand{\val}[1]{#1~\msf{val}} \newcommand{\num}[1]{\msf{Int}(#1)} \newcommand{\err}[1]{#1~\msf{err}} \newcommand{\trans}[2]{#1 \leadsto #2} \newcommand{\size}[1]{\left|#1\right|} $$

&Notepad

Programming Languages as Boy Scouts

Will Crichton   —   August 3, 2016
Programming languages are often evaluated on their efficiency and type safety, but what about friendliness? I explore how human values can be applied to programming language development, specifically looking at the twelve points of the Boy Scout Law: trustworthy, loyal, helpful, friendly, courteous, kind, obedient, cheerful, thrifty, brave, clean, and reverent.

Warning: this post contains strong opinions on programming languages. Viewer discretion is advised.

Introduction

In discussions of programming languages, we tend to focus on mathematical/logical objectives. This language is precise and modular, that language lacks formal semantics, this one strictly adheres to a paradigm, that one maps well to formulae. However, languages and their associated compilers are ultimately not abstract ideas but real tools used by people with more values than just mathematical purity. As an entertaining thought experiment, what if we ascribed the same values we expect from people on to programming languages?

For good values, we can look to the classic organization dedicated to instilling old fashioned American values into young men: the Boy Scouts of America 1. Specifically, all Boy Scouts are asked to follow the Boy Scout Law, a twelve point listing of values that all members ought uphold: “A Scout is trustworthy, loyal, helpful, friendly, courteous, kind, obedient, cheerful, thrifty, brave, clean, and reverent.” In this note, I will explore how each of these values can be applied in the context of programming languages.

Applying the Law

A brief preface: for brevity, I will lump together programming languages and compilers, referring to them interchangeably. My interpretation of broad human values like “friendliness” is obviously not authoritative, but just intended to spark further discussion on the topic.

  1. Trustworthy. A trustworthy language is one you can believe will protect your secrets and not go behind your back to mess with your program semantics. Such a language should have ASLR enabled and should have crypto libraries made by experts so users don’t roll their own. You should be able to trust that your language will make reasonable decisions on your behalf. For example, a language should not coerce your types in unreasonable ways when you’re not looking—we ought not confused dynamic typing (checking types at runtime) with weak typing (coercing types instead of failing in the presence of type errors). Trust is an essential element of productivity. When a programmer does not trust his language, he is forced to program defensively and must battle both his own bugs as well as the compiler’s. A trustworthy language permits the programmer to feel comfortable in his environment and to focus on the task at hand.

  2. Loyal. A language is loyal when its design evolves with the preferences of its users, not its owners or corporate overlords. One should feel comfortable that a language will not turn around and get you sued for using its basic features.

  3. Helpful. A language can be “helpful” in a myriad of ways, but one aspect often missing from newer languages is a means to help the programmer understand his errors. It should have a battle-tested debugger. You should not need an additional piece of software to decipher your compiler’s dense and enigmatic build output. For brownie points, your compiler should explain why the programmer has encountered an error, and for the gold medal it should propose a solution.

  4. Friendly. For a language, it makes the most sense to interpret this as “open to newcomers,” e.g. Python is simple to start with whereas Standard ML can be a formidable foe when first learning functional languages. Having a REPL is a crucial part of a friendly language—it allows new programmers to play around with syntax and get a quick feel for a new language without having to wade through a hundred compiler errors or a monstrous IDE. As a corollary, dynamic languages often feel more friendly since they can be run in small pieces. They permit the programmer to make small errors which the user doesn’t need to concern himself with until necessary.

  5. Courteous. Any language should follow common courtesy: it should clean up after itself (compiler-managed memory) and talk respectfully to its elders (have FFI out to C).

  6. Kind. For me, the distinguishing factor between kindness and other values like helpfulness and courtesy is intention. For a language to be kind, its designers need to be kind in turn. I have never met or heard of a language designer intentionally create a language harmful to its users or its community (although you have to watch people like Ken Thompson), but one should always be careful, particularly with closed-source languages. Language designers should have a clear, open dialogue with their community so as to not have their intentions misconstrued or turned against them.

  7. Obedient. Similar to being trustworthy, an obedient language will always do what it’s told and no more. The language’s libraries should be clearly named and documented. If a function has to fail, it should not do so silently, and the programmer should handle the error (error codes considered harmful). The language should be formally specified, and the compiler should adhere strictly to the specification, loudly telling the programmer where it does not.

  8. Cheerful. This is perhaps the least applicable to programming languages, as there’s a fine line between cheer and condescension. After all, nobody wants a compiler like this: However, arguably to be cheerful it means one should at least be more upbeat. Doom and gloom and vitriol are not essential parts of a compiler (or any piece of software).

  9. Thrifty. A language should do a lot with a little, or prefer a smaller number of powerful abstractions to a larger number of weaker ones. More concretely, do not make concepts first-class in the language unless they need to be, otherwise implement them in libraries. For example, Rust implements iterators as traits and Clojure implements gradual typing as a library (!). Similarly, a thrifty language should only pay for what it needs and prefer zero-cost abstractions where possible. If your quick script takes 0.5 seconds to run but 3 seconds to start the garbage collector, then the language is not thrifty. If your build takes an hour to compile against a library from which you only need one or two functions (cough OpenCV), then the language is not thrifty.

  10. Brave. As a language designer, do not be afraid to be brave when creating a new language. Although I received overwhelmingly positive feedback on one of my recent posts on language design, there are many naysayers stuck to their paradigms and editors who will be washed away with the flow of time and progress. Make that new type system. Change up the syntax. Just do so while trying not to violate the other principles of good language design.

  11. Clean. Just as people should look and smell clean, so should the syntax of a programming language. Although programmers often like to think in abstractions, the concrete syntax of a language really does matter. In most software development, code is meant to be read and not written, so the more legible your language, the easier it is to maintain. Remember that the language designers often dictate style used by the community. Avoid the majority of naming conventions brought from mathematics (e.g. one letter variable names) unless no one will read your code or it actually improves legibility. Naming is important!

  12. Reverent. Whether you belong to the Kingdom of Nouns or the Church of Lambda, be respectful of all faiths. Better yet, take the best of all worlds when designing your language (see: my previous post). Don’t force users to pick one paradigm over the other, but rather be flexible enough to accommodate all walks of life.

Conclusion

The inevitable counterpoint to this post is this: “but Will, I don’t care if my language is friendly. I just need it to work. We should care about efficiency, not these abstract notions of human good.” And this is a fair point! I wouldn’t use a language that was trustworthy but didn’t actually do anything. However, we shouldn’t let goals like efficiency wash away all else in the discussion. If we promote programming languages that have values like those discussed above, we can create a better world both for existing programmers as well as those seeking to enter the craft. Language designers should remember that their end users are people, not robots 2, and design accordingly.

Please direct your opinions to my email at wcrichto@stanford.edu or discuss this on Hacker News.

References

  1. Yes, I know the Boy Scouts are not always paragons of good values, and yes, I know the Boy Scouts are originally from Britain

  2. Unless, of course, your language is LLVM