使用 \/ 和 IO 的理解示例
For-Comprehension Example with \/ and IO
给定以下 Foo
案例 class:
scala> case class Foo(x: Int)
defined class Foo
我在 validateFoo
:
中构造它之前检查它是否有效 Foo
scala> def validateFoo(foo: Foo): \/[String, Foo] =
(if(foo.x > 0) foo.success[String]
else ("invalid foo").failure[Foo]).disjunction
validateFoo: (foo: Foo)scalaz.\/[String,Foo]
最后,f
创建一个 Foo
,然后尝试执行 IO
操作(例如:将 Foo
保存到数据库)。
scala> def f(x: Foo): IO[\/[String,Int]] = for {
| foo <- validateFoo(x)
| ioFoo <- IO { foo }
| } yield ioFoo
<console>:19: error: type mismatch;
found : scalaz.effect.IO[Foo]
required: scalaz.\/[?,?]
ioFoo <- IO { foo }
^
<console>:18: error: type mismatch;
found : scalaz.\/[String,Nothing]
required: scalaz.effect.IO[scalaz.\/[String,Int]]
foo <- validateFoo(x)
^
但是,我 运行 在尝试将 bind
链接在一起进行理解时遇到了上述问题。
在我看来,问题在于正确的 return 类型是 IO[\/[String, Int]]
。但是,我不知道如何验证 Foo
和 在 IO
monad 中用 for-comprehension 处理它。
您的 validateFoo 定义似乎有误。我想你想要
scala> def validateFoo(foo: Foo): \/[String, Foo] =
(if(foo.x > 0) foo.success[Foo]
else ("invalid foo").failure[String]).disjunction
您混淆了成功和失败案例中的 String 和 Foo。
您不能像这样组合 String \/ A
和 IO[A]
计算 — 如果您对 for
理解进行脱糖处理,您会发现这些类型根本无法计算。
如果你真的想要,你可以使用一个 monad 转换器来组合两个 monad。例如,假设我们有以下设置(本质上是您的代码,但进行了一些清理,并使用表示数据库操作的新方法):
import scalaz._, Scalaz._, effect._
case class Foo(x: Int)
def validateFoo(foo: Foo): String \/ Foo =
(foo.x > 0) either foo or "invalid foo"
def processFoo(foo: Foo): IO[Foo] = IO {
println(foo)
foo
}
现在我们可以这样写了:
type IOStringOr[A] = EitherT[IO, String, A]
def f(x: Foo): IOStringOr[Foo] = for {
foo <- EitherT(validateFoo(x).point[IO])
ioFoo <- processFoo(foo).liftIO[IOStringOr]
} yield ioFoo
现在您可以 运行 EitherT
获得 IO
操作:
val ioAction: IO[String \/ Foo] = f(Foo(10)).run
这就是您可以在for
理解中一起使用\/
和IO
的方式。不过,在您的情况下,这绝对是矫枉过正。我可能会这样写:
def f(x: Foo): IO[String \/ Foo] =
validateFoo(x).bitraverse(_.point[IO], processFoo)
这使用 \/
的 Bitraverse
实例将一个函数映射到析取两边的 IO
中,然后将整个东西翻转过来。
给定以下 Foo
案例 class:
scala> case class Foo(x: Int)
defined class Foo
我在 validateFoo
:
Foo
scala> def validateFoo(foo: Foo): \/[String, Foo] =
(if(foo.x > 0) foo.success[String]
else ("invalid foo").failure[Foo]).disjunction
validateFoo: (foo: Foo)scalaz.\/[String,Foo]
最后,f
创建一个 Foo
,然后尝试执行 IO
操作(例如:将 Foo
保存到数据库)。
scala> def f(x: Foo): IO[\/[String,Int]] = for {
| foo <- validateFoo(x)
| ioFoo <- IO { foo }
| } yield ioFoo
<console>:19: error: type mismatch;
found : scalaz.effect.IO[Foo]
required: scalaz.\/[?,?]
ioFoo <- IO { foo }
^
<console>:18: error: type mismatch;
found : scalaz.\/[String,Nothing]
required: scalaz.effect.IO[scalaz.\/[String,Int]]
foo <- validateFoo(x)
^
但是,我 运行 在尝试将 bind
链接在一起进行理解时遇到了上述问题。
在我看来,问题在于正确的 return 类型是 IO[\/[String, Int]]
。但是,我不知道如何验证 Foo
和 在 IO
monad 中用 for-comprehension 处理它。
您的 validateFoo 定义似乎有误。我想你想要
scala> def validateFoo(foo: Foo): \/[String, Foo] =
(if(foo.x > 0) foo.success[Foo]
else ("invalid foo").failure[String]).disjunction
您混淆了成功和失败案例中的 String 和 Foo。
您不能像这样组合 String \/ A
和 IO[A]
计算 — 如果您对 for
理解进行脱糖处理,您会发现这些类型根本无法计算。
如果你真的想要,你可以使用一个 monad 转换器来组合两个 monad。例如,假设我们有以下设置(本质上是您的代码,但进行了一些清理,并使用表示数据库操作的新方法):
import scalaz._, Scalaz._, effect._
case class Foo(x: Int)
def validateFoo(foo: Foo): String \/ Foo =
(foo.x > 0) either foo or "invalid foo"
def processFoo(foo: Foo): IO[Foo] = IO {
println(foo)
foo
}
现在我们可以这样写了:
type IOStringOr[A] = EitherT[IO, String, A]
def f(x: Foo): IOStringOr[Foo] = for {
foo <- EitherT(validateFoo(x).point[IO])
ioFoo <- processFoo(foo).liftIO[IOStringOr]
} yield ioFoo
现在您可以 运行 EitherT
获得 IO
操作:
val ioAction: IO[String \/ Foo] = f(Foo(10)).run
这就是您可以在for
理解中一起使用\/
和IO
的方式。不过,在您的情况下,这绝对是矫枉过正。我可能会这样写:
def f(x: Foo): IO[String \/ Foo] =
validateFoo(x).bitraverse(_.point[IO], processFoo)
这使用 \/
的 Bitraverse
实例将一个函数映射到析取两边的 IO
中,然后将整个东西翻转过来。