map 和 flatmap 操作对 Reader Monad 意味着什么
What does the map and flat map operations mean for a ReaderMonad
我是一个scala新手。我来自 Java 背景。我一直在阅读有关 monad 的文章,并对它形成了一个大概的认识。虽然我可以欣赏 map
和 flatMap
对 List
等类型的操作,但当涉及到 reader monad
时,我无法理解它们的含义。
有人可以举一些简单的例子吗?
我知道我们需要 ReaderMonads 来促进一元函数的组合,这样我们就可以使用像 for - comprehensions 这样的奇特语法。我也明白我们需要满足 monad gods 才能实现这一点。
我只想了解 "What does map and flatmap" 函数的意思 ?
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
可以根据 pure
和 flatMap
实现,如下所示:
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
类型的值,我们必须 return 一个给定 X
类型的值的函数,returns 一个 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
,它需要一个 A
和一个 X
。 x
正好有一个 X
,但我们需要一个 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
表示要从 "environment"(或 "config")X
中读取一些值 A
.然后,在 A
上分支,从环境中读取另一个值 B
。
我们也可以继续手动实施 map
:
def map[A, B](ma: X => A)(f: A => B): X => B = ???
查看参数 X => A
和 A => B
,以及预期的输出 X => B
,这看起来很像函数组合,确实如此。
使用 cats 和导入的示例用法:
import cats.data.Reader
假设我们有一些 Config
类型:
case class Config(inDev: Boolean, devValue: Int, liveValue: Int)
告诉我们是否处于 "dev" 环境中,并为我们提供 "dev" 和 "live." 的值 我们可以从编写一个简单的 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
这通常可以用作实现依赖注入的好方法,仅使用函数!
我是一个scala新手。我来自 Java 背景。我一直在阅读有关 monad 的文章,并对它形成了一个大概的认识。虽然我可以欣赏 map
和 flatMap
对 List
等类型的操作,但当涉及到 reader monad
时,我无法理解它们的含义。
有人可以举一些简单的例子吗?
我知道我们需要 ReaderMonads 来促进一元函数的组合,这样我们就可以使用像 for - comprehensions 这样的奇特语法。我也明白我们需要满足 monad gods 才能实现这一点。 我只想了解 "What does map and flatmap" 函数的意思 ?
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
可以根据 pure
和 flatMap
实现,如下所示:
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
类型的值,我们必须 return 一个给定 X
类型的值的函数,returns 一个 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
,它需要一个 A
和一个 X
。 x
正好有一个 X
,但我们需要一个 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
表示要从 "environment"(或 "config")X
中读取一些值 A
.然后,在 A
上分支,从环境中读取另一个值 B
。
我们也可以继续手动实施 map
:
def map[A, B](ma: X => A)(f: A => B): X => B = ???
查看参数 X => A
和 A => B
,以及预期的输出 X => B
,这看起来很像函数组合,确实如此。
使用 cats 和导入的示例用法:
import cats.data.Reader
假设我们有一些 Config
类型:
case class Config(inDev: Boolean, devValue: Int, liveValue: Int)
告诉我们是否处于 "dev" 环境中,并为我们提供 "dev" 和 "live." 的值 我们可以从编写一个简单的 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
这通常可以用作实现依赖注入的好方法,仅使用函数!