English

Using a programming language well means understanding how its programs behave and how to build, inspect, test, and maintain them. Syntax is one part of that knowledge. You also need to know how values are represented, how state is shared, how errors propagate, and what the runtime and libraries provide.

A useful way to learn is to predict a small program’s result before running it. In Python, after a = [1] and b = a, executing b.append(2) also changes the list seen through a. Assignment made another reference to the same list; it did not copy the list. Knowing the punctuation without this model leaves an important class of bugs unexplained.

Language choice and language learning are related skills. A task requires particular capabilities, while a team has particular knowledge and operating constraints. Learning several approaches broadens the available choices, but fluency is demonstrated by solving and explaining problems, not by counting languages.

Prerequisites: Basic programming (§2.1). One small project in a familiar language provides a useful comparison. Programming-language theory (§3.4) supplies deeper explanations of types, evaluation, and abstraction as questions arise.

The Shape of the Skill: From One Language to Many

Earlier programming systems help explain why languages differ. Symbolic assemblers replaced many numeric addresses with names, and subroutine libraries let programmers reuse operations through calling conventions. FORTRAN made numerical formulas more directly expressible; Lisp supported symbolic computation; Simula connected programs with interacting simulated entities. These developments addressed different tasks rather than advancing along one scale from inferior to superior languages.

Structured programming emphasized control flow that could be reasoned about in parts. Functional, object-oriented, logic, and relational approaches supplied other ways to organize computation. Most widely used languages combine several approaches. A language with functions is not necessarily purely functional, and a language with classes does not require every problem to be modeled as an inheritance hierarchy.

Type systems, runtime checks, garbage collection, and ownership rules address different sources of error. Python type annotations can support external checking without automatically validating incoming runtime data. TypeScript checks source programs and normally removes type annotations when producing JavaScript. Rust’s safe abstractions rely on ownership and borrowing rules, together with correctly implemented unsafe boundaries. None of these mechanisms establishes that a business rule is right or prevents every resource leak, deadlock, or service failure.

Previous experience makes familiar concepts easier to recognize, but new languages can change assumptions about evaluation, representation, and concurrency. Learning a fifth language is not necessarily just learning new syntax. A programmer accustomed to eager evaluation may need to rethink when work happens in a lazy language; a programmer accustomed to shared mutable objects may need a new design under ownership constraints. There is no fixed number of days or years that defines fluency.

Choosing, Learning, and Thinking in Languages

Choosing a Language for a Task

Start from the task and the environment in which the result must work. Identify the deployment platform, required libraries, interoperability needs, latency or memory constraints, and who will maintain the system. A numerical prototype using an established library and a small embedded controller can have very different requirements even if both perform arithmetic.

Separate a language from a particular implementation and library. Python code can call optimized native numerical kernels; compiled code can still use an inefficient algorithm. Measure the relevant workload before attributing performance to a language name. Include startup, data conversion, deployment, and debugging costs when they matter, not just a loop’s execution time.

For browser applications, JavaScript has direct access to the browser’s scripting environment; TypeScript is typically translated to JavaScript, and WebAssembly supplies another execution format with its own integration requirements. For an existing service, using its current language may avoid a new runtime and cross-language boundary. These are concrete constraints, not proof that one language is always best for a domain.

Compare plausible alternatives with one representative task. For a file-processing service, build a small path from reading the actual format through validation to output, including one malformed input. This can expose a missing library or awkward deployment dependency before much code is written. Record what the experiment tested and what remains unknown. Familiarity is a real benefit, while learning or adding a language is an investment that needs a reason.

Learning a New Language Efficiently

Map familiar concepts, then test where the mapping fails. Investigate numeric types, strings and encodings, equality and identity, copying and aliasing, evaluation order, exceptions or result values, and resource cleanup. These affect behavior even when a translation compiles.

For example, Python integer floor division gives -7 // 3 == -3, with remainder -7 % 3 == 2. JavaScript’s Math.trunc(-7 / 3) gives -2, and -7 % 3 gives -1. Both pairs reconstruct -7 as quotient times 3 plus remainder, but they choose different quotients. Translating a bucketing calculation by replacing operators can therefore change how negative inputs are classified.

Learn one complete development path: create a project, declare a dependency, run a test, inspect a failure, and produce a runnable artifact. Read a small example from the standard library documentation and a maintained project in the language. Identify why an idiom is useful before copying it. A formatter standardizes layout; a linter checks selected patterns; neither determines the appropriate domain model.

Use exercises that expose semantics, such as modifying a shared collection or propagating an error through two calls. Predict the result, consult the version-appropriate reference, and explain any surprise. AI can suggest translations and examples, but ask for the assumptions that changed and verify them independently. Keep enough unaided practice to know which concepts you can explain without the generated answer.

