1. 2

I strongly agree with this perspective.

Lots of programming (especially as taught in school) is about the relationship between the programmer and the machine. How do I get the computer to do the thing I want? What are techniques to get desired behavior out of the computer?

I think a key difference between software engineering as a profession and programming as a craft (e.g. as it’s taught in schools) is that an enormous part of software engineering is about preserving the intent of your code. Without preserving intent (“the program does X with the intent of accomplishing goal Y”), coordinating changes to a program done by multiple people over a long period of time becomes very difficult. This is because it becomes hard to tell which parts of the program are meant to work the way they do vs. just incidentally working the way they do. Is this weird if-statement with a specific condition a temporary hack, or is it a deliberate code path that handles some known edge case? Without knowing this, it’s much harder to determine whether deleting this if-statement cleans up some now-irrelevant code or will cause a spooky breakage elsewhere in the system.

Preserving intent comes in lots of ways. Sometimes it’s using code idioms, sometimes it’s comments, sometimes it’s design docs. But it generally needs to be a deliberate goal of the writer. Many heuristics for writing code such as DRY or SOLID are at their core about preservation of programmer intent.

Peter Naur has a great article on this called Programming as Theory Building, where he calls this “preserving the theory” (or “preserving the program”).

1. 16

I work on a team that primarily writes Haskell. We’re part of a small-ish (~20 person) engineering org where most people come in knowing Go or JavaScript or Ruby. We’ve had good (surprising!) success onboarding new engineers to Haskell. Zero to production-ready code review takes around 6 weeks, which isn’t that different from most languages.

Because of this, I’ve had quite a bit of experience teaching working programmers what monads are, and seen a ton of different approaches. Pedagogically, I would say that “monads as monoids of endofunctors” is probably one of the least effective approaches we’ve tried. There’s just too many concepts here that you need to introduce at the same time. Monoids make sense to most people (programmers write a lot of monoids day-to-day, so it’s easy to motivate a monoid with concrete examples like strings, lists, numbers, or Builders), but endofunctors and categories usually do not. What is a category? What is an example of a category in code I’ve seen before? These questions tend to trip people up because it’s hard to understand something until you’ve played with it for a bit (alas, You Can’t Tell People Anything).

The approach I’ve found to be most effective is to start from something a working programmer has seen before, and compare and contrast. Fortunately, most programmers usually do have a good starting point! Depending on their native language, this is usually a Promise or a Future or a continuation or a Stream - usually some kind of object that represents a computation, where these objects can be sequenced to construct larger computations. I’ve had great success working with JavaScript programmers and telling them “monads are a lot like then-ables (Promises), and async/await is a lot like do-syntax, and here’s where they’re similar, and here’s how they differ”.

I think this approach succeeds because it’s much easier to teach a concept by starting from something that someone has actually used before (and by using their existing vocabulary) than by starting from scratch. If I tell someone “monads are values that represent computations that can be sequenced”, I get a lot of blank stares. If I follow that up with “just like Promises!”, I see a lot of lightbulbs go off.

(This is why I think it’s actually particularly hard to teach applicative functors. Lots of Haskell pedagogy attempts to go in the order of the typeclass hierarchy, teaching functors then applicatives then monads. I think it’s actually more pedagogically effective to skip around, teaching functors then monads then applicatives, because it’s hard to motivate applicatives as “functors containing functions” and easy to motivate applicatives as “weaker monads when you don’t need the power and you want to constrain what the computation can do”.)

1. 7

“Monoid in the category of endofunctors” is true statement, but is an old joke that escaped containment.

Can you elaborate on your async/await comparison? I haven’t been in the JS world for a long time.

Re: Applicative: I used to help teach the System-F (nee Data61) FP Course, and we seemed to have reasonable success motivating Applicative by trying to map a binary function a -> b -> c over a functor f a and getting “stuck” with an f (b -> c). This motivates Applicative as the typeclass that provides lift0 and lift[2..\infty] functions, if you think of Functor as the typeclass that provides lift1 (as an alternative perspective on fmap).

1. 10

map a binary function a -> b -> c over a functor f a

The hard part about this example is that the immediate response is “why would I ever do this?”. I don’t think I have a compelling answer for that. I’ve found that helping someone answer “why would I ever use X?” for any concept really helps them understand the concept better.

Can you elaborate on your async/await comparison?

Sure! TL;DR: async/await is do-syntax for the Promise monad.

JavaScript has a concept of “Promises”, which are asynchronous computations that will eventually return a value. Promises are more convenient than using callbacks since you avoid the Pyramid of Doom problem (although they trade this off with some annoyances of their own), and most of the JS ecosystem has settled on Promises as a primitive for asynchronous computation. (Fun fact: Node actually had Promise support really early on, then switched to callbacks for most standard library functions, then switched back to Promises! There’s a really interesting aside about this in Platform as a Reflection of Values, a talk by Bryan Cantrill who was there at Joyent at the time.)

Promises provide a couple primitives:

• resolve(value: T): Promise<T>, which takes a regular value and “wraps” it into a Promise that immediately provides the value.
• then(this: Promise<A>, f: A -> Promise<B>): Promise<B>, that takes a Promise<A> and a function A -> Promise<B> and provides you with a Promise<B>. Mechanically, then waits until this has a value ready, and then runs f and returns the result.

(There are some other primitives for constructing promises from callbacks, synchronizing multiple promises, doing error handling, etc., but they’re not important for this comparison.)

Why do you have to use then in order to call a function using the Promise’s value? Because the Promise’s value isn’t guaranteed to be there yet! What you’re really saying is “here’s a computation that I want to run once the value becomes available”. (If you know monads, this should start sounding familiar.)

So programmers usually wind up with a bunch of code that looks like this:

somethingThatReturnsAPromise()
.then(a -> {
// Do a bunch of synchronous stuff,
// then end up doing something
// asynchronous, and return a Promise.
}).then(b -> {
// etc.
}).then(c -> {
// etc.
})
// This eventually becomes a long chain.


Later, JavaScript adopted what’s called “await/async” syntax, which is a syntax sugar that allows you to write code like this instead:

// Some types:
// somethingThatReturnsAPromise: () -> Promise<A>
// someOtherAsyncThing: A -> Promise<B>
// yetAnotherAsyncThing: B -> Promise<C>

a = await somethingThatReturnsAPromise()
// Do stuff...
b = await someOtherAsyncThing(a)
// etc.
result = await yetAnotherAsyncThing(b) // result: Promise<C>


This is a bit clearer and a bit more convenient to write, and basically desugars down to the same thing.

Now, how is this all related to monads? Well Promises are actually just an instance of monad! (Not really because of implementation details, but they’re monadic in spirit.)

Let’s look at how they compare. Monad is a typeclass, which basically means it’s an interface (or really more like a Rust trait). (Again not really, but correct in spirit - most of what I’m saying here is going to be not-super-technically correct.) A type is-a monad when it implements that interface. The monad interface basically looks like:

interface Monad for type T<A> {
resolve(value: A): T<A>
then(this: T<A>, f: A -> T<B>): T<B>
}


(Haskell’s actual Monad typeclass names these functions return instead of resolve, and bind instead of then.)

Hold on! Doesn’t that look familiar? Yes, this is almost exactly what Promise provides! Now let’s look at do-syntax:

// Some types:
// somethingThatReturnsT: () -> T<A>
// someOtherThingThatReturnsT: A -> T<B>
// yetAnotherThingThatReturnsT: B -> T<C>

result: T<A> // Where T is a monad
result = do
a <- somethingThatReturnsT()
// Do stuff in let-bindings...
b <- someOtherThingThatReturnsT(a)
// etc.
yetAnotherThingThatReturnsT(b)


Huh… isn’t that almost exactly async/await? Right again.

Why is this useful? Two reasons:

1. It turns out that there are lots of monads. This idea of “give me some computations, and I’ll sequence them together in a certain way” turns out to be very general. In Promise, the “way I sequence computations” is “wait until the asynchronous value is available”. In Maybe, it’s “don’t run the next computations if previous computations didn’t succeed”. In State, it’s “let future computations ask for a value that previous computations have put”. All of these implementations of Monad differ in how they implement then and resolve.
2. It turns out that you can write functions that are general across every single kind of monads! For example, you can write when or unless or forever or sequence (see Control.Monad for other examples). This makes it easier to reuse code and concepts across different kinds of monads. If you can also write your business logic to work across different kinds of monads, it also makes testing easier: you can use a monad that provides logging by writing to disk in production, and swap that out for a monad that provides logging by saving log messages in memory in testing, all without changing a single line of code in your business logic. Monads are like type-checked, effect-tracked dependency injection! (This idea is its own whole blog post - TL;DR: monads and DI are both about effects, so they wind up being used for similar purposes.)

