编程 Brunost:一个强制使用挪威语Nynorsk编写代码的编程语言——深度解析与实战

2026-04-18 13:16:43 +0800 CST views 7

Brunost:一个强制使用挪威语 Nynorsk 编写代码的编程语言——深度解析与实战

作者:程序员茄子

发布时间:2026年4月18日


一、引言:语言不只是工具,也是文化

编程语言的世界从来不缺"Hello World"新选手。但大多数新语言无非是换个语法糖,或者在性能与开发体验之间做不同的取舍。你见过用挪威语 Nynorsk 书写全部代码的编程语言吗?

今天要聊的这个语言叫 Brunost——它由挪威开发者 John Mikael Lindbakk(GitHub @atomfinger)创作,以挪威传统文化中极负盛名的棕色山羊奶酪(Brunost)命名。更重要的是,这是一个强制要求开发者使用 Nynorsk 书写变量、函数名和所有代码标识符的函数式编程语言。

如果你是挪威人,你可能会觉得这是一个玩笑。但如果你是程序员,你可能会意识到——这件事背后的工程挑战和文化意义,远比它看起来要有趣得多。


二、文化背景:Nynorsk 是什么?

在深入技术细节之前,我们需要理解 Nynorsk 是什么。

挪威有三种官方语言变体:Bokmål(书面挪威语)Nynorsk(新挪威语)Sami(萨米语)。其中:

  • Bokmål 是大多数挪威人日常使用的书面语言,受丹麦语影响很深
  • Nynorsk 则是一种基于挪威方言的规范化书面语言,历史上被视为"挪威语的更'纯粹'形式"
  • Sami 是原住民语言

Nynorsk 的一个有趣特性是:它基本上是一种纯书面语言。没有人说"纯正的 Nynorsk",因为即使在 Nynorsk 区域,人们说的也是当地方言。这意味着 Nynorsk 天然适合作为一个只有规范没有"母语者"的形式化系统——这和编程语言何其相似。

Brunost 的创造者正是抓住了这个类比:一个只有规范没有母语者的语言,正好可以用来写一个编译器。


三、Brunost 语言核心特性

3.1 强制 Nynorsk:字典即语法检查器

Brunost 的核心创新在于:它内置了一个 Nynorsk 词典,运行时强制检查所有标识符是否来自 Nynorsk 词汇表

这意味着:

  • 变量名 fart ✅(Nynorsk:速度)
  • 变量名 speed ❌ 编译报错:Feil: Namnet er ikkje gyldig nynorsk
  • 函数名 kalkulerFart
  • 函数名 calculateSpeed

这不仅仅是噱头——它意味着 Brunost 实际上在运行时做了一件很有意思的事:它把语言词汇表从 ASCII token 扩展到了一个完整的自然语言子集

3.2 基础语法结构

变量声明(可变 vs 不可变):

-- 不可变变量(Nynorsk: fast)
fast fart er 80
fart er 50  -- ❌ 编译错误:Kan ikkje endra ein uforanderleg variabel

-- 可变变量(Nynorsk: endreleg)
endreleg fart er 80
fart er 50  -- ✅ 正常运行

fast 对应英语的 const(不可变),endreleg 对应 var/let(可变)。关键词全部是 Nynorsk,这在代码可读性上造成了一种奇特的效果——懂 Nynorsk 的挪威人看到代码会感觉像在读母语说明书。

条件判断:

bruk terminal
fast fartsgrense er 80
fast minFart er 90

viss (minFart erStørreEnn fartsgrense) gjer {
    terminal.skriv("For høy fart!")
} ellers viss (minFart erSameSom fartsgrense) gjer {
    terminal.skriv("Farten din er på grensa!")
} ellers {
    terminal.skriv("Farten din er innafor.")
}

关键 Nynorsk 词汇:

  • viss = 如果(if)
  • ellers = 否则(else)
  • gjer = 做/执行(do)
  • erStørreEnn = 大于
  • erSameSom = 等于
  • erMindreEnn = 小于
  • erSameEllerMindreEnn = 小于等于

函数定义:

bruk terminal

gjer kalkulerFart(distanse, timer) {
    gjevTilbake distanse / timer
}

fast distanse er 50
fast timer er 2
terminal.skriv("Din fart er: " + kalkulerFart(distanse, timer) + "km/t")
  • gjer = 定义(do/define)
  • gjevTilbake = 返回(give back)

这里 Brunost 的有趣之处在于:顺序敏感。你必须在使用函数之前声明它,否则编译器会拒绝。这种设计反映了创造者的一种"强观点"——开发者应该知道代码的执行顺序,而不是依赖编译器的提升(hoisting)。

