Scala 如何正确使用IO和OptionT在服务层中进行理解?
我有一个带有CRUD操作的简单存储库接口(可能,在general trait中将隐式会话作为参数传递是个坏主意): 我想这样使用它:Scala 如何正确使用IO和OptionT在服务层中进行理解?,scala,functional-programming,scala-cats,Scala,Functional Programming,Scala Cats,我有一个带有CRUD操作的简单存储库接口(可能,在general trait中将隐式会话作为参数传递是个坏主意): 我想这样使用它: for { _ <- repository.insert(???) _ <- repository.delete(???) v <- repository.find(???).value _ <- someFunctionReliesOnReturnedValue(v) } yield (???) 表: create ta
for {
_ <- repository.insert(???)
_ <- repository.delete(???)
v <- repository.find(???).value
_ <- someFunctionReliesOnReturnedValue(v)
} yield (???)
表:
create table entity (id numeric(38), value varchar(255));
我得到了编译错误:
错误:(69,13)类型不匹配;发现:cats.effect.IO[单位]
必需:cats.data.option[猫效应IO,?]
_一般来说,您应该将所有不同的结果转换为具有monad的“最通用”类型。在这种情况下,这意味着您应该在整个过程中使用
OptionT[IO,A]
,通过OptionT.liftF将所有这些IO[Entity]
转换为OptionT[IO,Entity]
:
for {
_ <- OptionT.liftF(repository.insert(???))
_ <- OptionT.liftF(repository.delete(???))
v <- repository.find(???)
_ <- someFunctionReliesOnReturnedValue(v)
} yield (???)
有。此外,有些人更愿意返回IO[Option[Entity]]
,并将其包装到Option
中,仅在中进行理解。你能不能把这个问题转换成一个更类似的格式?哦,当然,我会转换的。只是想展示一下基本的想法,你可以在yield中使用Util.createFile(e.value)。或者,使用=Util.createFile(e.value)而不是@LalitPrakash,问题是在这一步之后可能还有另一步,这取决于文件创建的结果(即使存在单位返回类型-这只是一个示例),谢谢您的回答!我知道主要意思,但我有几个问题。将不同的结果类型显式转换为最通用的类型是正确的方法吗?还是使用隐式转换更好?和我在示例中所做的一样,将不同的结果类型混合在一个单独的结果类型中以便于理解,这是“确定”的吗?FP方法对我来说是新的,所以我试图找出“最佳实践”,是否使用隐式转换取决于您的偏好。我认为社区的趋势是非常少地使用它们,因为它们往往违反了最小惊讶的原则。混合不同的一元类型(IO、OptionT、Future等)是行不通的,但混合它们包含的结果类型(Entity、Int等)也可以。
import java.nio.file.{Files, Paths}
import cats.data.OptionT
import cats.effect.IO
import scalikejdbc._
import scala.util.Try
case class Entity(id: Long, value: String)
object Entity extends SQLSyntaxSupport[Entity] {
override def tableName: String = "entity"
override def columnNames: Seq[String] = Seq("id", "value")
def apply(g: SyntaxProvider[Entity])(rs: WrappedResultSet): Entity = apply(g.resultName)(rs)
def apply(r: ResultName[Entity])(rs: WrappedResultSet): Entity =
Entity(rs.long(r.id), rs.string(r.value))
}
trait Repository[Entity, PK] {
def find(pk: PK)(implicit session: DBSession): OptionT[IO, Entity]
def insert(e: Entity)(implicit session: DBSession): IO[Entity]
}
class EntityRepository extends Repository[Entity, Long] {
private val alias = Entity.syntax("entity")
override def find(pk: Long)(implicit session: DBSession): OptionT[IO, Entity] = OptionT{
IO{
withSQL {
select(alias.resultAll).from(Entity as alias).where.eq(Entity.column.id, pk)
}.map(Entity(alias.resultName)(_)).single().apply()
}
}
override def insert(e: Entity)(implicit session: DBSession): IO[Entity] = IO{
withSQL {
insertInto(Entity).namedValues(
Entity.column.id -> e.id,
Entity.column.value -> e.value,
)
}.update().apply()
e
}
}
object EntityRepository {
def apply(): EntityRepository = new EntityRepository()
}
object Util {
def createFile(value: String): IO[Unit] = IO(Files.createDirectory(Paths.get("path", value)))
}
class Service {
val repository = EntityRepository()
def logic(): Either[Throwable, Unit] = Try {
DB localTx {
implicit session => {
val result: IO[Unit] = for {
_ <- repository.insert(Entity(1, "1"))
_ <- repository.insert(Entity(2, "2"))
e <- repository.find(3)
_ <- Util.createFile(e.value) // error
//after this step there is possible more steps (another insert or find)
} yield ()
result.unsafeRunSync()
}
}
}.toEither
}
object Test extends App {
ConnectionPool.singleton("jdbc:postgresql://localhost:5433/postgres", "postgres", "")
val service = new Service()
service.logic()
}
create table entity (id numeric(38), value varchar(255));
for {
_ <- OptionT.liftF(repository.insert(???))
_ <- OptionT.liftF(repository.delete(???))
v <- repository.find(???)
_ <- someFunctionReliesOnReturnedValue(v)
} yield (???)
val result: OptionT[IO, ...] = ...
result.value.unsafeRunSync().getOrElse(throw new FooException(...))