Sorry in advance for typos or incoherence - most of this is off of the top of my head.

1. 2

Thanks for the detailed post. Is your type signature for then(this: Promise<A>, f : A -> B) incorrect? It should be a Promise<B>, right?

The hard part about this example is that the immediate response is “why would I ever do this?”

We’d usually motivate this with the Maybe applicative: apply f to both arguments if both are Just; return Nothing otherwise. Then consider other applicatives and notice how an embarrassingly large amount of imperative programming is subsumed by things you can build out of applicative operations (traverse and sequence in particular).

1. 2

Yes, good catch on the typo!

Hmm, I haven’t thought about teaching applicatives in the context of traverse. That’s an interesting idea, I’ll have to think about that more.

1. 1

Traversable makes it more powerful, but more complicated. Try specialising to the [] Traversable first, if you end up giving it a go?

2. 1

Then consider other applicatives and notice how an embarrassingly large amount of imperative programming is subsumed by things you can build out of applicative operations (traverse and sequence in particular).

Could you elaborate on this?

2. 3

I understand your approach, and while I think it’s effective, it does have some well-known weaknesses. The most important thing to emphasize is the algebraic laws, but your teams are working in Haskell, which is oblivious to those laws; it is easy for programmers to learn the entirety of the Monad typeclass in practice but never realize the laws. (I was such a Haskeller!)

What is a category?

C’mon, really? I know that you’re just playing, but this question really needs to be given a standard answer. A category is a collection of structures plus a collection of transformations from one structure to another. We usually call the structures “objects” and the transformations “arrows” or “morphisms”, but this is merely a traditional note so that readers can read common category-theoretic documentation. The main point of a category is to consider composition of transformations.

What is an example of a category in code I’ve seen before?

The following systems are categories which I’ve used in serious code. Each system has a proper title, but this is again merely mathematical tradition. This should not be a surprising collection; computer scientists should recognize all of these components, even if they’ve never considered how they’re organized.

• Set, whose objects are sets and arrows are functions
• Rel, whose objects are sets and arrows are relations
• P, whose objects are sets and arrows are permutations
• Circ, whose objects are the natural numbers and arrows are Boolean circuits
• Mat_R, whose objects are the natural numbers and arrows are matrices over some semiring R
• Eff, whose objects are sets with computable equality of elements and arrows are computable functions
• Pos, whose objects are partial orders and arrows are monotone maps

In addition, every programming language gives a category via the native type theory construction. This is actually misleading for Haskellers, since the category Hask is actually bogus, but there is a non-Hask category which accurately describes how Haskell transforms data.

1. 5

The most important thing to emphasize is the algebraic laws

Yes, agreed. Something I’ve found is useful for teaching is to explicitly map the maximally precise terminology that the Haskell community likes to use back to similar, vaguer terminology that working programmers are familiar with.

For example, I’ve found more success teaching “algebraic laws” as “coding conventions but motivated because look at how useful these equalities are”. Most programmers are familiar with conventions (e.g. don’t do side effects in your toString implementation), but I’ve found a lot of people get hung up on the terminology of what “algebraic” means and what “laws” are.

A category is a collection of structures plus a collection of transformations from one structure to another.

The difficulty I’ve had with a definition like this is answering “why is looking at something as a category useful to me as a programmer?”. I’ve found that providing a satisfying answer to this question for any concept (“why is X useful?”) helps tremendously with making the concept stick.

There are definitely categories in the list of examples you’ve provided that I’ve worked with (e.g. Set, Rel, P) in other languages, but where I’ve been struggling is providing a satisfying answer to “if I could look at this as a category in a programming language I’m familiar with, what would be easier?”.

It’s been relatively easier for me to answer this question for ideas like effect tracking (“your logging library will never secretly call the network again”) or algebraic data types (“you’ll never have a struct whose fields are in an inconsistent state”), but I haven’t found a satisfying answer for categories yet. If you’ve heard of one, I’d love to use it!

1. 4

A disconnect between (nominally) mathematical and pragmatic perspectives I’ve noticed that seems important to address: people who aren’t conditioned to think as mathematicians do tend to, understandably, relate to mathematical invariants as inscrutable laws passed down from the universe, when in fact they are just as much determined by a criterion of usefulness as any programming construct is. Take the case of linear transformations as a tensor product between vectors and covectors: considered on their own, the only matrices you get from just that operation are singular. Such a purity of definition results in a universe of discourse (singular matrices) that is, frankly, not very useful, so we go on to consider sums of such products so that we can talk about linear transformations with full rank.

It’s certainly the case that what counts as “useful” to a mathematician might very well diverge wildly from what counts as useful to a programmer, but it’s been my experience that conveying the understanding that mathematical constructs are chosen as much on the basis of what they can do as are programming constructs is an essential aspect of answering the question of “why is framing this practical concern in terms of a more abstract mathematical construct useful to me as a programmer?”

1. 3

All that said, though, I still harbor a suspicion that most invocations of category theory in the context of programming are fetishistic and convey little in the way of enhanced understanding of the shape of the problem at hand. The mere fact of dealing with a problem where one is operating on objects within a category and morphisms between them doesn’t at all imply that conceiving of the problem in category-theoretic terms is useful, even in the case of a lone creator who is free to organize their work according to whichever models they see fit, and much less in the case of someone who must work with others (which is to say, someone whose work could ever matter) and develop a shared mental model of their collective project with unquestionably competent people who nonetheless are in general unlikely to share the category-theoretic lens. It’s pretty silly to me to cite the mere fact of any one category’s objects and morphisms being a frequent domain of consideration as evidence of the notion that thinking of that domain in terms of its categorical representation is inherently useful.

1. 1

If literally nothing else, category theory is tightly related to programming language theory and design via type theory. I’ve explained before how this works for the MarshallB programming language, but it also works for languages like Joy. Categories also show up in computability theory, due to the fact that Turing categories are restriction categories.

1. 1

I’m absolutely in agreement with you on that, in that category theory provides an extraordinarily useful set of tools for modeling constructive type theories. Again, though, among all the problems that might ever be solved by a Turing-complete symbol system, how many benefit notably from a category-theoretic framing? To be clear, I’m coming at this from the position that cases like Haskell’s Monad typeclass don’t qualify, since they seem to be constructs that are merely influenced by category theory rather than being faithful representations of some category-theoretical construct: consider that return as implemented isn’t actually a natural transformation η in the theoretical sense, because it doesn’t actually map between functors: the identity functor is merely implied or assumed, even though the input is merely an object of Hask that need not be a functor itself.

2. 1

Thanks, the description of explaining things in terms of what the programmer already knows helped me a lot. I have been working in c# for years and I knew that I have been using and understanding the use of monads most of that time through linq. But I was having issues translating that to a more abstract definition and overall concept. After reading your comment I just googled ‘monads in c#’ and got this https://mikhail.io/2018/07/monads-explained-in-csharp-again/ which explained to me how what I already new related to the more general concept.

1. 5

Admit it. If you browse around you will realize that the best documented projects you find never provide that docs generated directly from code.

Is this saying that you shouldn’t use Javadoc or pydoc or “cargo doc”, where the documentation is located in the source files? So, from the previous point, it’s essential that docs live in the same repo as the code, but not the same files as the code? Seems like a pretty extreme position relative to the justification.

1. 18

As a concrete example, Python’s official documentation is built using the Sphinx tool, and Sphinx supports extracting documentation from Python source files, but Python’s standard library documentation does not use it - the standard library does include docstrings, but they’re not the documentation displayed in the Standard Library Reference. Partially that’s because Python had standard library documentation before such automatic-documentation tools existed, but it’s also because the best way to organise a codebase is not necessarily the best way to explain it to a human.

As another example in the other direction: Rust libraries sometimes include dummy modules containing no code, just to have a place to put documentation that’s not strictly bound to the organisation of the code, since cargo doc can only generate documentation from code.

