我尝试用 Gleam 参加 Advent of Code。
I tried Gleam for Advent of Code

原始链接: https://blog.tymscar.com/posts/gleamaoc2025/

## Gleam 与 2023 年 Advent of Code:积极体验 作者连续第七年完成了 Advent of Code 的所有挑战,这次使用了 Gleam 编程语言。尽管活动缩短至仅 12 天(24 个部分),而不是通常的 25 天,但谜题仍然引人入胜,并且常常出乎意料地困难。 Gleam 非常适合 AoC 的风格——解析、转换数据和折叠——这得益于其简洁的语法、有用的编译器和强大的管道运算符。该语言对函数式编程的强调及其强大的列表函数(如 `transpose` 和 `combination_pairs`)尤其有益。像 `fold_until` 和 `echo` 调试工具这样的特性显著简化了开发。 虽然总体上是积极的,但出现了一些摩擦点,包括文件 I/O 和正则表达式需要外部依赖,列表模式匹配的限制以及显式比较。针对 JavaScript 也需要使用大整数,因为其数字存在限制。尽管如此,作者发现 Gleam 的选项/结果处理和备忘录功能特别强大,从而产生了优雅的解决方案,尤其是在基于网格的谜题中。总而言之,这次体验非常令人满意,并激发了对未来项目中使用 Gleam 的兴趣。

## Gleam 与编程语言的未来 (Hacker News 总结) 一个 Hacker News 的讨论围绕 Gleam 编程语言及其在 LLM 时代的前景。作者尝试用 Gleam 参加 Advent of Code,发现其性能令人惊讶,并且语言服务器非常出色。 一个关键主题是 LLM 是否会因其对大型训练数据集的依赖而扼杀新语言的创新。一些人担心缺乏大量代码库的语言将被抛在后面,而另一些人则认为 LLM 将适应设计良好、更简单的语言,例如 Gleam。 讨论还涉及 Gleam 的设计选择,特别是它缺乏接口(尽管它*有*泛型),以及它侧重于显式代码而非简洁性。 许多评论员强调 Gleam 的强类型和与 Erlang VM (BEAM) 的集成是优势。 最终,对话反映了对 LLM 对编程格局的影响以及它们是否会偏爱成熟语言而非更新、可能更具创新性的语言的更广泛担忧。 一种观点是,如果设计良好的语言,即使是小众语言,也能提供独特的优势和清晰、一致的风格,从而使 LLM 能够轻松学习,那么它们就能蓬勃发展。
相关文章

原文

I do Advent of Code every year.

For the last seven years, including this one, I have managed to get all the stars. I do not say that to brag. I say it because it explains why I keep coming back.

My Advent of Code stars

It is one of the few tech traditions I never get bored of, even after doing it for a long time. I like the time pressure. I like the community vibe. I like that every December I can pick one language and go all in.

This year, I picked Gleam.

A much shorter year#

Advent of Code is usually 25 days. This year Eric decided to do 12 days instead.

So instead of 50 parts, it was 24.

That sounds like a relaxed year. It was not, but not in a bad way.

The easier days were harder than the easy days in past years, but they were also really engaging and fun to work through. The hard days were hard, especially the last three, but they were still the good kind of hard. They were problems I actually wanted to wrestle with.

It also changes the pacing in a funny way. In a normal year, by day 10 you have a pretty comfy toolbox. This year it felt like the puzzles were already demanding that toolbox while I was still building it.

That turned out to be a perfect setup for learning a new language.

Why Gleam felt like a good AoC language#

Gleam is easy to like quickly.

The syntax is clean. The compiler is helpful, and the error messages are super duper good. Rust good.

Most importantly, the language strongly nudges you into a style that fits Advent of Code really well. Parse some text. Transform it a few times. Fold. Repeat.

Also, pipes. Pipes everywhere. I love pipes.

One thing I did not expect was how good the editor experience would be. The LSP worked much better than I expected. It basically worked perfectly the whole time. I used the Gleam extension for IntelliJ and it was great.

