Skip to main content

As mentioned at the end of our last blog post, simplicity isn’t automatic, and if it isn’t specifically sought, complexity is the default.

This is especially true when management prioritizes programmers’ “productivity”, measured in lines of code produced and features added, over lines of code averted or deleted, or any indicator of architectural simplification. The incentives are misaligned, and just wrong, in many organizations that have coding metrics, potentially more so than those that do not. And often the incentives are not even wrong in organizations that fail to measure progress.

Even if incentives are properly aligned avoid those pitfalls, and you are genuinely aiming at simplicity, you need to understand where it comes from.

Abstraction – Abstract vs. Concrete

Abstraction is when your language takes care of a lot of details so you don’t have to.It supposes two levels of abstraction:

  1. An abstract level of computing (typically pictured above), which is usually given to you: from the hardware you have, its operating system, its environment, and its system programming language.
  2. A concrete level of computing (typically pictured below), which .has to be sufficiently low-level for the programmer to express the concepts that matter to him, yet sufficiently high-level to shield the programmer from the details that don’t or shouldn’t matter to him.

Usually, the concrete level is given to you: from the hardware you have, its operating system, its environment, and its system programming language.

The abstract level then has to be sufficiently low-level for the programmer to express the concepts that matter to him, yet sufficiently high-level to shield the programmer from the details that don’t or shouldn’t matter to him.

Abstraction in General Purpose Languages

Language Abstraction is everywhere, and everyone is keen to seek and enjoy its benefits, today, when choosing their programming language.

People have replaced binary code with assembly language. Assembly language with FORTRAN, or COBOL. Those first generation languages were replaced by C, then C++, Java, Python, JavaScript, Haskell.  Even in systems programming, Rust is now replacing C and C++, wholly eliminating their catastrophic memory leaks and buffer overflows. Now coming for your Linux kernel!

Though there may not be a clear “winner” across all dimensions, each of these and many other These languages can abstract over a lot of details that previous languages were forcing programmers to deal with.

And even Even in systems programming, Rust is now replacing C and C++, wholly eliminating their catastrophic memory leaks and buffer overflows. Now coming for your Linux kernel!

A Few Rigid Abstractions

All these languages provide a relatively small number of language abstractions to choose from. And once you pick one, buy into its entire ecosystem that comes with many libraries; but as the name entails, it remains general purpose.

A General Purpose Language general purpose programming language is not tailored to your problems; it can abstract over common issues, like memory safety (or it can fail to). But it can only go so deep in whatever particular issues you have, and whatever .general purpose programming language you’re using is likely missing a whole lot of concepts you need. 

Thus, Solidity or JavaScript, as specialized as they may be, couldn’t fix the “making participant payment obvious” problem, or the “keeping accounts balanced” problem, or the “handling timeout systematically” problem, or the “keeping the participants’ software in sync with the smart contract” problem, as experienced by developers of decentralized applications.

Glow, on the other hand, provides specific functionality to make it simple for you to address these issues in programming your DApps. 

I don’t know what domain each of you is working in, but whichever it is, I bet that whatever general-purpose language you use is missing a whole lot of concepts you need.

Library Abstractions are LEAKY

To bring some abstraction, programmers use libraries. Unhappily, libraries do only half of the job of a proper abstraction.

A proper abstraction does two things:

  1. It translates the abstract level into the concrete level, and
  2. It hides the details of the concrete level, makes them inaccessible.

Only then can the programmer safely enjoy the abstraction.

To bring some abstraction, programmers use libraries. Unfortunately,But libraries are generally leaky: they only do the first part, and not the second.

A good abstraction would do the second part, too. It would have no leak, it would be airtight. In Programming Language lingo, we say they would be a full abstraction.

Avoiding Leaks Through Discipline

Leaky abstractions don’t protect you… At. At least not as much as proper abstraction. They may help you save on some details at some place, but then get to handle a big mess of details at another place, at which point the mess will be inextricable.

The usual approach to deal with the conceptual leaks, is to maintain an iron discipline all along to prevent the mess: always make sure you use the libraries properly, that you match the openings with closings, that you satisfy all the constraints however subtle, that you follow the protocol down to the letter, that you properly implement your “design patterns”, and… that you never forget to propagate every change to all the places that need to know about it.

But you are fallible. Your team is fallible. Even when your code is 100% correct today, someone will forget one of those places to propagate the change to when they add a feature later.

But you must account for human error, even when your code is 100%. Discipline works , but Discipline does not scale and is costly. Discipline is costly. Perfect discipline is infinitely costly. And so you have to weigh this cost against the gains of the leaky abstraction.

What can you do to improve those odds?

Type Abstraction

Some modern languages such as Haskell or Scala have expressive type systems that allow library implementers to also express constraints on the use of their libraries, as types. The language then makes it impossible to misuse the library in a way that violates the types.

In many cases, this is enough indeed to make an abstraction airtight. 