There’s definitely a place for documentation extracted from code, in manpage-style terse reference material, but good documentation is not just the concatenation of small documentation chunks.

1. 1

Ah, I was thinking of smaller libraries, where you can reasonably fit everything but the reference part of the documentation on one (possibly big) page. Agreed that docs-from-code tools aren’t appropriate for big projects, where you need many separate pages of non-reference docs.

2. 10

There’s definitely a place for documentation extracted from code, in manpage-style terse reference material, but good documentation is not just the concatenation of small documentation chunks.

Can’t agree enough with this. Just to attempt to paint the picture a bit more for people reading this and disagreeing. Make sure you are thinking about the complete and exhaustive definition of ‘docs’. Surely you can get the basic API or stdlib with method arity and expected types and such, but for howtos and walkthroughs and the whole gamut it’s going to take some effort. And that effort is going to take good old fashioned work by technical folks who also write well.

It’s taken me a long time to properly understand Go given that ‘the docs’ were for a long time just this and lacked any sort of tutorials or other guides. There’s been so much amazing improvement here and bravo to everyone who has contributed.

On a personal note, the Stripe docs are also a great example of this. I cannot possibly explain the amount of effort or care that goes into them. Having written a handful of them myself, it’s very much “a lot of effort went into making this effortless” sort of work.

1. 8

Yeah I hard disagree with that. The elixir ecosystem has amazing docs and docs are colocated with source by default for all projects, and use the same documentation system as the language.

1. 2

2. 5

The entire D standard library documentation is generated from source code. Unittests are automatically included as examples. It’s searchable, cross-linked and generally nice to use. So yeah, I think this is just an instance of having seen too many bad examples of code-based docs and not enough good ones.

When documentation is extracted from code in a language where that is supported well, it doesn’t look like “documentation extracted from code”, it just looks like documentation.

1. 4

Check out Four Kinds of Documentation. Generated documentation from code comments is great for reference docs, but usually isn’t a great way to put together tutorials or explain broader concepts.

It’s not that documentation generation is bad, just that it’s insufficient.

1. 2

Maybe the author is thinking about documentation which has no real input from the developer. Like an automated list of functions and arguments needed with no other contextual text.

1. 8

As someone whose written Lisp, Ruby, and Node on one side, and Go and Haskell on the other, I’ve always been curious about this apparent split between strongly typed languages and languages with really good interactive connect-to-production drop-into-a-useful-debugger-on-crash REPLs. Is this intrinsic to the language, or just a result of differing focuses on the tooling?

I’m currently leaning towards “this is just a side effect of tooling focuses”, although I have yet to see even a really compelling UX sketch for a debug-it-in-prod REPL for a typed language. (For example, what would it mean to rebind a name to a value of a different type while paused computations may still refer to that name after resuming?)

1. 5

It depends what you mean by “debug”. If you want to inspect values, execute functions, modify value of same type - that’s supported by a lot of languages. GDB will let you do all three.

But the idea of using a value of a different type is… interesting. I don’t see why would you ever want to do that in production. You can of course replace an object with a vtable-compatible one. (Or however your language does dispatch) But a completely different type? Technically: You’d need to also replace all the following functions to match the change and all places which produce that value. Practically: Why not take a core dump and debug this outside of production instead?

(An incompatible type also doesn’t make sense really - the code won’t do the right thing with it in about the same way a dynamically typed language wouldn’t)

And finally as a devopsy person I’d say: “if you feel the need to drop into a debugger in production that means we should improve tracing / logging / automated coredump extraction instead.”

1. 2

I’ve always been curious about this apparent split between strongly typed languages and languages with really good interactive connect-to-production drop-into-a-useful-debugger-on-crash REPLs

This split is indeed interesting and it seems deep and hard to overcome. Languages emphasizing static typing provide a certain level of confidence, if it typechecks it is likely correct. But this requires an enforcement of type constraints across the program otherwise that promise is broken.

Interactive languages are more pragmatic. In Smalltalk, modifying a method in the debugger at runtime will compile and install it in the class, but existing instances of that method on the callstack will refer to the old one until they are unwound. There basically is no distinction in runtime / compile time which in practice works very well but is not really compatible with the confidence guarantees of more statically typed approaches.

I always wondered what a more nuanced compile time / runtime model would look like, basically introducing the notion of first-class behavior updates like Erlang. In different generations of code different type constraints apply. Which in turn means that for soundness the generations wouldn’t really be able to interact.

1. 8

This reminds me of one of my favorite blog posts of all time: You can’t tell people anything. This is a must-read for every new project leader on my team.

It’s just so hard to go from not knowing a thing to knowing that thing without having actually done the thing. Systemization of a process can help scale new processes a bit. But at the end of the day, organizations are made of people, and no amount of process or systems or tools will save you from people who don’t get it. Your people have to get it first, then the systems and processes come after as a way to scale the learnings of those people.

(For the equivalent idea on the technical design side, see Programming as Theory Building.)

1. 7

What are some alternatives to SQL that avoid some of these pitfalls?

One really interesting SQL library I’ve seen is opaleye, which bypasses a lot of the incompressibility issues by constructing queries in a Haskell DSL using reusable and composable monadic primitives, and then compiling those down into SQL (and automatically generating the repetitive parts of the query as part of that compilation).

1. 2

Thanks for linking to Opaleye, it was interesting to read about!

1. 1

I might be mistaken, but I don’t understand how this tool or the practices in this article get you a truly repeatable build.

To me, having a repeatable build means you produce the same binary artifacts when you build your checked-in source code no matter when or on what machine you run the build on. But using a tool like Docker seems to already make this impossible. If a Dockerfile allows you to RUN apt-get install foo, then running that command at time T1 will give you a different answer than running at time T2.

It seems to me like you can’t have real repeatability unless you have repeatable dependency semantics all the way down to the language level for each dependency manager you’re using. Tools like Blaze get around this by forcing you to use their own dependency management system that basically requires you to vendor everything, which guarantees repeatability. But I don’t see an analogous system in Earthly.

1. 2

We debated ourselves a lot about what the right term is for this. From community feedback, we learned that there is a term for 100% deterministic builds: Reproducible builds. Earthly is not that. Bazel is. Earthly (and the practices that this guide talks about) has some consistency, but as you point out, it doesn’t get you all the way. We called this “repeatable builds” as an in-between term. The reasoning is that for many use-cases, it’s better if you are compatible with most open-source tooling, but are not 100% deterministic, rather than go all the way deterministic, but usability is heavily impacted.

1. 1

No, you are not mistaken. Docker lets you RUN anything inside them, and so they are not reproducible by design. You could write Dockerfiles that are reproducible by not using some features (this is what Bazel does to ensure reproducibility when building Docker images).

1. 7

I like this article a lot. Two particular insights that really resonated with me:

1. “The most important thing in software design is problem framing” (and all of its presented corollaries e.g. assuming you’re working with an ill-defined problem, etc.).
2. “Knowledge about design is hard to externalize” (and therefore most learning is done through apprenticeship).

These two stood out to me because they both align strongly with my experience, and seem like they’re not really taught in a standard CS curriculum. School definitely taught me a lot about how to program, but very little about how to solve problems with software.

It feels almost like my university curriculum was missing a finishing course in actually solving real problems (although I can imagine designing such a course would be really difficult since it would need to effectively be an apprenticeship - maybe this is the role that internships are intended to fill?).

1. 2

Thank you for making this! It’s always surprising to me (coming from Go and Node) when I find basic tooling like this that’s missing in Haskell.

My work projects use Cabal, but I’ll take a look at adding Cabal support when I find the time.

1. 1

Sweet! Actually I added support tonight. I’ve never used a cabal.project file, so I kind of guessed, but if you want to try it out for a spin and let me know any issues in a DM please do: https://github.com/dfithian/prune-juice

1. 16

This is a particular reputational problem for unusual language choices because failures of a team or product tend to be blamed on the weirdest things that a team does. When a Go project fails due to design issues, people rarely say “this failed because of Go” because there are so many examples of projects written in Go that did not fail. (On the flip side, design failure is also more likely because there are so many more examples of successful Go projects to follow than there are examples of successful Haskell projects.)

1. 5

On the flip side, design failure is also more likely because there are so many more examples of successful Go projects to follow than there are examples of successful Haskell projects.

