【问题标题】:What does the map and flat map operations mean for a ReaderMonadmap 和 flatmap 操作对 Reader Monad 意味着什么
【发布时间】:2016-03-28 06:03:39
【问题描述】:

我是一个 scala 新手。我来自 Java 的背景。我一直在阅读 monads 并且已经形成了关于它的一般概念。虽然我很欣赏mapflatMapList 等类型的操作,但当涉及reader monads 时,我无法理解它们的含义。 有人可以举一些简单的例子吗?

我知道我们需要 ReaderMonads 来促进一元函数组合,以便我们可以使用像 for - 理解这样的花哨的语法。我也明白,我们需要满足单子神才能实现这一点。 我只想了解“地图和平面地图”对功能意味着什么?

【问题讨论】:

    标签: scala reader-monad


    【解决方案1】:

    reader monad,通常写成Reader[A, B],只是函数类型A => B。 Scala 中的 monad 编码如下所示:

    trait Monad[M[_]] {
      def pure[A](a: A): M[A]
      def flatMap[A, B](ma: M[A])(f: A => M[B]): M[B]
    }
    

    其中map 可以用pureflatMap 来实现,如下所示:

    def map[A, B](ma: M[A])(f: A => B): M[B] = flatMap(ma)(a => pure(f(a)))
    

    所以我们需要做的第一件事是让我们的二元类型构造函数Reader 适合Monad 期望的一元类型构造函数。这是通过固定第一个(输入)类型参数并保留第二个(输出)类型参数来完成的。

    implicit def readerMonad[X]: Monad[X => ?] = ???
    

    (这里?的使用是通过精彩的kind-projector编译器插件)。

    pure 开始,我们将出现的M[_] 替换为X => _

    def pure[A](a: A): X => A
    

    给定A 类型的某个值,我们必须返回一个函数,该函数给定X 类型的值,返回A 类型的值。唯一可能的就是常量函数。

    def pure[A](a: A): X => A = _ => a
    

    现在替换为flatMap..

    def flatMap[A, B](ma: X => A)(f: A => (X => B)): X => B = (x: X) => ???
    

    这有点棘手,但我们可以使用类型来指导我们实现!我们有:

    ma: X => A
    f: A => (X => B)
    x: X
    

    我们想得到一个B。我们知道如何做到这一点的唯一方法是通过f,它需要AX。我们正好有一个来自xX,但我们需要一个A。我们看到我们只能从ma 得到一个A,它想要一个X,同样只有x 提供给我们。因此我们有..

    def flatMap[A, B](ma: X => A)(f: A => (X => B)): X => B =
      (x: X) => {
        val a = ma(x)
        val b = f(a)(x)
        b
      }
    

    大声朗读,Reader 上的flatMap 表示要从“环境”(或“配置”)X 中读取一些值 A。然后,在A 上分支,从环境中读取另一个值B

    我们也可以手动实现map

    def map[A, B](ma: X => A)(f: A => B): X => B = ???
    

    查看参数X => AA => B,以及预期的输出X => B,这看起来完全像函数组合,确实如此。

    使用 cats 和导入的示例用法:

    import cats.data.Reader
    

    假设我们有一些Config 类型:

    case class Config(inDev: Boolean, devValue: Int, liveValue: Int)
    

    它告诉我们是否处于“开发”环境中,并为我们提供“开发”和“生活”的价值。我们可以从编写一个简单的Reader[Config, Boolean] 开始,它会为我们读出inDev 标志。

    val inDev: Reader[Config, Boolean] = Reader((c: Config) => c.inDev)
    

    我们可以编写一个简单的函数,给定一些布尔值,将读取适当的值。

    def branch(flag: Boolean): Reader[Config, Int] =
      if (flag) Reader((c: Config) => c.devValue)
      else      Reader((c: Config) => c.liveValue)
    

    现在我们可以组合两者:

    val getValue: Reader[Config, Int] =
      for {
        flag <- inDev
        value <- branch(flag)
      } yield value
    

    现在我们可以通过传入各种Config 值来获取我们的Int

    getValue.run(Config(true, 1, 2)) // 1
    getValue.run(Config(false, 1, 2)) // 2
    

    这通常可以用作实现依赖注入的好方法,只使用函数!

    【讨论】:

    • 谢谢你的答案。很深。我需要多次阅读这篇文章,看看我是否完全理解。能否也提供一些简单的用法示例?
    猜你喜欢
    • 2018-10-10
    • 2015-04-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-28
    • 2020-10-12
    • 2011-06-11
    • 2010-10-02
    相关资源
    最近更新 更多