3.3 循环结构

-- forEach 循环
bruk terminal
fast fylke er ["Vestland", "Rogaland", "Troms", "Finnmark"]
forKvart namn i fylke {
    terminal.skriv("Hei frå " + namn + "!")
}

-- while 循环
bruk terminal
endreleg teljar er 1
medan (teljar < 4) gjer {
    terminal.skriv(teljar)
    teljar er teljar + 1
}
  • forKvart ... i = for each ... in
  • medan = 当/当...时(while)

3.4 标准库模块

bruk terminal  -- 输入输出
bruk matte     -- 数学运算
bruk streng    -- 字符串处理
bruk liste     -- 列表操作
bruk prosess   -- 进程管理

所有模块名都是 Nynorsk 词汇。开发者也可以创建自己的模块:

bruk logikk              -- 导入同级目录的 logikk 模块
bruk kode.logikk         -- 导入子目录 kode 下的 logikk 模块

3.5 异常处理

bruk terminal

gjer del(første, andre) {
    viss (andre er 0) gjer {
        kast "Kan ikke dele på null"
    }
    gjevTilbake første / andre
}

prøv {
    del(10, 0)
} fang(feil) {
    terminal.skriv("Noe feilet: " + feil)
}
  • prøv = 尝试(try)
  • fang = 捕获(catch)
  • kast = 抛出(throw)

创造者对此有一段经典评论:"有些人说 errors-as-returns 比异常更好。为什么?就别产生错误就好了。简单。"

3.6 WebAssembly 支持

Brunost 解释器可以编译为 WebAssembly,这意味着你可以在浏览器中直接运行 Brunost 代码。创造者做了一个在线 Playground,可以直接体验:

bruk terminal
terminal.skriv("Hei, verda!")

在线体验地址:lindbakk.com(浏览器中直接运行)

创造者的原话:"Wasm 这部分内容主要是为了 SEO 而加的,希望它有用。"


四、技术架构深度解析

4.1 为什么用 Zig 编写解释器?

Brunost 解释器用 Zig 编写。选择 Zig 的理由是:

  1. 性能接近 C:Zig 没有隐藏的内存分配,性能极高
  2. 简单直接的内存管理:没有垃圾回收器,对解释器这种需要精细控制内存的场景非常友好
  3. 与 C 的互操作性:未来如果需要 FFI(外部函数接口),Zig 可以无缝集成 C 库
  4. 编译产出极小:解释器本身可以被 Wasm 编译,产出极小的二进制

以下是 Zig 解释器的核心设计思路(伪代码):

// 伪代码:Brunost 解释器核心
const std = @import("std");

pub fn eval(ast: AstNode, ctx: *Context) !Value {
    switch (ast.kind) {
        .fast_decl => {
            const val = try eval(ast.value, ctx);
            try ctx.defineConst(ast.name, val);
            return Value{ .void = {} };
        },
        .endreleg_decl => {
            const val = try eval(ast.value, ctx);
            try ctx.defineVar(ast.name, val);
            return Value{ .void = {} };
        },
        .function_def => {
            const func = Function{
                .params = ast.params,
                .body = ast.body,
                .closure = ctx.capture(),
            };
            try ctx.define(ast.name, func);
            return Value{ .void = {} };
        },
        // ...
    }
}

Zig 的 try/catch 错误处理机制和 Brunost 的 prøv/fang 异常处理在设计哲学上有一种奇妙的呼应——两者都强调显式错误处理

4.2 Nynorsk 词典的设计

Nynorsk 词典是 Brunost 系统中最关键也是最复杂的组件之一。它不仅包含单词列表,还需要处理:

  1. 词形变化:Nynorsk 有丰富的词形变化规则(变格、变位)
  2. 复合词:挪威语允许通过组合单词形成复合词
  3. 允许的借词:某些英语词汇在 Nynorsk 中也是合法的

词典的数据结构设计大致如下:

// 伪代码:词典结构
const Dict = struct {
    // 基础词汇集合(已验证的 Nynorsk 词汇)
    base_words: std.StringHashMap(WordInfo),
    
    // 词缀规则(Nynorsk 词形变化规则)
    affix_rules: []AffixRule,
    
    // 允许的借词(来自其他语言的合法词汇)
    loan_words: std.StringHashMap(LoanWordInfo),
};

const WordInfo = struct {
    root: []const u8,      // 词根
    category: WordCategory, // 词性(名词/动词/形容词等)
    forms: []const []const u8, // 各种变位形式
};

创造者提到的一个有趣限制:BMI(身体质量指数)这个例子在开发过程中被迫移除了,因为 "BMI" 在 Nynorsk 词典中不是合法词汇