https://plugins.jetbrains.com/plugin/25254-gleam-language

I also just like FP.

FP is not always easier, but it is often easier. When it clicks, you stop writing instructions and you start describing the solution.

The first Gleam superpower: echo#

The first thing I fell in love with was echo.

It is basically a print statement that does not make you earn it. You can echo any value. You do not have to format anything. You do not have to build a string. You can just drop it into a pipeline and keep going.

This is the kind of thing I mean:

list.range(0, 5)
|> echo
|> list.map(int.to_string)
|> echo

You can quickly inspect values at multiple points without breaking the flow.

I did miss string interpolation, especially early on. echo made up for a lot of that.

It mostly hit when I needed to generate text, not when I needed to inspect values. The day where I generated an LP file for glpsol is the best example. It is not hard code, but it is a lot of string building. Without interpolation it turns into a bit of a mess of <>s.

This is a small excerpt from my LP generator:

"Minimize\n"
<> "  total: "
<> buttons
|> string.join(" + ")
<> "\n\nSubject To\n"

It works. It is just the kind of code where you really feel missing interpolation.

Options everywhere, and why that matters for grid puzzles#

A lot of AoC is grids.

Grids are where you normally either crash into out of bounds bugs, or you litter your code with bounds checks you do not care about.

In my day 4 solution I used a dict as a grid. The key ergonomic part is that dict.get gives you an option-like result, which makes neighbour checking safe by default.

This is the neighbour function from my solution:

fn get_neighbours(grid: Grid(Object), pos: Position) -> List(Object) {
  [
    #(pos.0 - 1, pos.1 - 1),
    #(pos.0 - 1, pos.1),
    #(pos.0 - 1, pos.1 + 1),
    #(pos.0, pos.1 - 1),
    #(pos.0, pos.1 + 1),
    #(pos.0 + 1, pos.1 - 1),
    #(pos.0 + 1, pos.1),
    #(pos.0 + 1, pos.1 + 1),
  ]
  |> list.filter_map(fn(neighbour_pos) { grid |> dict.get(neighbour_pos) })
}

That last line is the whole point.

No bounds checks. No sentinel values. Out of bounds just disappears.

I expected to write parsers and helpers, and I did. What I did not expect was how often Gleam already had the exact list function I needed.

list.transpose saved a whole day#

Day 6 part 1 was basically a transpose problem in disguise.

I read the input, chunked it into rows, transposed it, and suddenly the rest of the puzzle became obvious.

input
|> list.transpose
|> list.map(fn(line) { line |> calculate_instruction })
|> bigi.sum

In a lot of languages you end up writing your own transpose yet again. In Gleam it is already there.

list.combination_pairs is a cheat code#

Another example is list.combination_pairs.

In day 8 I needed all pairs of 3D points. In an imperative language you would probably write nested loops and then question your off by one logic.

In Gleam it is a one liner:

boxes
|> list.combination_pairs

Sometimes FP is not about being clever. It is about having the right function name.

fold_until is my favorite thing I found#

If I had to pick one feature that made me want to keep writing Gleam after AoC, it is fold_until.

Early exit without hacks is fantastic in puzzles.

In day 8 part 2 I kept merging sets until the first set in the list contained all boxes. When that happens, I stop.

The core shape looks like this:

|> list.fold_until(initial, fn(acc, pair) {
  case done_yet {
    True -> Stop(new_acc)
    False -> Continue(new_acc)
  }
})

It is small, explicit, and it reads like intent.

I also used fold_until in day 10 part 1 to find the smallest combination size that works.

Where Gleam fought me a bit#

Even though I enjoyed Gleam a lot, I did hit a few recurring friction points.

None of these are deal breakers. They are just the kind of things you notice when you do 24 parts in a row.

File IO is not in the standard library#

This one surprised me on day 1.