I feel like this is a big part of it. Another issue is that often the developers choosing languages like Haskell are less likely to have used it at a large scale than people working with conventional languages. For many people this is because you don’t get the opportunity to work with a language at scale without using it at work.

1. 8

Indeed. And things work much differently at scale than they do in the small, the tradeoffs get different, and one has to develop a plan to compartmentalize ugliness and complexity.

1. 2

For an average project… I seriously doubt the “less likely to have used it at a large scale” part is true. Many teams don’t have a person on board who’d have practical experience using something at an actually large scale, much less designing for a large scale.

Just because there are more people with that experience in the general population, doesn’t mean a particular team has one of those. Also, many projects never reach a truly large scale, and with modern hardware, large is really quite large.

Of course, “using $language isn’t a stand-in for good software design”. But first, it’s true for any language, and second, there are already enough people trying to discourage everyone from moving beyond the current mainstream. 1. 3 I can’t tell you the number of Python shops that said the same thing, only to have their engineers perform expensive rewrites in more performant language runtimes. Twitter here is the poster child of doing a large-scale rewrite from Rails into Scala for performance reasons. It’s true that large scale does not happen to every company, but I also think optimizing solely for developer ergonomics will put you in a bind any time you do need to scale. Of course, “using$language isn’t a stand-in for good software design”. But first, it’s true for any language, and second, there are already enough people trying to discourage everyone from moving beyond the current mainstream.

Who? So many Silicon Valley companies have seen success embracing new technological stacks (like Rails and Django when they were hot, to Scala when it became cool, Go when it came onto the scene, and now Rust), so I can’t see why a new company would not want to use a new language if their devs were comfortable in it and confident in its ability to scale to the market they wished to address.

the current mainstream

There’s wisdom in accepting tried-and-true solutions. I’ve dug into new language libraries only to find undocumented settings or badly chosen constants more times than I can count. Edge cases in LRU caches, bugs in returning connections back into connection pools, circuit breakers that will oscillate between on-and-off when degenerate conditions are hit, these are all examples of algorithms that were incorrectly implemented, and it’s not always clear if the value proposition of a new language overcomes the complexity of implementing tried-and-true algorithms. Choosing an “unproven” language is not a light decision, especially when several engineers will spend their time and money on making it work.

1. 3

Most places aren’t Silicon Valley. Most companies aren’t startups. Most projects aren’t web services with open registration.

And it’s not about optimizing for developer ergonomics. Advanced type systems are about correctness first of all. It’s just sort of taking off with Scala, Swift, and Rust. Sort of. Those clearly still face more opposition than 70’s designs like Python, Ruby, or Go.

1. 5

Most places aren’t Silicon Valley. Most companies aren’t startups. Most projects aren’t web services with open registration.

According to StackOverflow [1], Typescript is a top 10 language, and Rust, Swift, and Typescript are top 20 languages. So while there is a bias for tried-and-true languages like Python and Java, languages with “advanced” type systems are in the top 20.

Advanced type systems are about correctness first of all

Is it? I’m not sure if there’s any proof that advanced type systems actually lead to more correct programs. I certainly don’t remember that when I was in grad school for PLT either. I understand that practitioners feel that type systems allow them to make more correct programs with less cognitive load, but I would bin that into developer ergonomics and not correctness until there’s proof positive that ML-based type systems do actually result in more correct programs.

It’s just sort of taking off with Scala, Swift, and Rust. Sort of. Those clearly still face more opposition than 70’s designs like Python, Ruby, or Go

Or, perhaps, most programmers genuinely do not enjoy working with heavily restrictive type systems or with monad transformer stacks and free monads. Haskell is a year older than Python and is an order of magnitude less widely used than Python. It’s clear that recent languages like Rust, Swift, Typescript, and Scala do use a lot of ideas from ML-descended type systems but it’s not clear whether they are popular as a whole or not. Many languages have taken pieces of these type systems (such as the Optional/Maybe sum type) and bolted them onto their language (Kotlin), but I think that ML-style languages will always be enjoyed by a minority, though how large that minority is time will tell.

1. 2

I understand that practitioners feel that type systems allow them to make more correct programs with less cognitive load, but I would bin that into developer ergonomics and not correctness until there’s proof positive that ML-based type systems do actually result in more correct programs.

This is an accurate statement I think. No type system can guarantee you are more correct unless the “spec” you are implementing is more correct. What it does help with is giving the developer more information on where they are starting to deviate from the spec. But even then it doesn’t guarantee that they are implementing the spec correctly in the type system. ML Type systems give you early information that your code won’t work as implemented when it hits certain edge cases. Those edge cases will sometime but not always manifest as bugs in production. So depending on the ergonomics you favor in your programming language you will either love that early information, be ambivalent, or you will dislike it.

1. 25

Let’s reframe this: maybe it’s more challenging to be an engineer (or a software manager) in a small company with a small customer base. Where building the wrong feature is a mistake that will cost you a year’s revenue. Where alienating your main client will lead to insolvency. Where you can’t just launch a new product and sunset it two years later.

FAANG’s enormous scale and customer-lock in makes it very inconsequential for them make mistakes. I’m sure that’s very comfortable for the engineers working there. But I wouldn’t make the mistake of confusing the dividend of those companies’ enormous success with the source of it.

1. 5

This is an important point. The motivations of an individual engineer (learning, problem solving, building a CV) may not automatically align with the motivations of a company (sustainable operations). This is not to say that large slathers of management are needed to align the motivations, it’s just that this alignment is needed.

1. 3

I’m not sure size is the key factor here. I worked at Google and now I work at a series B startup (joined at 3, now at 50) and we do a lot more of “makers (engineers, designers, etc.) talking to customers” at the startup than at Google. In fact, one of my biggest complaints about Google was that the game of telephone between the person making something and the person using the thing that was made was so long that it was very difficult as a maker to get meaningful feedback on whether you were building the right thing (because feedback from customers got passed through various PMs, etc. and lost detail at each step).

1. 1

I’ve worked for a company that prided itself on its customer support. And to be fair, their people were really good at talking things over with customers, making them feel appreciated, maybe offer a little discount for the next month. Anything rather than admit there’s a bug and escalate it. I think that strategy worked well for the company, but it made product development rather frustrating.

1. 27

This sort of article is deeply frustrating to me. I manage a team that develops a Haskell codebase in production at work, and for every engineer that (rightly) looks at this sort of incoherent, imprecise rambling and dismisses it as FUD, many more add it to their mental list of “times when Haskell has made someone quit in anger and confusion”.

There are valid complaints about Haskell, and its beginner unfriendliness. I made some very specific complaints about its syntax and tooling’s learning curve over in the thread on Hacker News (link: my comment on HN), and I can actually think of two more complaints[1] off of the top of my head this morning. There are definitely valid criticisms!

But the mysticism of “ooh, types and purity and monads are weird and spooky and Not Actually Good(tm)!” is FUD that is so difficult to evaluate critically, and I think makes folks who would otherwise pick Haskell up in a couple of weeks[2] really afraid to try it out.

[1] First, type declaration syntax is symmetric to value declaration syntax. This is elegant and beautiful and minimal, but really confuses beginners on what names are values and what names are types. It doesn’t help that values and types have different namespaces, so a can be a value in some contexts and a type in others. Second, Haskell has a very minimal no-parentheses syntax to type declaration. Again, this is elegant and minimal - once you grok it, you understand why they would choose this path to make mathematical symmetries clear - but it’s very confusing for beginners when types, type constructors, and data constructors are not syntactically distinguishable.

[2] Historically, our internal estimate for onboarding to Haskell is about 6 weeks from zero to production-ready code review. This is not much longer than other languages. However, zero to side project for Haskell is definitely a week or two, and this is definitely genuinely longer than other languages (e.g. zero to side project for Go is like 30 minutes).

1. 3

The premise of this article seems to be “filesystems vs. database is not the right way to frame technologies because the requirements they solve for do not conflict”. That is, providing support for database-like operations (e.g. transactions) does not intrinsically preclude providing support for filesystem-like operations (e.g. storing large objects).

