停止挖掘,开始构建:为什么我们需要乐高积木,而不是更深层的类型系统
Stop Digging and Start Building: Why We Need Lego Parts, Not Deeper Type Systems

原始链接: https://programmingsimplicity.substack.com/p/stop-digging-and-start-building-why

## 超越类型:对可组合软件的呼吁 作者认为,当前的软件开发方向存在偏差,过于关注日益复杂的类型系统,而忽视了基础的可组合性。虽然函数式编程(FP)提供了优雅性,但它对于构建真正模块化的软件来说是一个死胡同,这些软件适用于现代并发系统,例如物联网和分布式计算。FP固有的同步特性以及对数据转换(过滤器)的关注,限制了它处理现实世界复杂性的能力,例如时序、排序和并行性。 相反,作者提出了一种新的基础架构,建立在五个关键要素之上:简单的传输、递归部件定义、超越简单过滤器的通用部件、纯粹的异步消息传递(“发射并忘记”)、以及严格的基于端口的连接。这种方法旨在实现“类似乐高积木”的软件构建,从而实现真正的封装和抽象。 现有的解决方案,如UNIX管道,展示了这种潜力的部分,但缺乏关键特性,例如扇出。核心问题在于同步编程中固有的隐藏控制流——这是FP和传统CPU架构的共同限制。作者提倡将重点从证明类型安全转移到创建允许开发者从简单、可组合的部件构建复杂系统的基本元素,优先考虑实用性而非理论上的美感。

最近一篇Hacker News上的帖子讨论了文章“停止挖掘,开始构建:为什么我们需要乐高积木,而不是更深层的类型系统”。核心论点在于,过于复杂的编程解决方案,例如日益复杂的类型系统,并不是构建可靠软件的答案。 作者提倡一种更务实的做法——专注于易于使用的“构建块”,类似于乐高积木。一位评论员指出,文章中描述的状态管理需求,与操作系统作为函数式编程局限性的变通方法的功能相呼应。 许多回复指出,描述的解决方案与Erlang/BEAM系统非常相似。讨论还涉及编程语言研究的现实期望,以及问题是否在于*如何*应用函数式编程,而不是范式本身。最终,这篇帖子引发了关于软件开发中简单性与复杂性的争论。
相关文章

原文

The smartest people in our field are digging in the wrong direction.

While brilliant minds burrow ever deeper into type systems—adding dependent types, effect systems, and increasingly sophisticated type-level programming—we’re missing the forest for the trees. We don’t need smarter types. We need a better substrate for building composable software. We need LEGO parts.

Functional Programming: A Dead End for Composability

Let me be direct: functional programming, despite its elegant mathematics and appealing purity, is a dead end when it comes to achieving true LEGO-like composability in software.

The problem isn’t what FP does—it’s what it actively prevents. FP is suitable for expressing one kind of program: a calculator, where the result is the only thing of importance. In today’s world, with today’s hardware, the result isn’t the only item of interest. We care about how we get there—relative timing relationships, sequencing, parallelism, concurrency. We’re building for the Internet, robotics, IoT, distributed systems.

Yes, we can figure out workarounds to accomplish all of these things and force-fit them into a warped version of the functional paradigm. But it’s better to actually solve the real problem instead of expending brain power on workarounds.

What We Actually Need

To build software from true LEGO parts, we need five fundamental elements:

1. A Super-Simple Transport Mechanism

Not a labyrinth of complicated type parameters. Not GADTs or higher-kinded types. Just a straightforward way to move information between parts. The complexity budget should go into what we’re building, not into the plumbing.

2. Recursive Part Definition

Think of it this way:

This is like Lists and Atoms in Lisp, but instead of data types (numbers, symbols, strings, lists), these are types of code structure. Containers within containers, all the way down—until you hit a leaf that actually does something.

3. Parts That Aren’t Just Filters

The functional approach fetishizes filters: one input, one output, data flowing through like water through pipes.

But LEGO blocks don’t only connect in straight lines. You can combine them in countless ways—sideways, stacked, branched. Real software parts should offer the same freedom. Sequential pipelines are just one way to connect parts, not the only way.

4. Pure, Asynchronous Message-Passing

Here’s where functional programming reveals its fundamental flaw: FP only gives us impure, synchronous message-passing.

When a function calls another function, it blocks. It waits. It suspends. This is synchronous operation by definition—it can never be truly asynchronous.

This is synchronous control flow. The synchronous nature of data delivery in the paradigm expects that action or reaction to the data be forced to happen immediately, while the caller waits/blocks. This is the fundamental nature of the functional paradigm. We can find workarounds like adding queues, but the paradigm itself discourages better approaches by making us work harder to imagine workarounds. The idea of synchronicity is built into the notation. We need a notation that has asynchronicity built into it at a fundamental level—one that encourages us to think that way instead of discouraging thoughts in these directions. Today, most programming languages cause us to write code with a built-in synchronous assumption: we hard-wire the code to know that a result will be returned before it can proceed. In a fire-and-forget paradigm, we wouldn’t write code that way, nor would we end up in inconvenient places like callback hell.

What we actually need are software units that fire-and-forget: asynchronous message passing with no constraint on when the message will be processed. Pass data only, with zero assumptions about timing.