For AoC you read a file every day. In this repo I used simplifile everywhere because you need something. It is fine, I just did not expect basic file IO to be outside the standard library.

Regex is a dependency too#

Day 2 part 2 pushed me into regex and I had to add gleam_regexp.

This is the style I used, building a regex from a substring:

let assert Ok(re) = regexp.from_string("^(" <> substring <> ")+$")
regexp.check(re, val)

Again, totally fine. It just surprised me.

List pattern matching limitations#

You can do [first, ..rest] and you can do [first, second].

But you cannot do [first, ..middle, last].

It is not the end of the world, but it would have made some parsing cleaner.

Comparisons are explicit#

In Gleam a lot of comparisons are not booleans. You get an order value.

This is great for sorting. It is also very explicit. It can be a bit verbose when you just want an <= check.

In day 5 I ended up writing patterns like this:

case cmp_start, cmp_end {
  order.Lt, _ -> False
  _, order.Gt -> False
  _, _ -> True
}

Big integers, and targeting JavaScript#

I used bigi a few times this year.

On the Erlang VM, integers are arbitrary precision, so you usually do not care about overflow. That is one of the nicest things about the BEAM.

If you want your Gleam code to also target JavaScript, you do care. JavaScript has limits, and suddenly using bigi becomes necessary for some puzzles.

I wish that was just part of Int, with a single consistent story across targets.

The most satisfying part: XOR as bitmasks#

Day 10 part 1 was my favorite part of the whole event.

The moment I saw the toggling behavior, it clicked as XOR. Represent the lights as a number. Represent each button as a bitmask. Find the smallest combination of bitmasks that XOR to the target.

This is the fold from my solution:

combination
|> list.fold(0, fn(acc, comb) {
  int.bitwise_exclusive_or(acc, comb)
})

It felt clean, it felt fast, and it felt like the representation did most of the work.

The least satisfying part: shelling out to glpsol#

Day 10 part 2 was the opposite feeling.

I knew brute force was out. It was clearly a system of linear equations.

In previous years I would reach for Z3, but there are no Z3 bindings for Gleam. I tried to stay in Gleam, and I ended up generating an LP file and shelling out to glpsol using shellout.

It worked, and honestly the LP format is beautiful.

Here is the call:

let _ =
  shellout.command(
    "glpsol",
    ["--lp", "temp.lp", "-w", "temp_sol.txt"],
    ".",
    [],
  )

It is a hack, but it is a pragmatic hack, and that is also part of Advent of Code.

Memoization keys that actually model the problem#

Day 11 part 2 is where I was happy I was writing Gleam.

The important detail was that the memo key is not just the node. It is the node plus your state.

In my case the key was:

#(neighbour, new_seen_dac, new_seen_fft)

Once I got the memo threading right, it ran instantly.

The finale, and the troll heuristic#

The last day was the only puzzle I did not fully enjoy.

Not because it was bad. It just felt like it relied on assumptions about the input, and I am one of those people that does not love doing that.

I overthought it for a bit, then I learned it was more of a troll problem. The “do the areas of the pieces, when fully interlocked, fit on the board” heuristic was enough.

In my solution it is literally this:

heuristic_area <= max_area

Sometimes you build a beautiful mental model and then the right answer is a single inequality.

Closing thoughts#

I am very happy I picked Gleam this year.

It has sharp edges, mostly around where the standard library draws the line and a few language constraints that show up in puzzle code. But it also has real strengths.

Pipelines feel good. Options and Results make unsafe problems feel safe. The list toolbox is better than I expected. fold_until is incredible. Once you stop trying to write loops and you let it be functional, the solutions start to feel clearer.

I cannot wait to try Gleam in a real project. I have been thinking about using it to write a webserver, and I am genuinely excited to give it a go.

And of course, I cannot wait for next year’s Advent of Code.

If you want to look at the source for all 12 days, it is here:

https://github.com/tymscar/Advent-Of-Code/tree/master/2025/gleam/aoc/src

联系我们 contact @ memedata.com