fpinscala 练习 12.12 精解:用 Applicative 的 `sequenceMap` 反转 Map 的效果层

发布时间:2026/10/12 3:25:34
fpinscala 练习 12.12 精解:用 Applicative 的 `sequenceMap` 反转 Map 的效果层 示例工程【免费下载链接】fpinscalaCode, exercises, answers, and hints to go along with the book Functional Programming in Scala项目地址https://gitcode.com/gh_mirrors/fp/fpinscala点击查看免费下载导读本文聚焦《Functional Programming in Scala》第 12 章applicative中练习 12.12 的官方提示与参考答案深入讲解如何利用 Applicative 的map2/unit原语将一个键值均为F效果的Map[K, F[V]]整体「翻转」为F[Map[K, V]]——即sequenceMap的实现。文章会先带你读懂提示中把Map当作键值对列表的关键思路再结合本仓库 Applicative.scala 练习版 与 答案版 的完整源码逐行拆解foldLeft折叠过程与类型推演并顺带对比Traverse中mapTraverse的平行实现帮助你彻底掌握 Applicative 在 Map 这类可折叠容器上的序列化能力。一、练习上下文这道题在讲什么练习 12.12 位于第 12 章 applicative 的练习序列中紧接练习 12.10product/compose与 12.11monad 组合与 distributive law 的讨论。它的任务是在Applicative[F]特质上新增一个方法def sequenceMapK, V: F[Map[K, V]]方法的语义非常直观给定一个普通键、效果值的Map把它整体翻转为一个效果、普通 Map——所有键保留值从F[V]还原为V整个过程被包进同一个F上下文。它在本质上就是sequence把List[F[A]]变成F[List[A]]在Map容器上的推广Map[K, F[V]]与List[(K, F[V])]存在天然的同构因此只需要把每一对(k, fv)依次用map2累积进一个F[Map[K, V]]即可。官方提示只有一句话却点出了实现的核心洞察The standard library lets you treat aMapas essentially a list of pairs.标准库允许你把Map本质上当作一个键值对列表来对待。这意味着既然 Scala 标准库的Map本身就是Iterable[(K, V)]天然支持foldLeft/foldRight等列表式折叠操作我们完全可以直接在Map上折叠而不必先.toList转换。下面看答案是如何顺着这条思路落地的。二、参考答案逐行拆解foldLeftmap2累积12.answer.md 给出的参考实现如下def sequenceMapK, V: F[Map[K, V]] ofv.foldLeft(unit(Map.empty[K, V])): case (acc, (k, fv)) acc.map2(fv)((m, v) m (k - v))逐行解读折叠起点种子值unit(Map.empty[K, V])把空 Map 提升进F上下文作为累积器的初始值。它类型为F[Map[K, V]]与最终返回类型一致。折叠方向选用foldLeft而非foldRight。因为Map的键值对遍历顺序在语义上无关紧要m (k - v)对单个键是覆盖式插入左折叠足够且实现最简洁。折叠函数对每一对(k, fv)调用acc.map2(fv)(...)。acc是当前累积的F[Map[K, V]]fv是当前键对应的F[V]map2把它们各自的效果层剥开、以纯函数(m, v) m (k - v)组合把新键值对插入已累积的 Map。效果语义最终得到F[Map[K, V]]其中F的所有副作用Option 的缺失、Either 的错误、Validated 的累积校验、List 的多重结果、State 的状态传递等都会按各自 Applicative 实例的map2规则被正确执行与传播。2.1 类型推演验证假设F Optionofv: Map[String, Option[Int]]初始acc0 unit(Map.empty) Some(Map())第一轮(k1, fv1)Some(Map()).map2(Some(1))((m, v) m (k1 - v))Some(Map(k1 - 1))第二轮(k2, fv2)Some(Map(k1 - 1)).map2(None)(...)None这正是Option语义任何一个键对应的值缺失整个序列化结果即为None。若换成Validated则会按 答案版 Validated 的 map2 累积所有Invalid错误实现一次收集全部校验失败的效果。三、支撑原语unit与map2从何而来sequenceMap只依赖两个 Applicative 原语它们定义在 练习版 Applicative 特质def unitA: F[A] extension A def map2B, C(f: (A, B) C): F[C] ???unit把纯值提升进效果上下文map2把两个效果值用二元纯函数组合。这里的关键点在于sequenceMap是定义在Applicative特质上的派生方法它不需要知道F具体是什么只要F提供unit和map2任何 ApplicativeOption、Either、Validated、List、State、自定义类型等都能自动获得对 Map 的序列化能力。这也是为什么答案中的map2走的是练习 12.2 中由apply与unit推导出的默认实现见 02.answer.md——sequenceMap站在map2之上map2又站在apply/unit之上构成一条自底向上的抽象阶梯。四、对比延伸Traverse.mapTraverse的同构实现值得注意sequenceMap与 Traverse 练习版 中待实现的mapTraverse高度同构。答案版 Traverse.scala 给出了它的实现given mapTraverse[K]: Traverse[Map[K, _]] with extension A override def traverse[G[_]: Applicative, B](f: A G[B]): G[Map[K, B]] m.foldLeft(summon[Applicative[G]].unit(Map.empty[K, B])): case (acc, (k, a)) acc.map2(f(a))((m, b) m (k - b))两者结构几乎一模一样都是foldLeftunit(Map.empty)map2累积插入。区别在于sequenceMap处理的是值已经是F[V]的 Map直接对F[V]做map2mapTraverse处理的是值还是纯值A的 Map先经f: A G[B]产生G[B]再做同样的累积。若定义f identityA F[V]情形traverse便退化为sequence与sequenceMap殊途同归。这也印证了提示中Map 本质上是键值对列表的普适性任何能用foldLeft折叠的容器都可以用同样的unitmap2配方实现序列化。五、实操验证在仓库中运行与自测本仓库为每个练习都提供了脚手架、答案与测试骨架你可以这样验证sequenceMap打开练习文件在 练习版 Applicative.scala 中找到sequenceMap的???占位填入上面的实现建议先独立尝试卡住再看 12.answer.md。对照答案完整实现位于 答案版 Applicative.scala可作为类型与语义的参照。编译与交互验证在仓库根目录执行scala-cli compile .编译全部练习与答案需要 REPL 试跑时执行scala-cli console .例如scala import fpinscala.answers.applicative.Applicative.{optionMonad, given} scala val ofv Map(a - Some(1), b - Some(2)) scala import fpinscala.answers.applicative.Applicative.{*, given} scala ofv.foldLeft(...) // 或用答案中的 sequenceMap 直接验证更简单的做法是直接构造Map[String, Option[Int]]后调用答案版Applicative[Option]的sequenceMap观察某个值为 None 时整体返回 None的效果传播。运行测试执行scala-cli test .可运行全部单元测试注意未完成的练习对应测试会失败这是 README 明确说明的预期行为README.md。目前src/test下尚无针对sequenceMap的专门测试用例但你可以参照 src/test/scala/fpinscala/exercises 下既有套件的写法自行补充。提示本项目基于 Scala CLI 构建另提供 SBT 构建见 project/plugins.sbt 与 README.md执行编译、REPL 与测试的完整命令清单都在 README.md 中。六、小结从一条提示到一类模式练习 12.12 的提示虽短却浓缩了函数式编程中一个高频模式把可折叠的容器当作对列表来处理用unit造种子、用map2累积、用折叠遍历。掌握sequenceMap后你可以把同样的配方迁移到任意Foldable容器List、Tree、Map、Option 乃至自定义数据类型这也正是下一节练习 12.13Traverse与mapTraverse将要系统化的内容——届时你会发现sequenceMap只是对所有可遍历结构统一抽象的一个具体实例。赞分享示例工程【免费下载链接】fpinscalaCode, exercises, answers, and hints to go along with the book Functional Programming in Scala项目地址https://gitcode.com/gh_mirrors/fp/fpinscala点击查看免费下载相关推荐fpinscala 第 12 章实战用 Applicative 的 sequenceMap 实现 Map[K, F[V]] → F[Map[K, V]]fpinscala 第 12 章实战用 Applicative 的 sequenceMap 实现 Map K, F V → F Map K, V 导读 本文围示例工程fpinscala 精讲用 Applicative 的 traverse/sequence 实现列表转置第 12 章练习 04fpinscala 精讲用 Applicative 的 traverse/sequence 实现列表转置第 12 章练习 04 导读 本篇文章围绕 fpi示例工程fpinscala 练习 12.8 详解用 Applicative.product 组合两个 Applicative 实例fpinscala 练习 12.8 详解用 Applicative.product 组合两个 Applicative 实例 本篇技术指南聚焦《Function示例工程上一篇N_m3u8DL-RE 手把手流媒体下载指南下一篇LrcHelper网易云音乐双语歌词下载完整指南 - 轻松获取精准歌词创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询