But often,Often, the abstraction is still leaky: 

  • There are some laws to your “monad” or library, that the programmers must manually follow, because the type system won’t help them. 
  • But even then, the abstraction can help a lot: like a boat with a few identified leaks you can afford to bail out, rather than a wreck that is more holes than hull.

Still, what is the way to full abstraction?

Language Abstraction

The only true approach to full abstraction is language abstraction, where you build a language that can let users express exactly the concepts they need, while not being even able to express any of the details that parasite users of libraries, wherein they can only shoot themselves in the foot at the cost of a lot of repetitive work, with no benefit whatsoever when the entire ordeal can be automated away.

LangSec

Since today’s audience is people interested in security, this should remind you of Meredith Patterson’s “Language Theoretic Security” (a.k.a. LangSec).

In Meredith Patterson’s  “Language Theoretic Security” (a.k.a. LangSec), you analyze the behavior of a program’s interactions with its environment as a language in which the environment issues programs that the program evaluates.

Network I/O is a language. User interactions constitute a language. Every program is a language to its users!

But understanding that every program is a language is not just for analysis. It’s also for synthesis: write every program as if it were a programming language — because it is.

Now, for most programs, that language is very simple, and only allows for simple questions and answers, wherein the user configures a few knobs, and the program executes a simple request in response. But other programs offer more control to their users, with a richer language. And the more powerful that language, the more the environment controls you. That might be exactly what’s wanted when the program is indeed a language implementation, targeting a local authenticated user as a master to serve. But this is exactly the wrong thing when the program is a public server on the Internet, and offering a universal (“Turing-equivalent”) language to a random connected adversary is essentially being p0wned by them.

DSL School

But understanding that every program is a language is not just for analysis. It’s also for synthesis: write every program as if it were a programming language — because it is.

There is an entire school of programming that thinks kind of this way: the solution to write a program is to design and implement a Domain-Specific Language (DSL) that would be the ideal language in which to write the program, at which point the program because very simple to write, to test, and to audit, with much fewer lines of code and weeks of efforts, — because you only need to deal the the concepts that matter, unhindered by the details that don’t.

That’s an approach that is actually popular in a large fraction of the users of many programming languages such as FORTH, LISP, APL or Scheme.

But if every program is a language, then every language in which to write a program musthas to be be not onlyjust a language but a language-implementation language, with language-implementation tools. None take this “Language-Oriented Programming” story as serious as the Racket community. I personally am using Gerbil Scheme, a close cousin of Racket, with some advantages for my particular use case., though disadvantages for other use cases.

Composing Towers of Languages

In Racket, and to a point in Scheme and other Lisp languages, you don’t just build one abstract computation on top of a more concrete one; you build towers of abstractions, where each floor (but the last?) is itself the ceiling of further lower-level abstraction. Below, there is nothing but turtles all the way down; above, it’s towers all the way up. Language abstractions built on top of language abstractions.

Interestingly, some, even most, of these language abstractions may leak; yet, as long as they are part of implementing a simple enough DSL that doesn’t leak, the overall design won’t leak, and can isolate the application complexity from the implementation complexity, with a language abstraction in-between. In other words, it’s easier to maintain the discipline of dealing with leaky abstractions when these leaky abstractions are part of a small project that will have a small amount of leakage to deal with, and that itself won’t leak details in or out.

Taking Action

Choose your language strategically. Requirements may depend on the scope and duration of a given project and may change over the course of a project, based on various factors. . 

  • What language did you use yesterday?
  • What language shall you use today?
  • What language will you use tomorrow?

These questions need not have the same answer.

If you need something done tomorrow that will be thrown away next week, pick whichever language is at hand.

For a project with a 1-year horizon, pick a popular tool of the day.

But for a project with a 10-year horizon or beyond, maybe you should think strategically. Never thinking about the long-term is a good way of ensuring you will never have long-term to think about. At the same time, Systemantics teaches us that large systems that run always start as small systems that run, that grow, and that never stop to run as they grow.

Getting There

I can’t convince people who are not interested in the long run. I can only convince people who are interested in the long run to consider concepts they might never have even exposed to: that there is a better way to organize software in the long run than most people imagine.

Once you accept that fact, then you realize you will have to write software differently — but also to rewrite your existing software to be better architected and safer. And you may have millions of lines of code, maybe even hundreds of millions, to deal with.

Fortunately, you do not have to migrate your entire codebase at once. You can do it incrementally, and you should prioritize.

Clarity is for more than Software Security

Clarity is not just for the Security of your Software. It is also for the Security of your wetware against attacks.

If in any matter you don’t pick the simplest explanation, then you are letting someone else manipulate you — whoever inserts the extra details. And if there is some way in which you are systematically eschewing the simplest explanation, then you are letting someone else systematically manipulate you.

That is why the approach of Security through Clarity applies to all venues of life. It is an important concept of epistemology. It has been said of a philosopher I like that she had a knack for Reductio ad claritatem — reduction to clarity. We should strive in our life in general to achieve this clarity in our thoughts. That is hard. But the effort is worth it.

References & Further Reading

 

 

Leave a Reply