I’d argue that this premise is incorrect, because it considers requirements only from the perspective of capabilities when in reality there are also requirements from the perspectives of performance and cost. Databases and filesystems have dramatically different characteristics in terms of how much it costs to store some amount of data (remember, databases need to build indexes) and how quickly I can query and search for data. The reason I don’t mind working with two sets of technologies (at least, for now) is because I have intrinsically different requirements for the kind of data I put in a database vs. the kind of data I put in a filesystem, and it would be cost-prohibitive for me to use a database/filesystem hybrid abstraction.

I don’t find “the correct framing is controlled vs. uncontrolled changes” to be a compelling argument for using a hybrid system - what I would find to be extraordinarily compelling for a hybrid system is “here is an explanation of how we managed to build database-like capabilities at filesystem-like cost”.

1. 3

I’d argue that this premise is incorrect, because it considers requirements only from the perspective of capabilities when in reality there are also requirements from the perspectives of performance and cost

May be if the title (and therefore the implied premise) of the article was replaced into:

“Data lifecycle, the application way” or something like that It would reflect the intended capabilities better.

There is certainly a need to have technologies that can reflect ‘Life-cycle’ of an application object, and not just the structure of that object.

The document oriented databases, tried to (perhaps not always successfully) reflect a ‘structure’ of an application object, but not its life-cycle. Instead the application developers have to write code to accommodate the life-cycle.

In my reading, boomla recognizes that life-cycle gap, and seems to want to go beyond what we have today.

We look to day at ‘append only’ and ‘authenticated databases’ approaches are means to ‘simplify’ the life-cycle gap I noted above, but those are simplifications, are more of ‘atomic’ building blocks of something bigger, in my view.

When I architecture a system, that deals with ‘critical data’, I want to think of the data lifecycle as a whole from the time it created, it is read, it is transacted with, it is archived, it is backed up, it is restored, it is reviewed for compliance, it is referenced (this is a hard problem, that probably was not solved by MS’s CreateFileTransactedA that @malxau mentioned)

Today, I have to ‘custom design’ an ecosystem (assuming a large enterprise) around the above. But there is more that can be done in that space from the basic technologies prospective.

I agree with @liftM that may be trying to project the idea into known ‘light’ formalism (like databases or filesystem) may not carry the message, the intent in the best way.

1. 1

That’s an interesting way of looking at it. I have no clue about that space. Could you rephrase the gap you see?

One aspect I understand is audit-ability. Boomla can store every single change and currently does so. But I hardly see this being unique to Boomla, every copy-on-write filesystem does that. Backups, archives, and restoration all work but again I don’t see the uniqueness here.

I don’t quite follow what you mean with “it is transacted with”, “it is reviewed for compliance”, “it is referenced”. Maybe by transacting with, you mean the file is accessed? I see how that could be used for audition purposes, but again, that would not be unique.

I’d love to understand this.

I actually see the biggest value of Boomla in the entire integrated platform. It simplifies writing applications and eliminates problem spaces. Looking at any one of the eliminated problems, one can say, nah, that’s not a big issue. Yet as they add up, one ends up having a platform that’s 10x simpler to work with. That’s what I’m really after.

Thanks for your comment!

2. 1

If I understand correctly, the argument is that databases have in-memory indexes which require lots of memory while filesystems don’t do that and as a result need much less memory.

I don’t see why filesystems couldn’t index structured file attributes the same way databases index the data. Boomla doesn’t currently do that thus its memory footprint is super low. In the future, apps will define their own schema similar to database tables and define any indexes. At that point one will have to ability to create in-memory indexes.

Again, I do not see this as conflict. Did I miss something?

1. 1

The conflict is that I don’t need indexing on structured file attributes and I don’t want to pay for that extra capability. Cost is just as much of a requirement for me as capability.

I think that filesystems and databases genuinely are intrinsically different abstractions (as opposed to the article’s premise that filesystems and databases are not intrinsically different, and that the real differentiation for data storage abstractions is along the “controlled vs. uncontrolled changes” axis) because they have different cost profiles, optimized for different workloads.

databases have in-memory indexes which require lots of memory while filesystems don’t do that and as a result need much less memory

[nit] Database indexes also live on disk - indexing on a field necessarily requires more space (and therefore costs more) than only storing the field.

1. 1

I think we are both right in different contexts.

If your main concern is cost, sure, that’s an optimization. In that case you should do whatever you can to reduce storage requirements.

The context I was exploring is a general purpose storage system for web development. In this context, storage space is not the key thing to optimize for, it is the developer’s brain capacity and working efficiency.

1. 2

I’m starting my vacation! I have a pair of absolutely insane side project hacks I’d like to try. I have no idea if these make sense - they are currently at the “I thought about them once in the shower” stage.

1. Transparent autosharding for Postgres. Create a service that sits between an application and a database instance. Record incoming queries. Given the schema of the database and the recorded queries, do some kind of math to suggest good fields to automatically shard on (for example, “given current access patterns, sharding on field X would evenly split load between two instances”). Super ambitious stretch goal: automatically spin up a new database instance with the sharded table, and transparently rewrite queries so they correctly retrieve data from the new shards as well.
2. True polyglot programming. This is one of those “never use in production but this would make for a hilarious repo” ideas. Basically, autogenerate RPC bindings for a couple different languages and figure out how to get different languages talking to each other in an idiomatic way (e.g. spin up a Python process for the Python files in the project, autogenerate some server stubs, then autogenerate client code so my Ruby program now looks like it can just call the Python magically and it just works). My goal is to have a repository written in many languages where it feels like each language is just doing regular function calls into the other languages. Definitely a terrible idea for an engineering team but feels hilariously crazy as a hack.

I’m on vacation next week so working on one of these will be a great change of pace from the more management focused stuff I’ve been doing at work.

1. 25

I would love to see some examples where a company changed the license of their codebase to comply with a dependency. I have never seen this happen in my entire career, even though they are obligated to. I have seen countless examples of people rewriting code to rid of a dependency with an incompatible license, I have even done it myself.

I understand and respect the pure ideology of strong copyleft licenses (that in a perfect world, everything would be open source), but I don’t believe that it actually yields more open source in the world. In practice, people simply avoid it and it yields more duplicated work that ends up either proprietary (more common worst case) or more permissive (best case).

It is difficult to prove, but I feel that the “leading by example” ideology of permissive licenses is responsible for far more code being made open source in practice (though I acknowledge this is not everyone’s goal).

1. 16

I think OpenWRT exists because linksys had to do this.

I was just looking into this question myself today and have this history of openwrt on my reading list if that helps.

1. 5

Linksys did that, and then stopped maintaining the code, and switched to another OS for most of its hardware. Was that VMX, perhaps? I don’t remember. Some commercial OS. They stated that the reason was that linux needed too much RAM, which I find difficult to believe. Slimming down a linux kernel is IMO likely to be simpler than porting a code base to a new OS, so I tend to believe that Linksys’ stated reason was a polite lie. They did release a single model that ran linux, and I bought that model.

1. 4

I believe it was VxWorks

1. 1

All the vendors do a fantastically bad job.

1. 1

When you say that everyone in a specific field does a fantastically bad job, you should also consider the possibility that your assessment might be off, and why that might be the case.

2. 2

Queued that up as well! I also had a WRT54G(L) for a very long time, excellent device.

3. 11

I have seen countless examples of people rewriting code to rid of a dependency with an incompatible license

This is a very good case, IMO, and is my primary motivator for using (A)GPL on most of my newer work. I would much rather force the big bads to rewrite my code than simply profit off my work, and we have some evidence that they will do that to avoid (A)GPL sometimes.

I would love to see some examples where a company changed the license of their codebase to comply with a dependency.

I think to be fair on this one you have to also include all the code that started or stayed freedomware because of the requirement. This would include the example from the OP of the same downstream hosting the other project in compliance.

1. 13

I would much rather force the big bads to rewrite my code than simply profit off my work, …

I don’t know who you imagine is “the big bads”, but in reality it’s a lot of people like me who release all their projects under permissive licenses like MIT.

1. 11

You can use an (A)GPL’d dependency on an MIT’d project, just the resulting “combined work” is effectively (A)GPL’d. Some projects even have build flags to choose to build in “GPL mode” or not depending on which dependencies you use. It’s all about goals.

If you want your software to be used to build nonfree products so badly that you reimplement something under the GPL to use as a dependency for your otherwise MIT’d project… I mean, more power to you, right? It’s your choice.

