示例简单 IO 类型如何消除 "FP in Scala" 中的副作用?
How the sample Simple IO Type get rid of side effects in "FP in Scala"?
我正在阅读第 13.2.1 章并遇到了可以处理 IO 输入并同时消除副作用的示例:
object IO extends Monad[IO] {
def unit[A](a: => A): IO[A] = new IO[A] { def run = a }
def flatMap[A,B](fa: IO[A])(f: A => IO[B]) = fa flatMap f
def apply[A](a: => A): IO[A] = unit(a)
}
def ReadLine: IO[String] = IO { readLine }
def PrintLine(msg: String): IO[Unit] = IO { println(msg) }
def converter: IO[Unit] = for {
_ <- PrintLine("Enter a temperature in degrees Fahrenheit: ")
d <- ReadLine.map(_.toDouble)
_ <- PrintLine(fahrenheitToCelsius(d).toString)
} yield ()
关于这段代码我有几个问题:
- 在
unit
函数中,def run = a
到底做了什么?
- 在
ReadLine
函数中,IO { readLine }
到底做了什么?它真的会执行 println
函数还是只是 return 一个 IO 类型?
- for理解中的
_
是什么意思(_ <- PrintLine("Enter a temperature in degrees Fahrenheit: ")
)?
- 为什么它消除了 IO 副作用?我看到这些功能仍然与输入和输出交互。
将 IO
monad 视为一小段程序定义的最简单方法。
因此:
- 这是
IO
定义,run
方法定义了IO
monad的作用。 new IO[A] { def run = a }
是创建 class 实例和定义方法 run
. 的 Scala 方法
- 有一点语法上的糖分。
IO { readLine }
与 IO.apply { readLine }
或 IO.apply(readLine)
相同,其中 readLine
是 => String
类型的按名称调用函数。这从 object IO
调用 unit
方法,因此这只是 IO
class 的实例的创建,还没有 运行。
- 因为
IO
是一个monad,可以使用for comprehension。它需要以类似 result <- someMonad
的语法存储每个 monad 操作的结果。要忽略结果,可以使用 _
,因此 _ <- someMonad
读取为执行 monad 但忽略结果。
- 这些方法都是
IO
定义,它们没有 运行 任何东西,因此没有副作用。只有在调用 IO.run
时才会出现副作用。
你的IO
定义如下:
trait IO { def run: Unit }
按照这个定义,你可以理解写 new IO[A] { def run = a }
意味着从你的特征中初始化一个匿名的 class,并分配 a
成为你调用 [= 时运行的方法15=]。因为 a
是 by name parameter,所以在创建时实际上没有 运行。
Scala 中的任何对象或 class 遵循 apply
方法的约定,可以调用为:ClassName(args)
,编译器将在其中搜索object/class 上的 apply
方法并将其转换为 ClassName.apply(args)
调用。更详尽的答案 can be found here。因此,因为 IO
伴随对象拥有这样一个方法:
def apply[A](a: => A): IO[A] = unit(a)
扩张是允许发生的。因此我们实际上调用了 IO.apply(readLine)
。
_
has many overloaded uses in Scala. This occurrence means "I don't care about the value returned from PrintLine
, discard it". It is so because the value returned is of type Unit,与我们无关
并不是IO
数据类型去掉了做IO的部分,而是延迟了[=56] =] 它到以后的时间点。我们通常说IO运行在应用程序的"edges"处,在Main
方法中。这些与外部世界的交互仍然会发生,但是由于我们将它们封装在 IO 中,我们可以在我们的程序中将它们 作为值 进行推理,这带来了很多好处。例如,我们现在可以 组合 副作用并依赖于 success/failure 它们的执行。我们可以模拟这些 IO 效果 (using other data types such as Const
),以及许多其他非常好的属性。
我正在阅读第 13.2.1 章并遇到了可以处理 IO 输入并同时消除副作用的示例:
object IO extends Monad[IO] {
def unit[A](a: => A): IO[A] = new IO[A] { def run = a }
def flatMap[A,B](fa: IO[A])(f: A => IO[B]) = fa flatMap f
def apply[A](a: => A): IO[A] = unit(a)
}
def ReadLine: IO[String] = IO { readLine }
def PrintLine(msg: String): IO[Unit] = IO { println(msg) }
def converter: IO[Unit] = for {
_ <- PrintLine("Enter a temperature in degrees Fahrenheit: ")
d <- ReadLine.map(_.toDouble)
_ <- PrintLine(fahrenheitToCelsius(d).toString)
} yield ()
关于这段代码我有几个问题:
- 在
unit
函数中,def run = a
到底做了什么? - 在
ReadLine
函数中,IO { readLine }
到底做了什么?它真的会执行println
函数还是只是 return 一个 IO 类型? - for理解中的
_
是什么意思(_ <- PrintLine("Enter a temperature in degrees Fahrenheit: ")
)? - 为什么它消除了 IO 副作用?我看到这些功能仍然与输入和输出交互。
将 IO
monad 视为一小段程序定义的最简单方法。
因此:
- 这是
IO
定义,run
方法定义了IO
monad的作用。new IO[A] { def run = a }
是创建 class 实例和定义方法run
. 的 Scala 方法
- 有一点语法上的糖分。
IO { readLine }
与IO.apply { readLine }
或IO.apply(readLine)
相同,其中readLine
是=> String
类型的按名称调用函数。这从object IO
调用unit
方法,因此这只是IO
class 的实例的创建,还没有 运行。 - 因为
IO
是一个monad,可以使用for comprehension。它需要以类似result <- someMonad
的语法存储每个 monad 操作的结果。要忽略结果,可以使用_
,因此_ <- someMonad
读取为执行 monad 但忽略结果。 - 这些方法都是
IO
定义,它们没有 运行 任何东西,因此没有副作用。只有在调用IO.run
时才会出现副作用。
你的
IO
定义如下:trait IO { def run: Unit }
按照这个定义,你可以理解写
new IO[A] { def run = a }
意味着从你的特征中初始化一个匿名的 class,并分配a
成为你调用 [= 时运行的方法15=]。因为a
是 by name parameter,所以在创建时实际上没有 运行。Scala 中的任何对象或 class 遵循
apply
方法的约定,可以调用为:ClassName(args)
,编译器将在其中搜索object/class 上的apply
方法并将其转换为ClassName.apply(args)
调用。更详尽的答案 can be found here。因此,因为IO
伴随对象拥有这样一个方法:def apply[A](a: => A): IO[A] = unit(a)
扩张是允许发生的。因此我们实际上调用了
IO.apply(readLine)
。_
has many overloaded uses in Scala. This occurrence means "I don't care about the value returned fromPrintLine
, discard it". It is so because the value returned is of type Unit,与我们无关并不是
IO
数据类型去掉了做IO的部分,而是延迟了[=56] =] 它到以后的时间点。我们通常说IO运行在应用程序的"edges"处,在Main
方法中。这些与外部世界的交互仍然会发生,但是由于我们将它们封装在 IO 中,我们可以在我们的程序中将它们 作为值 进行推理,这带来了很多好处。例如,我们现在可以 组合 副作用并依赖于 success/failure 它们的执行。我们可以模拟这些 IO 效果 (using other data types such asConst
),以及许多其他非常好的属性。