我是斯卡拉新手。我来自Java的背景。我一直在读monad,并形成了一个大致的想法。虽然我对map
等类型的flatMap
和List
操作表示赞赏,但我无法理解reader monad
的含义。
有人可以提出一些简单的例子。
我知道我们需要ReaderMonads来促进一元函数的组合,这样我们就可以使用像for - comprehensions这样的花哨语法。我也明白,为了实现这一点,我们需要monad gods满意。 我想要了解的是“地图和平面图对于函数意味着什么?”
答案 0 :(得分:4)
读者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
开始,我们用[{1}}替换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
和A
。我们只有一个来自X
的{{1}},但我们需要一个X
。我们发现,我们只能从x
获得A
,A
需要ma
,而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
}
上的flatMap
说要从“环境”(或“配置”)Reader
中读取一些值A
。然后,在X
上分支,从环境中读取另一个值A
。
我们可以继续手动实施B
:
map
查看参数def map[A, B](ma: X => A)(f: A => B): X => B = ???
和X => A
,以及预期的输出A => B
,这看起来与函数组合完全一样,确实如此。
使用cats导入的示例用法:
X => B
假设我们有一些import cats.data.Reader
类型:
Config
告诉我们我们是否处于“开发”环境中并为“dev”和“live”提供了价值。我们可以先编写一个简单的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
值来获取Int
:
Config
这通常可以用作实现依赖注入的好方法,只使用函数!