4.3 模块系统和文件系统布局

Brunost 的模块系统遵循一个清晰的路径映射规则:

prosjekt/
├── hovud.brunost        -- 主入口
├── logikk.brunost       -- 同级模块
└── kode/
    └── logikk.brunost   -- 子模块
bruk logikk        -- 导入同级
bruk kode.logikk   -- 导入子模块

这种设计非常简洁,没有 npm/pip 那样的复杂包管理逻辑,但也意味着没有版本控制、没有依赖解析。对于一个"实验性"的语言来说,这完全是合理的工程决策。


五、实战代码:斐波那契数列与 Conway 生命游戏

5.1 斐波那契数列

bruk terminal

gjer rekke(grense) {
    viss (grense < 2) gjer {
        gjevTilbake grense
    }
    gjevTilbake rekke(grense - 1) + rekke(grense - 2)
}

endreleg teljar er 1
medan (teljar erSameEllerMindreEnn 15) gjer {
    terminal.skriv(rekke(teljar))
    teljar er teljar + 1
}

输出:

1
1
2
3
5
8
13
21
34
55
89
144
233
377
610

5.2 FizzBuzz

bruk terminal
bruk matte

endreleg teljar er 1
medan (teljar erSameEllerMindreEnn 15) gjer {
    viss (matte.modulus(teljar, 15) erSameSom 0) gjer {
        terminal.skriv("FizzBuzz")
    } ellers viss (matte.modulus(teljar, 3) erSameSom 0) gjer {
        terminal.skriv("Fizz")
    } ellers viss (matte.modulus(teljar, 5) erSameSom 0) gjer {
        terminal.skriv("Buzz")
    } ellers {
        terminal.skriv(teljar)
    }
    teljar er teljar + 1
}

5.3 Conway 生命游戏

bruk liste
bruk terminal
bruk matte
bruk prosess
bruk streng

fast breidde er 60
fast høgd er 30

-- 创建随机初始状态
gjer lagBrett() {
    endreleg brett er []
    endreleg rad er 0
    medan (rad erMindreEnn høgd) gjer {
        endreleg rekkje er []
        endreleg kolonne er 0
        medan (kolonne erMindreEnn breidde) gjer {
            -- 随机填充 0 或 1
            endreleg tilfeldig er matte.floor(matte.tilfeldig() * 2)
            rekkje.push(tilfeldig)
            kolonne er kolonne + 1
        }
        brett.push(rekkje)
        rad er rad + 1
    }
    gjevTilbake brett
}

注:生命游戏在在线版本中因为 Wasm 的 I/O 限制,运行效果不太理想,这是创造者已知的限制。


六、性能分析:Zig 解释器 + Wasm 的优势

6.1 解释器性能

由于 Brunost 是解释型语言(不是编译型),性能主要由解释器的执行效率决定。Zig 的以下特性对性能至关重要:

特性优势
无隐藏内存分配解释器核心循环性能稳定,无 GC 停顿
Comptime(编译时计算)词法分析器在编译期完成部分解析
SIMD 支持列表/数组操作可受益于 Zig 的 SIMD intrinsics
跨平台 Wasm 编译一次编译,到处运行

6.2 Wasm 部署架构

Brunost 源代码 (.brunost)
       ↓
   Zig 解释器(编译目标)
       ↓
  Wasm 二进制 (~200-500KB)
       ↓
  浏览器 / Node.js / Deno

Zig 对 Wasm 的支持已经相当成熟,可以直接编译为 wasm32-wasi 目标。对于一个需要跨平台部署的解释器来说,这是一个非常优雅的方案。

6.3 已知性能瓶颈

  1. 字典查询:每次标识符解析都需要在 Nynorsk 词典中查找,这是解释器中额外的 O(n) 开销
  2. 词形变化:如果词典包含变位形式的完整列表,存储开销较大
  3. Wasm I/O:WebAssembly 的沙箱限制使得标准 I/O 操作需要特殊处理

七、Brunost 的工程哲学:强观点与刻意限制

Brunost 创造者John Mikael Lindbakk 在项目中体现了一系列强观点

7.1 "代码是写给人看的,顺便给机器执行"

大多数编程语言的设计哲学是"降低机器的理解成本"(性能优先)或者"降低人的理解成本"(可读性优先)。Brunost 则更进一步:它要求你用一种特定自然语言来表达代码,这使得代码的"文化语义"变得非常重要。

fast fart er 80 不只是声明了一个不可变的数值变量——它还传达了挪威文化中"速度"的概念(fart)。

