How do you explain open source to people who lack a programming background?

I recently had a conversation with a professor about programming and open source after he started a conversation about my hacktoberfest shirt. I told him about hacktoberfest and tried to explain what open source was, but I didn’t know how to explain it to him in a way that made sense, but I tried anyway and I ended up confusing him. He was shocked by the amount of work that goes into open source and was confused as to why people “work for free" as he put it. Does anyone have suggestions about how to explain open source to people who lack a programming background?


Functional programming is not a paradigm

Or; functional and object-oriented are members of two different classes.

What is a programming paradigm?

When somebody talks about a “paradigm", they might equate it with a "worldview" or an "ethos". A programming paradigm will certainly define a sense of right and wrong, like code smells and common patterns. A programming paradigm will tell you how to structure things at a macro scale; the minor scale is called a "style", or even just called a "language".

Object-oriented zealots will list some of these

Good patterns: hiding data behind action interfaces (tell, don’t ask), separating concerns into multiple objects
Code smells: allowing disassociated modules to directly manipulate data or each other

In an object-oriented game, the renderer will pull data from the game world, which then pulls data from the entities (or maybe it’s push-based instead of pull-based, but whatever). It is considered a code smell if the renderer is aware of different kinds of game entity, because those are disassociated concerns. The renderer may support more than one game world implementation by talking to an interface or an abstract base class, not by having special support for both.

Data-Oriented Programming zealots will list these:

Good patterns: causing actions to occur by sharing data
Code smells: directly invoking routines between subsystems

In a data-oriented game, the renderer queries a data store full of sprites, which was previously populated by the game world. It is considered a code smell for either the renderer or the game world to make a blocking invocation on the other, preferring that they instead share data (obviously, the world writes to it and the renderer reads from it). The renderer may support more than one game world implementation by having different game world implementations all capable of populating the data store.

Service-Oriented Programming: isolation, the actor model, let-it-crash, and the difference between services and objects

Good patterns: encapsulating data within a data store fronted by a service API
Bad patterns: shared, mutable data, and blocking

In a service oriented game, the renderer and the game world would have about the same relationship as in an object-oriented one. The main difference is that a service-oriented system almost always uses non-blocking calls.

What is Functional Programming?

The Pure Function, the Higher-Order Function, and the Closure

If you ask someone to define what makes a programming language supportive of functional programming, they would probably refer to higher-order functions, value-level functions, and closures. Stuff like this example Elixir code:
@spec callback_when_two_should_run(fn(integer) -> term) :: term
def callback_when_two_should_run(action) do
@spec convert_term_to_0ary_function(term) :: fn() -> term
def convert_term_to_0ary_function(value) do
fn -> value end

Is this functional, though?
def compute_the_total_of_two_bank_accounts(a, b) do
a + b

It’s certainly not object-oriented. There’s no encapsulation; the values of the bank accounts are directly exposed for any non-banking-aware behavior to mess with. It’s not service-oriented, since we haven’t defined an API for dealing with services. And it really isn’t very data-oriented, either, since a data-oriented system should connect the schema of the data with the data itself, not allowing numbers to just float around like this.
But what any expert in any of the three example paradigms would instantly tell you is that this is not a fair question to really ask. All of those paradigms deal with macro-scale software design decisions; something this small, as long as its kept within a module somewhere, could form a part of any good program, whether OO, SOA, or DOA, but on its own, it clearly isn’t from any of them.
But it’s still perfectly functional, in fact trivially so, despite not being big enough to actually have any "structure" per se. Object-oriented programming mandates that you isolate concerns behind facades of objects, service-oriented programming mandates that you isolate concerns behind service APIs, and data-oriented programming mandates that you isolate concerns into data schemas, but there ain’t no rule in functional programming that stops you from making this function, that operates on raw numbers, visible to the whole world. That’s why it’s not a paradigm.

What is functional programming, then?

That function is a perfect example of a "pure function", as defined, because it has no side effects.
Functional programming languages are (conceptually) executed by reducing expressions. You can compute_the_total_of_two_bank_accounts(1, 3), execute it by expanding the body of the function into 1 + 3, and perform the final execution step to turn it into 4 with an arithmetic rule. Having a functional language that is also Turing Complete requires that you extend it to be as powerful as the Lambda Calculus, a set of expression expansion rules that is capable of undecidable computation. "Pure functional programming language" has a much more solid definition than an "object-oriented language" (which is basically just an imperative language with a couple of gizmos added), and programming languages that claim to be data-oriented or service-oriented are pure niche.

Adhering to SOLID: writing functional OO code

More than that, there’s nothing stopping you from encapsulating data and following SOLID in an otherwise functional language. As an imaginary sensei once said, "objects are a poor emulation of closures, and closures are a poor emulation of objects":
@type person :: {fn binary -> person, fn -> binary, fn integer -> person, fn -> integer}
def new_person(name, age) do
fn name -> new_person(name, age) end,
fn -> name end,
fn age -> new_person(name, age) end,
fn -> age end,
def set_name({f, _, _, _}, name) do
def get_name({_, f, _, _}) do
def set_age({_, _, f, _}, age) do
def get_age({_, _, _, f}) do

Sorry for writing vtables by hand. I probably should’ve used a protocol, or at least written a macro to generate all that stuff, or used an Agent or something. But that would’ve missed the point that FP primitives are enough to do OO programming.
You can see how, just like any normal functional data structure, I "mutate" the person by returning a new instance with the applied changes. Even though this is a terrible example of good OO design (you should avoid getters and setters in favor of proper domain-appropriate message types), this same approach of using closures for state hiding and modeling object state by copying can be applied to any set of verbs.

Normalized data storage: writing functional data-oriented code

This is the inversion of OOP. Instead of encapsulating data and exposing behavior, you are exposing data and encapsulate behavior.
def age_management_system(person, date) do
case person.birthday do
^date -> %{person | age: person.age + 1}
_ -> person
def motion_system(position, velocity, time) do
%{position | x: velocity.x * time + position.x, y: velocity.y * time + position.y}
def registry(ecs) do
# Entity-Component-System implementation elided for brevity
|> add_system_for_component(&age_management_system/1, [:person, :date])
|> add_system_for_component(&motion_system/1, [:position, :velocity, :time])

High-level modularity: writing a functional SOA

That’s what the OTP is. Do what Erlang tells you to do with it.

So if it’s not a paradigm, and is in fact completely orthogonal to paradigms, then what is FP?

Functional Programming is a model of computation. It is computation without mutation (which would be imperative) and without backtracking (which would be logic-based). Practicing functional programming in a language that has support for one of these features is essentially restricting yourself, which even if it’s a good idea, does not make it a full-fledged paradigm.
Effectively writing functional code, then, still requires you to make a plan for your software system’s architecture beyond "functional design". Are you going to model your solution as message passing? Reaction to events? What, if anything, will you use to draw boundaries between parts of your system? Choose wisely.