We have examples of Apple, Google, VMWare, Linksys, and others doing rewrites to avoid using GPL’d code, and I would say that is the point, for me.

1. 15

I wrote before:

It is difficult to prove, but I feel that the “leading by example” ideology of permissive licenses is responsible for far more code being made open source in practice (though I acknowledge this is not everyone’s goal).

My goal is to provide as much value to the society as possible through each unit of my effort. I want people to use my code, I want people to profit from my code even if I get nothing for it, I want people to build things they otherwise wouldn’t have built because my code enables it. I am going to make the effort to make my code as accessible and permissive as possible, this often means avoiding (A)GPL dependencies and often duplicating effort in the process.

I recognize that your goal is not the same, and that’s fine. I just hope that you also recognize the reality that Apple/Google/VMWare/Linksys etc don’t care at all, they’ll simply not even look at AGPL code and move on. If they find AGPL code in their stack by accident, they will purge it. If they’re caught in a situation where they are in legal trouble, they will do the absolute minimum to comply with that version and purge it moving forward.

Overall, my bet is that choosing strong copyleft licenses has more of a net-negative effect on people who share my goal than any measurable effect on “the big bads”.

1. 9

Apple/Google/VMWare/Linksys etc don’t care at all, they’ll simply not even look at AGPL code and move on

again, I consider that a win for me

1. 6

It sounds as if your primary aim is to prevent some some people from using your code, without blocking access for too many other people. As opposed to the BSD/MIT/Apache licenes, whose primary aim is to make software available for all to use, without any attempt at dividing the world into us and them.

1. 5

Close. The goal is to prevent some uses which in this world tends to leave out some users.

1. 1

The goal is to prevent some uses

It is obligatory at this point to remind everyone that the AGPL should not be considered a Free Software license, as it does not grant Freedom 0. In fact, its entire purpose is to withhold Freedom 0 from recipients of the software in order to try to gain leverage over them.

1. 8

The AGPL only triggers if you modify the software (since otherwise no copyright is in play and no license would be relevant). So if you just run unmodified software (freedom 0) the AGPL does not apply or restrict you.

1. 5

It is obligatory to point out that the people who defined Freedom Zero, and in doing so defined Free Software, also explicitly state that the AGPL is absolutely a Free Software license.

Your point is mooted.

1. 3

The FSF’s stance on freedom is that you shouldn’t be allowed to have too much of it, lest you use it to do things the FSF disapproves of.

The AGPL was simply a reaction to the discovery that there were more things of which the FSF disapproved and which had not been foreclosed by prior licenses, so a new license was concocted to ensure that dangerous freedom wouldn’t get around too much.

1. 3

The logical gymnastics of both using the FSF’s definition of Free Software while rejecting their definition of Free Software is awesome to behold, and honestly would put Simone Biles to shame.

1. 2

I would flip that around and suggest that the rhetorical gymnastics the FSF uses to try to trick people into thinking their positions are coherent are something to see.

Essentially, they want to bludgeon everyone else with an absolutist position, while never being held to that same absolutism in their own actions. Or, more succinctly, they want to be able to compromise “freedom” when they think doing so will achieve a higher/larger goal. But woe to anyone else who tries doing that – then they’ll tell you that compromising freedom is never acceptable, no matter how good or great the thing you’d achieve by doing it!

Their adoption of the AGPL, which does not conform to their own original definition of Free Software and on those grounds never should have been accepted as a Free Software license, is just one especially obvious proof of that.

2. 6

My goal is to provide as much value to the society as possible through each unit of my effort.

I want freedom for users to educate themselves and contribute as opposed to becoming mindless consumers.

That’s why I believe AGPL is a good license for applications.

I also believe that for libraries and frameworks MIT, APL or MPL work better to achieve that goal.

Having more educated people - in my opinion - is better than having more usable code in the long-term.

1. 3

Overall, my bet is that choosing strong copyleft licenses has more of a net-negative effect on people who share my goal than any measurable effect on “the big bads”.

As someone who also prefers to release under permissive licenses: this, a million times. Big companies will always have a way to work around copylefted software, so it literally is not cutting them off from being able to do things the FSF disapproves of. Like, it’s not causing them to angrily shake their fists and yell “I would have gotten away with it, if not for you meddling Free Software kids!” It’s just causing them to use alternatives that aren’t under the FSF’s licensing regime.

Meanwhile, as the FSF gets ever more paranoid about ever more arcane “loopholes” in its licenses, the worries of small-time open-source developers go up as we juggle increasingly large transitive dependency trees that might have anything lurking in them. Not to mention whatever “loophole closure” the FSF might roll out next with brand-new extensions of what is or isn’t a derivative work.

2. 9

I think the NcFtp client famously changed its licence to the GPL so it could use Readline… then in 1999 or so it switched licences again. The copyright.h file included in ncftp 1.9.5 says:

static char copyright[] = "@(#) Copyright (c) 1992, 1993, 1994, 1995 by NCEMRSoft and Copyright (c) 1985, 1989 Regents of the University of California.\n All rights reserved.\n";


…but the comment at the top of that file says “All rights reserved” and:

Redistribution and use in source and binary forms are permitted provided that: (1) source distributions retain this entire copyright notice and comment, and (2) distributions may not be sold for profit on physical media such as disks, tapes, and CD-ROMS, without expressed written permission.

…which is granting some rights so clearly they’re not all reserved.

Meanwhile, Wikipedia cites a Common Lisp implementation named “CLISP” as having switched to the GPL but I’m not sure what licence it switched from.

As perhaps a more famous example, the Objective C system that Mac OS X used at least during the PPC era was GPL’d because back in the day NeXT wanted to use GCC as their compiler, and the FSF said they couldn’t use GCC and keep the Objective C bits proprietary. Of course, as soon as Mac OS X got serious momentum behind it, Apple poured resources into LLVM and Clang…

1. 4

That is a fascinating journey, thank you for sharing!!

Wonder if there’s anything more recent? Mid-90s certainly predates my career. I feel I am more in tune with modern open source culture, also I remember reading somewhere that more permissive licenses like MIT really took off in the era of Github.

2. 8

At a previous employer we wanted to use an AGPL-licensed library as part of our SaaS offering. We wrote the extensions that directly linked to it into its own microservice and licensed that as AGPL and put it on GitHub. Rest of the SaaS product stayed proprietary since calling the AGPL parts over HTTP does not trigger the AGPL. Well, the legalities on that are very unclear, since “intimate enough” on the GPL FAQ. Not sure if we did the right thing legally, and morally I’m even less sure.

Last I heard the library in question was relicensed as BSD, so the issue is moot and nobody is using the old one anymore.

1. 8

I promise you that Apple did not want to LGPL webkit, but they did really want to use KHTML in it. Google may or may not have open-sourced Blink if webkit hadn’t been copyleft, but they almost certainly wouldn’t have used a copyleft license.

1. 7

The place I work at builds tools that help other companies stay compliant with open source licenses. A lot of our bigger and most risk-averse customers (e.g. hardware manufacturers) actually take the stance that once GPL is brought into their first-party code, that code is “tainted” (i.e. you can’t make it compliant again just by removing the GPL dependency, because the commits where the GPL dependency were integrated are forever tainted by GPL and are forever in the commit history of any subsequent commits). Their default action is actually to publish “tainted” parts of their code base as open source to stay compliant - they feel that they’d rather publish some maybe-not-super-important parts of their IP rather than risk the trouble of a lawsuit.

1. 4

Place I used to work had a codebase under GPLv2 (containing lots and lots of GPLv2 source by other people), decided it would be convenient if their stuff was AGPL instead, got told “no that’s impermissible” (I can’t remember if they actually tried it out they got told no before actually trying it) and went with GPLv2 instead of making a huge mess out of it. Dunno if that’s close enough to count.

Replacing all the GPLv2 code in there would’ve cost about the company’s yearly turnover times two, prolly, so doing anything other than just complying with the license as written was a non starter.

1. 2

I know of several cases where the licensing was changed from foo to “either foo or gpl, your choice”, but I don’t think that’s what you really had in mind, right? You had in mind a change that grants users substantial additional rights?

So I agree with your intuition that the permissive licenses have achieved more, even if not quite the same.

1. 3