But here’s the thing: when timing does matter, we should be allowed to reason about it and express it. Protocols, state changes, statecharts—these are fundamental to real-world software. The FP paradigm forces us to hide this behavior in the bowels of operating systems and to find workarounds that force-fit expression of timing and sequencing into the functional paradigm. Since FP gives us only one way to think about control flow (sequential), we have to run the fans in our brains on High to find workarounds.

Ever wonder why we need operating systems? They’re elaborate workarounds for the functional programming concept.

An OS converts long-running synchronous flows of function-calling-function behavior into state machines by silently saving state somewhere deep in the system (process descriptors, stack frames, registers).

But here’s the kicker: the Software Architect gets no say in how this synchronous flow is chopped into states. The partitions are determined dynamically by an algorithm at runtime, and they change over time.

Dynamic anything is harder to debug. When you can’t control how your code is partitioned into states, you’re left scratching your head when problems appear.

Protocols that run silently underneath our software aren’t good enough. We need better control. If we want to buy-in a fancy algorithm, we should be able to plug it in like a LEGO block instead of compromising with what the operating system has deemed is needed in our specific case, or by futzing with a zillion parameters and options and priorities and unforeseen gotchas.

5. Ports and Gates

Information flow must not cross the boundaries of parts willy-nilly. Flows can only connect to ports at the edges of parts. The outside connection point is a “port”; the inside connection point is a “gate.”

This enforces true encapsulation at the architectural level, not just at the code level.

UNIX Pipelines: Good Start, Wrong Direction

UNIX pipelines gave us a taste of LEGO parts, but only along a single dimension. They’re inspired by functional thinking: one input, one output, rendezvous semantics, no fan-out.

cat file | grep pattern | sort | uniq

Beautiful, yes. But limited.

We need to build on these ideas and modernize our workflow. We need to add fan-out, even if it offends FP purists.

Why Fan-Out Matters

Fan-out makes developer experience vastly simpler:

  • You can lasso a bunch of parts and abstract them into another layer

  • That layer can have as many or fewer input and output ports as the original

  • Fan-out makes it possible to build true black boxes

    • One input on the outside

    • You don’t care how many places it goes on the inside

    • The box is “black”—you can’t see inside, you only see it as one thing

    • If the architect did their job right, the black box appears simpler than what’s inside.

This is exactly what happened with computers and CPUs. We had big blobs of thousands of electronic components (transistors, tubes). Then someone produced a “narrow waist”—80 opcodes instead of thousands of transistors. From that simplification, many people could build electronic devices and discover new combinations.

Why Black Box Abstraction Fails Today

Black box abstraction is essential for the LEGO part approach. So why does it fail in current development workflows?

Functional programming leaks control flow.

When a calling function blocks (control flow) while calling another function, it creates hidden coupling. That coupling causes black box abstraction to fail.

The rendezvous model—the expectation of synchronous, blocking interaction—plus the filtering model (1-in, 1-out) greatly restricts the possibilities for abstraction.

You can’t build arbitrary LEGO structures if every connection forces a blocking rendezvous.

The Cognitive Dissonance of Functional Programming

Let’s be honest about what we’re actually working with.

Synchronous, sequential programming is just a glorified form of assembler, inspired by how CPU ICs work. But CPUs are just building material. We need new ways to express what we want to build. Then, and only then, can we try to map those new expressions onto CPUs and sequencers.

Here’s the irony: functional programming actually violates the realities of CPUs. CPUs are bolted to RAM. CPUs are meant to mutate RAM—that’s their fundamental design. To think in terms of the functional paradigm, you have to prohibit mutation. Cognitive dissonance.

At its core, FP is just a fancy, complicated way to perform find-and-replace. We’ve built elaborate edifices of type theory on top of what is essentially pattern matching and substitution.

The Path Forward

We don’t need smarter type systems. We need simpler, more powerful primitives for composition:

  • Asynchronous, fire-and-forget message passing

  • Container/leaf part hierarchies

  • Fan-out and arbitrary connection topologies

  • Strict port-based boundaries

  • Zero hidden control flow

  • The ability to express and reason about timing when it matters

The mathematics of type theory is beautiful. But beauty without utility is just decoration.

It’s time to stop digging deeper into type systems and start building the substrate that will let us construct software from true LEGO parts—parts that snap together easily, parts that hide their complexity, parts that compose freely in any direction.

The future of software isn’t in proving more theorems about types. It’s in making software that fits together like LEGO bricks.

For a sketch of how black-box abstraction can simplify presentation of complicated ideas, see my article on PBP Black Box Abstraction, Ports, Fan-out.

What are your thoughts? Are we over-investing in type sophistication at the expense of compositional simplicity? Let me know in the comments.

See Also

Email: [email protected]

Substack: paultarvydas.substack.com

Videos: https://www.youtube.com/@programmingsimplicity2980

Discord: https://discord.gg/65YZUh6Jpq

Leanpub: [WIP] https://leanpub.com/u/paul-tarvydas

Twitter: @paul_tarvydas

Bluesky: @paultarvydas.bsky.social

Mastodon: @paultarvydas

(earlier) Blog: guitarvydas.github.io

References: https://guitarvydas.github.io/2024/01/06/References.html

Leave a comment

Share

联系我们 contact @ memedata.com