How Paradigms Shape Thought

Paradigms suggest different decompositions. In an imperative solution, you may update a total while traversing records. In a functional solution, you may filter valid records and pass them through transformations before reduction. A pure function’s result depends on its inputs and produces no externally visible side effects, which can make local reasoning easier. A chain of higher-order calls that still mutates shared state does not acquire that property merely from its syntax.

Object-oriented design can place a state transition with the object responsible for preserving its rules. A reservation object might reject confirmation after cancellation. The useful boundary is the invariant and permitted operations; inheritance is only one possible reuse mechanism. Composition can combine independently changing responsibilities without requiring a base-class hierarchy.

Relational and logic approaches describe relationships and conditions for results. A SQL query states which rows and aggregates are wanted while a query optimizer chooses among execution plans. A Prolog program supplies clauses, but clause order, goal order, and the search strategy can still affect termination. Declarative syntax does not eliminate operational behavior.

Concurrency introduces activities that overlap. Actors organize communication around messages; async functions can suspend while waiting; threads may run simultaneously. An await does not itself make CPU-heavy work parallel, and sending a message does not prove that it was processed exactly once. Choose a model by explaining state ownership, ordering, cancellation, and failure for a particular task.

What Changes With Mastery

You can evaluate a language feature by the behavior it permits and the assumptions it makes, rather than by resemblance to familiar syntax. This makes both translation and code review more precise.

Implement a small record-processing task in a familiar language and one new language. Include empty input, a negative numeric value, malformed data, and an operation that can fail. Compare the semantic differences and the steps needed to build and test the program. The useful result is an account of those differences, not a race to finish a second implementation.

A working language portfolio can remain small. Add a language when it supports a real task or teaches a concept you want to understand, and revisit the choice as the task and team change.

Resources

Books and Texts

Seven Languages in Seven Weeks and Seven More Languages in Seven Weeks provide comparative exercises; their titles should not be read as proficiency schedules. SICP develops procedures, state, and interpreters. Scott and Sebesta offer systematic comparisons of language concepts. For a language you use, consult its own reference alongside a tutorial: the Python expression reference, JavaScript remainder documentation, and TypeScript handbook support the semantic distinctions discussed above. The Rust Book’s discussion of unsafe code explains where additional programmer obligations arise.

Book Role Type
Tate; Tate et al., Seven Languages / Seven More Languages in Seven Weeks Comparative paradigm exercises Entry
Abelson & Sussman, SICP (free) Deep treatment of programs and languages Depth
Scott, Programming Language Pragmatics (4th ed.) Features-to-implementation reference Practice
Sebesta, Concepts of Programming Languages (12th ed.) Comparative language concepts textbook Reference
The Rust Book (free) Ownership, types, and Rust programming Entry

Courses and Lectures

Grossman’s Programming Languages course compares concepts through ML, Racket, and Ruby. Exercism supplies language-specific exercises and feedback opportunities. Use these to practice one unfamiliar semantic idea at a time; completing an exercise is a starting point for inspecting its idioms and boundary cases.

Course Platform Type
Grossman, Programming Languages Coursera / University of Washington Entry
Exercism (free) exercism.org Practice

Practice, Tools, and Projects

Reimplement one small task in two languages and explain the differences. Formatters and linters help with conventions and selected errors, but do not teach the whole design of a language. Compiler Explorer connects source to generated code for chosen compilers and options; its output alone is not a complete performance measurement. Rosetta Code and standard guidance such as Effective Go provide comparison material to read critically.

Resource Platform Type
Multi-paradigm reimplementation exercise Local Practice
Formatters and linters (gofmt, clippy, Black, Prettier) Language-specific Practice
Compiler Explorer (free) godbolt.org Practice
Rosetta Code (free) rosettacode.org Reference
Effective Go (free) go.dev Reference
Advent of Code (free; donations optional) adventofcode.com Practice

Traps

Trap Why it misleads Better response
Counting languages as proficiency A list of syntaxes does not show that behavior can be explained or maintained. Demonstrate a small complete task and explain its semantics.
Translating operators mechanically Division, equality, copying, and evaluation rules can differ. Test semantic boundaries, including negative and missing values.
Choosing by language reputation alone Libraries, deployment, and existing integration may dominate the task. Prototype the critical path under realistic constraints.
Treating static types as runtime validation External data and unchecked boundaries can violate assumed shapes. Validate at boundaries and understand what the type checker establishes.
Assuming a paradigm makes code good Syntax does not establish useful boundaries or correct state handling. Explain the responsibility, invariant, or data transformation it supports.

中文

熟练使用编程语言,意味着理解程序怎样运行,以及怎样构建、检查、测试和维护程序。语法只是其中一部分。还需要知道值如何表示、状态如何共享、错误如何传播,以及运行时和库提供什么能力。