Right, what I had in mind is more going from “we have a proprietary/commercial license/closed source codebase” to “we open sourced it under AGPL/GPL to comply with the requirements of a dependency we just added or had all along and didn’t realize.”

1. 3

Yes, and I think that if that were a significant effect, then I would have noticed it by now.

FWIW I worked at Trolltech until 2001; the team members’ reactions to the mail we got from GNU fans from 1994 until I left weren’t in the least favourable. At the time I thought I was special, we were special, but maybe we weren’t. Maybe most people who are, uhm, educated by GNU fans react negatively to the experience.

1. 1

Curious to hear more, what kind of mail did you get? Do you mean regarding most of the stack being GPL licensed?

1. 1

What stack being GPL? Libc and libX11 wasn’t, etc.

GNU fans sent us a lot of mail that might be described, somewhat uncharitably, as walls of text written by people who had much spare time and little salesmanship talent. For someone who has code to write and customers to help, dealing with yet another clueless wall of text is unappealing or worse.

2. 1

I would love to see some examples where a company changed the license of their codebase to comply with a dependency.

I think this is a weird standard. Alternatively: examples where existing reciprocally licensed codebases were built upon instead of started from scratch?

• GCC and its myriad of backends including …
• Objective-C
• Linux
• Webkit / Blink
• MySQL
• Heaps of emulators
• Git
• ffmpeg
• Blender
• VLC

I feel like this is a target rich environment. What domains do you care about?

(* why is it always a company?)

1. 1

Consider it a focus group.

The viral clause affects two groups: People who want to the viral clause to bind others, and people who are bound by the clause and wouldn’t have chosen the GPL otherwise. If you want to know about the viral clause of the GPL, then it makes sense to look at the reactions of a focus group inside each group. GP’s question is a nice way to find some in the latter group.

1. 1

The viral clause

The use of “viral” makes me worry this isn’t a good faith response…

The viral clause affects two groups: People who want to the viral clause to bind others, and people who are bound by the clause and wouldn’t have chosen the GPL otherwise.

GPL code has no agency. That latter group chose to use GPL code. I see no complaints of “we pirated Oracle and now we have to pay a licensing fee” or “we pirated Oracle to get traction, and now we’re forced to rewrite.”

And I think there are more than two groups. e.g. people who specifically choose to work on the GPL projects.

1. 1

“Viral” was common parlance when I learned about the GPL, in the early nineties. I agree that it has acquired more negative connotations since then.

More unaffected groups don’t matter. Unless you want to argue that the pool of people who’ll work on, say, GPL’d code but not APL’d code or closed-source code is so large that it will affect companies’ strategy for theiir implementation work?

1. 1

I think most license selection is driven more by the authors and less by their lawyers, yes.

1. 22

I agree lots of people don’t, because they never even bother to learn anything past GRANT ALL…

So, who out there has used these features?

We use PG’s row-level security exclusively. It is 100% worth the introduction pain. Every user of our software has their own PG DB login, and that’s how they login to the application.

How did that impact your application/environment?

The application does only 1 thing with access control, what shows up on the menus(well and logging in). 100% of the rest of the app is done via PG RLS, and the app code is a bunch of select * from employees; kind of thing.

What have you used them for?

Everything, always! :) lol. (also see next answer)

Do they provide an expected benefit or are they more trouble than they’re worth?

When we got a request to do a bunch of reporting stuff, we connected Excel to PG, had them login with their user/password to the PG DB, and they were off and running. If the user knows SQL, we just hand them the host name of the PG server, and let them go to town, they can’t see anything more than the application gives them anyway.

When we added Metabase, for even more reporting, we had to work hard, and added a reporting schema, then created some views, and metabase handles the authorization, it sucks. Metabase overall is great, but It’s really sad there isn’t anything in reporting land that will take advantage of RLS.

How did you decide to use them?

When we were designing the application, PG was just getting RLS, we tried it out, and was like, holy cow.. why try to create our own, when PG did all the work for us!

Trying to get access control right in an application is miserable.

Put permissions with the data, you won’t be sorry.

1. 6

Doesn’t this require a lot of open connections? IME, postgres starts to struggle past a couple of hundred open connections. Have you run into that at all?

1. 5

If you run everything inside of transactions you can do some cleverness to set variables that the RLS checks can refer to, emulating lots of users but without requiring more connections.

1. 2

See my other comment, but you don’t have to work quite that hard, PG has a way now to become another user.

1. 2

How many users does this support? I might be being overly cautious, but we have been told to look at user counts in the millions.

1. 2

We are an internal staff application, we max around 100 live open DB connections, so several hundred users. This runs in a stable VM with 32GB ram and < 1TB of data. We will never be a Google or a Facebook.

One can get really far by throwing hardware at the problem, and PG can run on pretty big hardware, but even then, there is a max point. Generally I recommend not optimizing much at all for being Google size, until you start running into Google sized problems.

Getting millions of active users out of a single PG node would be hard to do, regardless of anything else.

2. 2

In our experience, the struggle is around memory, PG connections take up some memory, and you have to account for that. I don’t remember the amount per connection, but this is what I remember.

It’s not entirely trivial, but you can re-use connections. You authenticate as a superuser(or equivalent) send AUTH or something like that after you connect, to lazy to go look up the details.

We don’t currently go over about 100 or so open active connections and have no issues, but we do use pgbouncer for the web version of our application, where most users live.

EDIT: it’s not AUTH but almost as easy, see: https://www.postgresql.org/docs/12/sql-set-session-authorization.html

3. 3

How do RLS policies impact performance? The Postgres manual describes policies as queries that are evaluated on every returned row. In practice, does that impact performance noticeably? Were there gotchas that you discovered and had to work around?

1. 3

Heavily.

It is important to keep your policies as simple as possible. E.g. if you mark your is_admin() as VOLATILE instead of STABLE, PG is going to happily call it for every single row, completely destroying performance. EXPLAIN is your best friend.

But even then, some queries are performed needlessly. Imagine you use transitive ownership. For example Users own Boxes, Boxes contain Widgets. When you want to determine what Widgets can User manipulate, you usually cache the User-Boxes set at the application server level and query “downwards”. With RLS, you need to establish a link between the Widget and User, joining over Boxes “upwards” as there is no cache.

The real problem here is that with sufficiently large schema, the tools are lacking. It’s really inconvenient to develop within pgAdmin4, away from git, basically within a “live” system with its object dependencies and so on.

1. 2

It can, as I mentioned in my other comment in this thread, we have only run into a few instances where performance was an issue we had to do something about.

As for tools, We use liquibase[0], and our schema’s are in git, just like everything else.

1. 1

I’ll check it out.

1. 1

How does the Liquibase experience compare to something like Alembic or Django migrations?

The main difference I see is whether your migrations tool is more tightly coupled to your app layer or persistence layer.

With Alembic you write migration modules as imperative Python code using the SQL Alchemy API. It can suggest migrations by inspecting your app’s SQL Alchemy metadata and comparing it to the database state, but these suggestions generally need refinement. Liquibase appears to use imperative changesets that do basically the same thing, but in a variety of file formats.

1. 2

I’m not very familiar with alembic or django migrations. Liquibase(LB) has been around a long time, it was pretty much the only thing doing schema in VCS back when we started using it.

Your overview tracks with my understanding of those. I agree LB doesn’t really care about the file format, you can pick whatever suits you best.

The LB workflow is pretty much:

• Figure out the structure of the change you want in your brain, or via messing around with a development DB.

• Open your favourite editor, type in that structure change into your preferred file format.

• Run LB against a test DB to ensure it’s all good, and you didn’t mess anything up.

• Run LB against your prod DB.

• Go back to doing whatever you were doing.

2. 1

We actually use an OSS extension veil[0], and while performance can be an issue, like @mordae mentions, if you are careful about your use, it’s not to bad. We have only had a few performance issues here and there, but with explain and some thinking we have always managed to work around it without much hassle. You absolutely want indexes on the things you are using for checking permissions with.

Veil makes the performance a lot less painful, in our experience.

0: https://github.com/marcmunro/veil Though note, veil2 is the successor and more relevant for new implementations, that we don’t currently use(and have no experience with): https://github.com/marcmunro/veil2

Veil2 talks about performance here in 23.1: https://marcmunro.github.io/veil2/html/ar01s23.html

3. 3

Same here, I used row level security everywhere on a project and it was really great!

