
文档教程知识库【免费下载链接】til:memo: Today I Learned项目地址https://gitcode.com/gh_mirrors/ti/til点击查看免费下载本篇技术指南围绕 TIL 仓库中的 elixir/updating-values-in-a-map.md 笔记展开深入讲解 Elixir 标准库Map模块的get_and_update/3函数——它能在一次调用中同时读取旧值并写回新值并通过回调函数统一处理键不存在的场景。读完本文你将掌握该函数的调用契约、计分器这类累加型更新的完整写法以及它与Map.update/4、Map.put/3等相邻 API 的取舍并能在 IEx 中直接复现验证。为什么需要读取 更新一步完成的 API任何语言中操作映射Map时都会频繁遇到两种需求更新已有的键值对例如把%{jake: 1}中的1改成2处理键不存在的情况通常需要为缺失的键关联一个默认值例如 jake 第一次得分时Map 里还没有:jake这个键此时应当按0 1 1处理。Elixir 的 Map 是不可变数据结构任何更新都会返回一个全新的 Map。如果只使用Map.fetch/2Map.put/3的组合你需要两步操作才能完成读旧值、写新值还要自己判断:error分支来提供默认值代码会显得啰嗦。Map模块为此提供了Map.get_and_update/3把取旧值和写新值合并成一次原子化的调用并把缺失键的默认值处理一并交给调用方自定义。Map.get_and_update/3 的调用契约函数的签名是Map.get_and_update(map, key, fun)你需要提供三样东西map待操作的 Mapkey要读取并更新的键fun一个用于转换更新现有值的函数。调用后的返回结果是一个二元组{旧值, 新Map}其中旧值是本次更新前该键对应的值如果键原本不存在旧值为nil新Map是更新后的完整 Map因为不可变性原 Map 保持不变。回调函数fun的契约是它接收当前值键不存在时收到nil并且必须返回一个包含原值和更新后的值的二元组{原值, 新值}。二元组的第一项会被原样返回给调用方即上面提到的旧值第二项则被写入新 Map 中。正因回调在键缺失时收到的是nil我们才能利用这个事实在回调内部构造默认值——这正是原笔记中(x || 0) 1技巧的由来。计分示例为缺失键提供默认值原文档给出了一个非常直观的计分score counting示例可以直接在iex中逐行粘贴运行。这里完整复现 scores %{} %{} # jake scores a point {_, scores} Map.get_and_update(scores, :jake, fn(x) - {x, (x || 0) 1} end) {nil, %{jake: 1}} # chris scores a point {_, scores} Map.get_and_update(scores, :chris, fn(x) - {x, (x || 0) 1} end) {nil, %{chris: 1, jake: 1}} # jake scores another point {_, scores} Map.get_and_update(scores, :jake, fn(x) - {x, (x || 0) 1} end) {1, %{chris: 1, jake: 2}} # final scores scores %{chris: 1, jake: 2}逐行解读其中的关键点第一次给 jake 计分Map 中还没有:jake回调收到nil(nil || 0) 1计算得到1。返回的元组是{nil, 1}其中nil是旧值键原本不存在1被写入新 Map因此返回值为{nil, %{jake: 1}}。第一次给 chris 计分同理返回{nil, %{chris: 1, jake: 1}}注意 Elixir 会按键排序输出所以显示为chris在前。jake 再得一分这次键已存在回调收到旧值1(1 || 0) 1得到2返回{1, %{chris: 1, jake: 2}}——元组第一项1正是被更新前的旧值。这里(x || 0) 1是为缺失键提供默认值的简洁写法x为nil时取0作为基数再执行加一x已有值时直接加一。该技巧与原文档的语义完全一致一次get_and_update/3调用同时完成了取旧值、按旧值计算新值、处理缺失键默认值三件事。丢弃旧值与模式匹配的配合示例中每一行都使用了{_, scores} Map.get_and_update(...)这种写法值得单独说明用_丢弃二元组中我们不关心的旧值本例中我们只关心累加结果用scores重新绑定更新后的新 Map让下一次计分可以在最新状态上继续累加。这正是函数式风格中常见的重绑定rebinding模式——由于 Map 不可变变量名scores依次指向每次更新产生的新 Map最终scores就是%{chris: 1, jake: 2}的最终比分。如果你需要同时保留旧值做其他逻辑例如日志输出、差值计算则可以直接使用{old_value, new_map} Map.get_and_update(...)拿到两个值。与相邻 Map API 的横向对比get_and_update/3并非唯一的选择Elixir 标准库还提供了一组功能相近的 Map 更新函数它们的差异主要体现在键缺失时的行为和返回值上函数键缺失时的行为返回值典型场景Map.put/3直接写入新值新 Map无条件赋值不需要旧值Map.put_new/3仅在键缺失时写入新 Map只设默认值、绝不覆盖已有值Map.update/4使用传入的default作为新值新 Map不关心旧值、但需要缺失默认值Map.update!/3抛出KeyError新 Map键必须已存在强制更新Map.get_and_update/3回调收到nil自行决定默认值{旧值, 新Map}同时需要旧值和新值Map.pop/3直接删除该键{旧值, 新Map}取出值并删除键其中与get_and_update/3最相似的是Map.update/4Map.update(map, :jake, 0, fn x - x 1 end)它同样为缺失键提供默认值0但只返回更新后的新 Map不返回旧值。相比之下若你的逻辑是只要新状态例如计数器累加Map.update/4更简洁若你需要更新前的值参与后续处理例如判断从 0 变 1 的那次得分是否是新玩家首分get_and_update/3能一步到位省去额外的Map.fetch/2调用。此外较新版本的 Elixir 标准库中get_and_update/3的回调还可以返回原子:pop此时该键会从 Map 中被移除并返回被删除的旧值——这一行为与Access协议中get_and_update/3回调的约定保持一致适合读取后即删除的场景。进阶嵌套 Map 的更新get_and_update_in如果数据结构是嵌套的例如%{team: %{jake: 1}}标准库还提供了Map.get_and_update_in/3与Kernel.get_and_update_in/3可以在一条路径上做同样的读旧写新scores %{team: %{jake: 1}} # Map 模块版本传入键路径列表 {old, new_scores} Map.get_and_update_in(scores, [:team, :jake], fn current - {current, (current || 0) 1} end) # old 1 # new_scores %{team: %{jake: 2}} # Kernel 宏版本使用方括号路径语法 {old, new_scores} get_and_update_in(scores[:team][:jake], fn current - {current, (current || 0) 1} end)可以看到(current || 0) 1的默认值技巧在嵌套场景下同样成立只要路径中某个键缺失例如:team下还没有:jake回调就会收到nil从而自动按0起步。嵌套更新依赖 Elixir 的Access协议路径既可以是键列表也可以配合Access.key/2、Access.all/0等实现对列表、动态键的定位。小结与关联阅读总结一下Map.get_and_update/3的核心价值可以归纳为三点一次调用完成读改写返回{旧值, 新Map}避免fetch put两步操作与中间的:error分支判断统一的缺失键处理键不存在时回调收到nil配合(x || 0) 1这类表达式即可注入默认值与模式匹配无缝协作{_, new_map} Map.get_and_update(...)的写法在累加、计分、递归状态传递中非常顺手。本文对应仓库中的原始笔记为 elixir/updating-values-in-a-map.md。如果还想继续深入 Elixir 的 Map 与集合操作可以顺带阅读同仓库下的这些 TILelixir/match-on-a-map-in-a-with-construct.md在with构造中直接对 Map 做模式匹配配合Map.fetch/2优雅提取键值elixir/all-values-for-a-key-in-a-keyword-list.md对比 Keyword List 中同名键的取值行为理解 Map 与 Keyword 的差异elixir/append-to-a-keyword-list.md以变量动态构造键值对时需要注意的元组包装细节。赞分享文档教程知识库【免费下载链接】til:memo: Today I Learned项目地址https://gitcode.com/gh_mirrors/ti/til点击查看免费下载相关推荐Hugo 模板方法 time.IsZero判断零值时间并优雅处理缺失日期Hugo 模板方法 time.IsZero判断零值时间并优雅处理缺失日期 导读 time.IsZero 是 Hugo 模板系统中挂在 time.Time 值上开发工具前端CLIInngest社区与支持如何参与贡献和获取帮助的完整指南Inngest社区与支持如何参与贡献和获取帮助的完整指南 Inngest是一个为TypeScript和JavaScript开发者打造的零基础设施工作流平台让KVOController大数据处理海量键值更新的优化KVOController大数据处理海量键值更新的优化 在iOS和macOS开发中键值观察Key Value ObservingKVO是一种强大的机制开发工具上一篇如何解密 RPG Maker MV 资源文件三条路线快速解锁 rpgmvp 图片与 rpgmvm 音频下一篇DXVK3 步把 Windows 游戏搬上 Linux 的 DirectX 转 Vulkan 方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考