一种有用的学习方式,是先预测小程序的结果,再运行验证。在 Python 中,执行 a = [1] 和 b = a 后,b.append(2) 也会改变通过 a 看到的列表。赋值建立了指向同一列表的另一个引用,并没有复制列表。只认识符号而没有这个模型,就难以解释一整类错误。

语言选择与语言学习相互联系。任务需要特定能力,团队则有已有知识和运行约束。学习多种方法可以扩大选择范围,但熟练程度应通过解决并解释问题来体现,而不是通过语言数量衡量。

前置知识:基础编程(§2.1)。用熟悉语言完成过一个小项目,就能为比较提供基础。遇到类型、求值和抽象问题时,可以借助编程语言理论(§3.4)进一步理解。

这项能力的形状:从一门语言到多门语言

早期编程系统有助于解释语言为何不同。符号汇编器用名字替代许多数值地址,子程序库让程序员通过调用约定复用操作。FORTRAN 使数值公式更容易表达;Lisp 支持符号计算;Simula 把程序与相互作用的模拟实体联系起来。这些发展针对不同任务,而不是沿着“低级语言到更优语言”的单一尺度前进。

结构化程序设计强调能够分部分推理的控制流。函数式、面向对象、逻辑式和关系式方法,又提供了其他组织计算的方式。许多常用语言结合多种方法。有函数不等于纯函数式,有类也不要求把所有问题都建成继承层次。

类型系统、运行时检查、垃圾回收和所有权规则,处理的是不同错误来源。Python 类型注解可以支持外部检查,但不会自动验证运行时传入的数据。TypeScript 检查源码,并通常在生成 JavaScript 时移除类型注解。Rust 的安全抽象依赖所有权与借用规则,也依赖正确实现的不安全边界。这些机制都不能证明业务规则正确,也不能防止所有资源泄漏、死锁或服务故障。

已有经验有助于识别熟悉概念,但新语言可能改变求值、表示和并发方面的假设。学习第五门语言不一定只是学习新语法。习惯急切求值的程序员,在惰性语言中可能需要重新理解工作何时发生;习惯共享可变对象的程序员,则可能需要在所有权约束下重新设计。熟练程度没有统一的天数或年数门槛。

选择、学习和用语言思考

为任务选择语言

从任务及结果运行的环境开始。明确部署平台、必需库、互操作需求、延迟或内存约束,以及谁来维护系统。使用成熟库的数值原型与小型嵌入式控制器,即使都做算术,也可能有很不同的要求。

区分语言、具体实现和库。Python 代码可以调用优化过的原生数值内核;编译后的代码也可能使用低效算法。把性能归因于语言名称之前,应测量相关工作负载。必要时把启动、数据转换、部署和调试成本纳入,而不只比较一个循环的执行时间。

浏览器应用中,JavaScript 可以直接使用浏览器脚本环境;TypeScript 通常翻译成 JavaScript,WebAssembly 则提供另一种执行格式及相应集成要求。对于既有服务,继续使用其语言可能避免引入新运行时和跨语言边界。这些是具体约束,不能证明某种语言在整个领域总是最好。

用一个有代表性的任务比较可行方案。例如,为文件处理服务打通读取实际格式、验证和输出的流程,并包含一个错误输入。这样可以在写出大量代码之前,发现缺少库或部署依赖不便的问题。记录试验检查了什么、还有什么未知。熟悉程度是真实收益,而学习或引入新语言是一项需要理由的投入。

高效学习一门新语言

先映射熟悉的概念,再检查映射在哪些地方失效。考察数值类型、字符串与编码、相等与同一、复制与别名、求值顺序、异常或结果值,以及资源清理方式。即使翻译后的程序能编译,这些方面也会影响行为。

例如,Python 整数向下取整除法得到 -7 // 3 == -3,余数为 -7 % 3 == 2。JavaScript 的 Math.trunc(-7 / 3) 得到 -2,-7 % 3 则得到 -1。两组结果都满足“商乘以 3 加余数等于 -7”,但选取的商不同。因此,只替换运算符来翻译分桶计算,可能改变负数输入的分类方式。

学习一条完整开发路径:创建项目、声明依赖、运行测试、检查失败,并生成可运行制品。阅读标准库文档中的小例子,以及用该语言维护的项目。在照搬惯用法之前,先指出它为什么有用。格式化工具统一布局,静态检查器检查特定模式,但两者都不能决定合适的领域模型。

使用能够暴露语义的练习,例如修改共享集合,或让错误穿过两次调用。先预测结果,再查对应版本的参考文档,并解释意外之处。AI 可以建议翻译和例子,但应追问改变了哪些假设,再独立验证。保留适量不依赖生成答案的练习,以便知道自己真正能解释哪些概念。

