卫兵!卫兵
摘要
探讨 Elixir 中一个令人惊讶的行为:`is_map_key/2` 守卫可能导致守卫失败而非返回 false,从而破坏了布尔运算符的可交换性。
<p><a href="https://lobste.rs/s/b2emi7/guards_guards">评论</a></p>
查看缓存全文
缓存时间: 2026/06/27 17:55
# Hauleth 的博客 - 守卫!守卫!
来源:https://hauleth.dev/post/guards-guards/
我们先从一个简单的小测开始。
---
给定如下模块定义:
```elixir
defmodule Foo do
def a(x) when is_integer(x) or is_map_key(x, :foo), do: true
def a(x), do: false
def b(x) when is_map_key(x, :foo) or is_integer(x), do: true
def b(x), do: false
end
```
试着回答以下问题。
**问:** `Foo.a(%{foo: 21})` 的结果是什么?
`true`
`false`
错误
正确
这个问题很直白。
我们检查守卫,它有一个条件 `is_integer(x) or is_map_key(x, :foo)`。第一个返回 `false`,第二个返回 `true`,布尔运算的或运算结果为 `true`,因此匹配第一个子句。
**问:** `Foo.a(37)` 的结果是什么?
`true`
`false`
错误
正确
这个问题同样直白。
我们检查守卫,它有一个条件 `is_integer(x) or is_map_key(x, :foo)`。第一个返回 `true`,第二个根本不会触发,因为 `or` 运算符是短路的。
**问:** `Foo.b(%{foo: 21})` 的结果是什么?
`true`
`false`
错误
正确
同样,和前几个问题类似。
我们检查守卫,它有一个条件 `is_map_key(x, :foo) or is_integer(x)`。第一个返回 `true`,剩余部分被短路。
**问:** `Foo.b(37)` 的结果是什么?
`true`
`false`
错误
正确
哦,出现了变化……
我们再次检查守卫,一个条件 `is_map_key(x, :foo) or is_integer(x)`。我们首先遇到第一个子句 `is_map_key(x, :foo)`,而这个子句 **没有** 返回 `false`,相反它失败了。守卫函数中的任何一个失败都不会转换为 `false`,而是导致整个守卫表达式失败。这意味着 `is_integer(x)` 部分将 **永远不会** 被调用。
---
这种行为常常让许多 Elixir 开发者感到惊讶,因为它似乎破坏了布尔运算符的交换律。然而,说实话,由于短路求值的缘故,它们从来就不是可交换的。
似乎在撰写本文时(Elixir 1.20.1,OTP 29),Elixir 并未对此问题发出警告。
∎
相似文章
整数无故发生神秘变化,而本应无代码生成影响
一位开发者发现,交换两个等效宏竟导致无关函数中出现意外的整数变化,这篇博客文章深入探究了这一谜团,并对某个大语言模型(LLM)关于控制流保护的解释提出了质疑。
OCaml 中的受保护方法
本文解释了如何使用类型相等性证明在 OCaml 中编码受保护方法,从而仅对特定方法而非整个类施加接收者约束。
我自己的 PreToolUse 护栏阻止了我的代理编写 Markdown 文件。这个 bug 具有普遍性。
一位开发者描述了编码代理 PreToolUse 护栏中的一个常见 bug:护栏将所有工具参数扁平化为单个字符串并统一分类,导致代理仅仅提及禁止话题时就产生误报。修复方法是同时考虑字符串和工具的实际效果。
一个不可见字符让我们的护栏失效了三周,而症状看起来完全像是模型不稳定
一位开发者讲述了一个持续三周的生产环境 bug:一个包含字面退格字符的正则表达式静默地禁用了语言检测护栏,让 LLM 看起来不稳定。这篇文章强调了需要对确定性护栏进行埋点,以将其与模型的非确定性区分开来。
LLM护栏的思维模型,终于让我明白了。
这篇文章概述了一种思维模型,将LLM护栏作为独立层次实施入站和出站检查,强调需要超越系统提示的强制执行,以及与延迟的权衡。