7.2 刻意限制是设计的核心

创造者明确表示:

  • 不会有完整的生态系统
  • 不会有复杂的包管理器
  • 这是一个"有趣的小项目"

这种刻意限制反而让 Brunost 更像一件完整的艺术品,而非一个半途而废的项目。它不是要取代 Go/Rust/TypeScript,它只是要回答一个问题:如果一门语言强制使用一种非英语的自然语言来书写,会发生什么?

7.3 US 键盘布局的"意外代价"

创造者自己在使用 US 键盘布局时,发现输入 Nynorsk 字符(æ/ø/å)极其困难。这揭示了一个有意思的工程现实:语言的语法和它的输入方式是不可分割的。如果你打算推广 Brunost,你最好有一块挪威键盘,或者花时间配置输入法。


八、Brunost 的局限性:为什么它不适合生产

8.1 词典的局限性

  • Nynorsk 词典不可能覆盖所有现代技术词汇
  • 技术术语(如 HTTP、API、JSON)需要额外处理
  • 不同地区的 Nynorsk 方言差异可能引发兼容性问题

8.2 缺乏工具链

  • 没有语法高亮插件(目前)
  • 没有 Language Server Protocol(LSP)实现
  • 没有调试器
  • 没有单元测试框架

创造者开放了 GitHub 仓库,欢迎社区贡献编辑器插件、LSP 等。但这些都需要时间和精力投入。

8.3 性能限制

解释型语言的固有局限,加上 Nynorsk 词典查询的开销,使得 Brunost 不适合计算密集型场景。


九、社区参与:如果你想贡献

创造者明确表示欢迎社区 PR,并给出了几个"低风险"贡献方向:

方向难度价值
编辑器语法高亮插件⭐⭐VS Code / Vim / Neovim / Helix 支持
Language Server Protocol⭐⭐⭐⭐智能提示、跳转、错误诊断
标准库扩展⭐⭐补充更多实用模块
文档完善README / 教程 / 示例
Wasm I/O 优化⭐⭐⭐改善在线版本体验

GitHub 仓库地址:github.com/atomfinger/brunost


十、总结:Brunost 教会我们什么?

Brunost 不是一个"应该被用于生产"的语言。它的创造者自己也说了:"你可以用它……但你不应该。"

但它给我们带来了几个重要的思考:

1. 编程语言的"元设计"
大多数编程语言将词汇表限制在 ASCII/Unicode token,而 Brunost 将其扩展到了一个完整的自然语言子集。这是一种极端的"可读性实验"。

2. 文化与工程的交汇
编程语言不只是技术工件,它们也承载着文化偏见。英语作为编程语言的主导语言是一个历史偶然,而不是必然选择。Brunost 用一种幽默但严肃的方式挑战了这个假设。

3. Zig 作为语言实现工具的价值
选择 Zig 编写解释器展示了 Zig 在工具链开发领域的成熟度——高性能、跨平台、Wasm 支持、简洁的内存管理,使得 Zig 成为一个理想的语言实现平台。

4. "刻意小" 的工程美学
在一个追求"功能完备"的行业里,Brunost 选择了一个有边界的项目,然后把这个边界做到极致。这是一种值得尊敬的工程态度——不是所有东西都需要成为平台


参考链接

  • Brunost 官方介绍:https://lindbakk.com/blog/introducing-brunost
  • GitHub 仓库:https://github.com/atomfinger/brunost
  • 在线体验:https://lindbakk.com(浏览器 Wasm 版本)

Tags: Brunost | Nynorsk | 编程语言 | Zig | 函数式编程 | WebAssembly | Gleam | 自然语言编程

Keywords: Brunost | Nynorsk programming language | Zig interpreter | Gleam | WebAssembly | functional programming | natural language programming | 挪威语编程 | 编程语言设计

推荐文章

Vue3中的Store模式有哪些改进?
2024-11-18 11:47:53 +0800 CST
Golang 中你应该知道的 noCopy 策略
2024-11-19 05:40:53 +0800 CST
Vue3中的JSX有什么不同?
2024-11-18 16:18:49 +0800 CST
解决python “No module named pip”
2024-11-18 11:49:18 +0800 CST
五个有趣且实用的Python实例
2024-11-19 07:32:35 +0800 CST
Go 如何做好缓存
2024-11-18 13:33:37 +0800 CST
Nginx 反向代理 Redis 服务
2024-11-19 09:41:21 +0800 CST
2025年,小程序开发到底多少钱?
2025-01-20 10:59:05 +0800 CST
Nginx 跨域处理配置
2024-11-18 16:51:51 +0800 CST
程序员茄子在线接单