范式如何塑造思考

范式提示不同的分解方式。命令式方案可能在遍历记录时更新总额;函数式方案可能先筛选有效记录,再经过若干变换并归约。纯函数的结果依赖输入,且不产生外部可见副作用,这可以简化局部推理。一串高阶函数调用如果仍然修改共享状态,并不会仅因语法形式就获得这一性质。

面向对象设计可以把状态转移放在负责维持规则的对象中。例如,预约对象可以拒绝对已取消预约进行确认。有用的边界是它维护的不变量与允许操作,继承只是可能的复用方式之一。组合可以连接独立变化的职责,而不要求建立基类层次。

关系式与逻辑式方法描述结果应满足的关系和条件。SQL 查询说明需要哪些行与聚合,查询优化器则从执行方案中选择。Prolog 程序提供子句,但子句顺序、目标顺序和搜索策略仍然可能影响终止。声明式语法不会消除运行行为。

并发引入相互重叠的活动。Actor 围绕消息组织通信,异步函数可以在等待时挂起,线程则可能同时执行。await 本身不会使密集计算并行,发送消息也不能证明它恰好被处理一次。应针对具体任务,解释状态归属、顺序、取消和故障,再选择模型。

熟练之后会发生什么变化

你能够根据特性允许的行为与依赖的假设来评价它,而不只看它是否像熟悉的语法。这会使代码翻译和审查更加准确。

用熟悉语言和一门新语言分别实现一个小型记录处理任务。包含空输入、负数、格式错误的数据,以及可能失败的操作。比较语义差异和构建、测试程序所需的步骤。有用的结果是对这些差异的解释,而不是赶快完成第二份实现。

日常使用的语言组合可以很小。遇到实际任务,或希望理解某个概念时,再增加语言;任务与团队变化后,也可以重新评估选择。

资源

书籍与文本

《Seven Languages in Seven Weeks》和《Seven More Languages in Seven Weeks》提供比较练习,不应把书名当成熟练掌握的时间表。《SICP》讨论过程、状态与解释器;Scott 和 Sebesta 的教材系统比较语言概念。学习正在使用的语言时,应把参考文档与教程结合起来:Python 表达式参考、JavaScript 余数文档和 TypeScript 手册支持前述语义区别。Rust Book 对不安全代码的讨论说明哪些地方需要程序员承担额外责任。

书籍 作用 类型
Tate;Tate 等,Seven Languages / Seven More Languages in Seven Weeks 比较不同范式的练习 入门
Abelson & Sussman,SICP(免费) 对程序和语言的深入处理 深入
Scott,Programming Language Pragmatics(第 4 版) 从特性到实现的参考书 实践
Sebesta,Concepts of Programming Languages(第 12 版) 比较性语言概念教材 参考
The Rust Book(免费) 所有权、类型与 Rust 编程 入门

课程与讲座

Grossman 的 Programming Languages 课程通过 ML、Racket 和 Ruby 比较概念。Exercism 提供针对不同语言的练习和获得反馈的机会。可以借此每次练习一个陌生语义概念;完成题目之后,还应检查惯用写法与边界情况。

课程 平台 类型
Grossman,Programming Languages Coursera / 华盛顿大学 入门
Exercism(免费) exercism.org 实践

实践、工具与项目

用两种语言重新实现一个小任务,并解释差异。格式化与静态检查工具有助于掌握约定、发现部分错误,但不能教授一门语言的全部设计。Compiler Explorer 展示所选编译器与参数下源代码和生成代码的关系;仅凭输出还不能完成性能测量。Rosetta Code 和 Effective Go 等指南,可以作为需要批判性阅读的比较材料。

资源 平台 类型
多范式重新实现练习 本地 实践
Formatter 和 linter(gofmt、clippy、Black、Prettier) 各语言专用 实践
Compiler Explorer(免费) godbolt.org 实践
Rosetta Code(免费) rosettacode.org 参考
Effective Go(免费) go.dev 参考
Advent of Code(免费;可选捐赠) adventofcode.com 实践

陷阱

误区 为什么会误导 更好的做法
用语言数量衡量熟练度 会列举语法不代表能解释和维护行为。 完成一个小任务,并解释其语义。
机械替换运算符 除法、相等、复制和求值规则可能不同。 检查负数、缺失值等语义边界。
只凭语言声誉选择 库、部署和已有集成可能更影响任务。 在实际约束下试做关键路径。
把静态类型当成运行时验证 外部数据和未检查边界可能违反类型假设。 在边界验证,并理解类型检查真正保证什么。
认为采用某种范式就能写好代码 语法不能证明边界合理或状态处理正确。 解释它支持的职责、不变量或数据变换。