1. 2

One mistake I’ve made is copy-pasting the same permission checks on several pages of an app. Later I tried to define the permissions all in one place, but still in the application code (using “django-rules”). But you still had to remember to check those permissions when appropriate. Also, when rendering the page, you want to hide or gray-out buttons if you don’t have permission on that action (not for security, just niceness: I’d rather see a disabled button than click and a get 403).

With row-level permissions in the DB, is there a way to ask the DB “Would I have permission to do this update?”

1. 2

Spitballing but maybe you could try run the query in a transaction and then roll it back?

Would be a bit costly because you’d have to run the query twice, once to check permissions and then again to execute, but it might be the simplest solution.

1. 1

Maybe what you need is to define a view that selects the things you can update and use that view to define the RLS. Then you can check whether the thing you want to update is visible through the view.

1. 1

With row-level permissions in the DB, is there a way to ask the DB “Would I have permission to do this update?”

Yes, the permissions are just entries in the DB, so you can query/update whatever access you want(provided you have the access to view/edit those tables).

I’m writing this from memory, so I might be wrong in the details… but what we do is have a canAccess() function that takes a row ID and returns the permissions that user has for that record. So on the view/edit screens/pages/etc, we get the permissions as well returned to us. So it’s no big deal to handle.

2. 1

Follow question: How did you handle customers (accidentally even) writing expensive sql queries?

1. 2

We would admonish them appropriately :) Mostly the issue is making sure they know about the WHERE clause. It hasn’t been much of an issue so far. We have _ui table views, that probably do 90% of what they are wanting anyways, and they know to just use those most of the time. The _ui views flatten the schema out, to make the UI code easier, and use proper where clauses and FK joins, to minimize resource usage.

If our SQL user count grew enough that we couldn’t handle it off-hand like this, we would probably just spin up a RO slave mirror, and let them battle each other over resources and otherwise ignore the problem until we got complaints enough to upgrade resources again.

1. 4

I’m a bit of a skeptic of Postel’s law as a guiding principle. And I’m not even talking about in the sense of “it’s a good rule of thumb, but obviously there are exceptions.” I mean that I actually think it might be the wrong idea in some contexts.

In the context of something like a REST API, I can understand maybe ignoring JSON fields that you don’t need. But if someone sends a JSON body when you don’t expect anything at all? I think it’s pretty clear that the client misunderstands the API and almost anything you do is likely not what they expect. Same thing with sending extra query parameters. If they sent them, then they probably expect them to affect the results. Is it really best to let the client think they succeeded when they probably didn’t succeed in what they expected?

1. 2

I agree that the implementation of an external SaaS API implementation seems like the wrong place to aggressively apply Poster’s Law.

We need to differentiate between two interpretations of “robustness”:

1. The program accepts as many inputs as possible that could be conceivably valid.
2. The programs accepts inputs in such a way that maximises the chance that its behaviour is correct.

Postel’s Law seems to strongly advocate for the former, but I think the latter is actually much more helpful for API consumers. (Generally, I would argue that (2) is the better thing to optimize for whenever your program could have side effects, such as “being billed for an API call” or “modifying user data”.)

1. 19

This article is a bunch of concept name-dropping and strong but unsupported assertions without a lot of explanation. It was hard to tell whether any of the explanations were correct because none of them were precise enough to be falsifiable.

For a much better explanation of categories, see Category Theory for Programmers.

1. 4

The author has published versions three distinct version of that for Haskell, Scala, and Ocaml. Very nice.

1. 3

Given a formal CS background, there probably isn’t a whole lot extra computer science that will be useful to brush up on before starting the job. I would focus on books covering project management, especially case studies. Two pieces of reading I’ve found to be very useful:

1. Strategically: The Phoenix Project is phenomenal (and more importantly, hilarious and easy to read). It’s a case study of a fictional team in a project management death spiral, and how they pull out of the death spiral. As I read the book, I definitely had many moments where I thought “this literally happened to me last week” and “I know exactly who this character is on my team”.
2. Tactically: Avery Penwarr has an excellent blog post on his philosophy for project management. This should honestly be required reading for anyone project managing an engineering project (whether that’s a TL, EM, project manager, or just an engineer).

That said, I found this reading useful a couple years into my career (not because it was not useful at the beginning, but because I discovered them a couple years in - I have no data on how useful this is at the beginning). I suspect you might find them unrelatable or highly theoretical. I’d recommend coming back to these about a year in, and seeing how the additional experience changes your perspective.

(If you end up reading them, I’d love to hear about your initial impressions! Helping educate better engineers is a topic I’m very interested in, and I’m curious about how we can better build project management skills.)

1. 1

I will definitely look into these! I found that most of the hard problems that I or my team would encounter during my internships were more organizational rather than technical.

1. 2

It’s nice to see slow and steady progress on what seem to me to be “boring” engineering things like the standard library and performance. Out of curiosity, do Lobsters who use golang in anger find the balance picked by the golang team there good or bad?

1. 14

Despite working on PL tools and having a bunch of PL theory and Haskell nerds, my team has found Go to be a really productive tool in practice. My likes:

• Opinionated, clear idioms. Code reviews waste less time on nits.
• An acceptable, easy-to-explain type system. For a startup, the common alternative (since iteration speed is our constraint) is honestly Node.js or Ruby. (Our starting codebase is TypeScript-on-Node.js, which has its own set of glaring frustrations. In particular, the drift between NPM modules and their type declarations is a serious and unavoidable footgun.)
• A lot of stuff just works out of the box (JSON parsing in the standard library, you can build an HTTP server with the standard library, etc.).
• A native test runner. This is a huge win for junior engineers to not need to learn another library.
• Not enough rope to hang yourself with. It’s more annoying to try to do the elegant thing, so people focus on getting it working first. This is great at helping junior engineers avoid rabbit holes, while senior engineers still have enough tools to build acceptable abstractions (you have to hold them the right way, but right is usually obvious) with acceptable developer overhead (annoying, but not devastating).

My biggest complaints are the lack of sum types and write-a-for-loop-for-every-slice-type syndrome. Errors also take a while to get used to, but are not nearly as bad as they appear once your team has developed a sense of how to use them correctly (e.g. when do I wrap an error vs. use its message? Answer: use the technique that signals your intent for this specific abstraction).

We also have a smaller, domain-specific tool that does build analysis that’s written in Haskell. The trade-offs between these languages are very stark. From an technology standpoint, I would argue that they fulfil different niches.

Our programs in Haskell can express much more complex business logic much more clearly, in a big part thanks to algebraic data types (please, Robert Griesemer, all I want is sum types) and effect tracking (intended invariants are clearer, and are compiler-enforced). My biggest complaints here are:

• Learning Haskell takes a specific interest and a long time. Any programmer can pick up Go in half a week. Our experience ramping Haskell engineers is about 3 months to trustworthy independent code review and 6 months to creating new projects. For their first month and a half, they spend a lot of time pairing.
• There are too many ways to do things. Should we use an effect system? Which one? How should we handle records? Which pipeline library do we use? Servant seems cool, should we try it? For engineers just learning Haskell, this steepens the learning curve significantly.
• Beginner-unfriendly documentation. It takes a while to grok types-as-documentation (empirically, about 4 months). Before then, selecting new libraries is very difficult for engineers new to Haskell because (1) they don’t understand what different APIs are possible and the trade-offs between them and (2) libraries often use language extensions or advanced techniques and they don’t understand the library’s source code.
• Debugging experience is poor. When libraries use advanced language features, it’s often unclear from error messages whether you’re using the library wrong or the library has a bug. This is exacerbated by sparse documentation and difficult-to-read library source. This is a far, far cry from jump-to-definition/jump-to-implementation in Go.

If going the Haskell route, make sure that your team has a lot of senior engineers who have significant experience in Haskell. Otherwise, your team will spend a lot of time figuring out the basics before they can become productive. The main benefit of Haskell is that an engineer who is ramped up on Haskell can jump into a large, complex Haskell codebase and almost immediately understand the core abstractions and invariants. These abstractions and invariants are also expressible in other languages, but are usually expressed through idiom and are less compiler-enforced.

The biggest barrier to Haskell adoption here is that if your team already has that many senior engineers, they will likely be more productive in a language they can pick up quickly, and everyone can pick up Go.