拓冰建站拓冰建站
首页 / 资讯中心 / 正文

动手创造Eff自定义效果:从零实现Maybe效果的完整教程

动手创造Eff自定义效果从零实现Maybe效果的完整教程【免费下载链接】effEff monad for cats - https://atnos-org.github.io/eff项目地址: https://gitcode.com/gh_mirrors/eff/effEff 是 Scala 生态中基于可扩展效果extensible effects的计算库它用Eff[R, A]类型优雅地管理副作用。这篇文章带你动手创造 Eff 自定义效果——从零开始实现一个Maybe效果类似Option的可选值类型帮你彻底搞懂 Eff 的核心机制。为什么需要自定义效果在 Scala 函数式编程中副作用日志、IO、状态、可选值通常通过单子或单子转换器来处理。当效果一多单子转换器就会形成洋葱式嵌套组合起来非常痛苦 。Eff 的思路完全不同效果可组合多个效果放在同一个效果栈R中而不是层层嵌套定义与执行分离先写程序再决定如何解释执行模块化处理器每个效果有独立的解释器可随时替换实现当你发现内置效果Reader、Writer、State、Option等见 OutOfTheBox.scala不够用时就可以动手创造自己的效果。本教程的目标效果是Maybe表示有值Just或无值Nothing。动手创造 Eff 自定义效果的三要素根据官方文档 CreateEffects.scala定义一个新效果只需要三样东西基础数据类型ADT代数数据类型智能构造函数把值发送进效果栈解释器决定如何执行该效果完整实现参考 MaybeEffectSnippet.scala。第一步定义 Maybe 数据类型sealed trait Maybe[A] case class JustA extends Maybe[A] case class Nothing[A]() extends Maybe[A]sealed trait加两个case class构成一个封闭的代数数据类型。这就是效果语法——描述程序能做的操作。官方提示建议把这类 ADT 定义在 Effect trait/object之外见 CreateEffects.scala 中的 Compiler limitation 一节否则在Member隐式解析时可能触发编译器崩溃。第二步定义类型别名与智能构造函数object MaybeEffect { type _maybe[R] Maybe | R def justR: _maybe, A: Eff[R, A] sendMaybe, R, A) def nothing[R: _maybe, A]: Eff[R, A] sendMaybe, R, A) }逐行拆解代码含义type _maybe[R] Maybe | R类型别名声明Maybe是效果栈R的成员sendMaybe, R, A)用Eff.send把Maybe值发送进效果栈返回Eff[R, A]R: _maybe上下文限定context bound要求效果栈R中确实包含Maybe核心原理Eff[R, A]中R是一组效果的类型级集合效果栈A是计算最终返回的值。send的作用是把一个操作嵌入到任意包含Maybe的栈中而不需要提前固定整个栈——这正是 Eff 相对单子转换器的最大优势。Maybe | R这种成员关系由 Member.scala 中的隐式实例自动推导|就是MemberIn类型的简写。第三步为效果栈提供 Applicative 实例given Applicative[Maybe] new Applicative[Maybe] { def pureA: Maybe[A] Just(a) def apA, B(fa: Maybe[A]): Maybe[B] (fa, ff) match { case (Just(a), Just(f)) Just(f(a)) case _ Nothing() } }为什么需要它因为 Eff 支持Applicative 效果组合把多个Maybe效果并行收集再一起解释底层逻辑见 Eff.scala 的文档注释。Maybe的语义是任何一环缺失就整体无值所以ap里只要出现Nothing就返回Nothing()。第四步编写解释器最关键的一步解释器回答的问题是遇到一个Maybe[X]值该做什么def runMaybeR, U, A (using m: Member.Aux[Maybe, R, U]): Eff[U, Option[A]] recurse(effect)(new Recurser[Maybe, U, A, Option[A]] { def onPure(a: A): Option[A] Some(a) def onEffectX: Either[X, Eff[U, Option[A]]] m match { case Just(x) Left(x) case Nothing() Right(Eff.pure(None)) } def onApplicative[X, T[_]: Traverse](ms: T[Maybe[X]]): Either[T[X], Maybe[T[X]]] Right(ms.sequence) })三个要点签名读法Member.Aux[Maybe, R, U]表示效果栈R包含Maybe把它移除后剩下的栈是U。解释完Maybe后程序继续在剩余栈U上运行最终结果是Option[A]。Recurser 三个回调定义见 Recurser.scalaonPure纯值直接包装成Some(a)onEffectLeft(x)表示抽取出值 x继续后续计算Right(...)表示放弃后续直接返回结果——所以Nothing时返回Eff.pure(None)干净地终止onApplicative把T[Maybe[X]]序列化为Maybe[T[X]]利用Maybe的 Applicative 语义recurse来自org.atnos.eff.interpret对象Interpret.scala它是栈安全的递归折叠器逐个消费效果、编译到目标、计算下一步——类似对List做foldRight。运行你的第一个 Maybe 效果程序万事俱备写一个 for 推导程序并执行import org.atnos.eff._ import MaybeEffect._ val action: Eff[Fx.fx1[Maybe], Int] for { a - just(2) b - just(3) } yield a b run(runMaybe(action)) // 得到 Some(5)Fx.fx1[Maybe]只含一个效果的栈类型级列表的语法糖for 推导只是构建递归数据结构并不会执行任何操作——程序是纯数据runMaybe(action)解释Maybe得到Eff[NoFx, Option[Int]]最后run取出纯值Some(5)如果把just(3)换成nothing[Int]结果就是None且后续步骤被安全跳过。从 Maybe 到实战组合更多效果自定义效果的威力在于组合。教程项目 Tutorial.scala 演示了一个 key-value 存储 DSL其安全解释器把KVStore效果翻译为内置效果组合State维护 map 数据Writer记录日志Either[Throwable, *]处理类型级错误type Stack Fx.fx4[KVStore, Either[Throwable, *], State[Map[String, Any], *], Writer[String, *]]四个效果共存于一个栈各自拥有独立解释器最后链式调用runStore.runEither.evalState(...).runWriter.run逐个剥洋葱。你也可以给自定义效果加扩展语法让运行链更流畅——这正是 Extensible Effects 的精髓效果处理器是模块化的甚至可以运行时替换。动手创造 Eff 自定义效果的检查清单✅ 完成一个效果只需按顺序回答四个问题ADT这个效果有哪些操作Just/Nothing构造函数如何用send把操作送入效果栈Applicative 实例多个该效果值如何并行组合Recurser 解释器遇到每个 case 时——继续Left、终止Right、还是转换到其他效果 想更深入建议按这个路径阅读项目文档源码Introduction.scala —— Eff 入门与效果栈概念CreateEffects.scala —— 创建自定义效果本文依据Tutorial.scala —— 对照 cats Free 单子教程的完整实战MaybeEffectSnippet.scala —— 本文 Maybe 效果完整代码现在你已经掌握了从零实现 Maybe 效果的完整流程定义 ADT →send智能构造 → Applicative 实例 →Recurser解释器。动手试试吧——下一个效果可以是你业务里的日志、配置读取或数据库操作 【免费下载链接】effEff monad for cats - https://atnos-org.github.io/eff项目地址: https://gitcode.com/gh_mirrors/eff/eff创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门