繁体   English   中英

使用 scala-cats IO 类型封装一个可变的 Java 库

[英]Using scala-cats IO type to encapsulate a mutable Java library

我知道,一般来说,关于决定一个人想要 model 作为效果有很多话要说这个讨论是在 IO 一章的 Scala 的函数式编程中介绍的。

尽管如此,我还没有完成这一章,我只是在与 Cats IO 一起讨论之前将其从头到尾浏览。

同时,我有点需要在工作中尽快交付一些代码。 它依赖于一个 Java 库,它只是关于突变。 该库是很久以前开始的,出于遗留原因,我看不到它们发生变化。

总之,长话短说。 实际上将任何变异的 function 建模为 IO 是封装变异 java 库的可行方法吗?

Edit1(根据要求,我添加了一个片段)

准备进入 model,改变 model 而不是创建一个新的。 例如,我会将 jena 与 gremlin 进行对比,gremlin 是一个基于图形数据的函数库。

def loadModel(paths: String*): Model =
    paths.foldLeft(ModelFactory.createOntologyModel(new OntModelSpec(OntModelSpec.OWL_MEM)).asInstanceOf[Model]) {
      case (model, path) ⇒
        val input = getClass.getClassLoader.getResourceAsStream(path)
        val lang  = RDFLanguages.filenameToLang(path).getName
        model.read(input, "", lang)
    }

那是我的 scala 代码,但是网站中记录的 java api 看起来像这样。

// create the resource
Resource r = model.createResource();

// add the property
r.addProperty(RDFS.label, model.createLiteral("chat", "en"))
 .addProperty(RDFS.label, model.createLiteral("chat", "fr"))
 .addProperty(RDFS.label, model.createLiteral("<em>chat</em>", true));

// write out the Model
model.write(system.out);
// create a bag
Bag smiths = model.createBag();

// select all the resources with a VCARD.FN property
// whose value ends with "Smith"
StmtIterator iter = model.listStatements(
    new SimpleSelector(null, VCARD.FN, (RDFNode) null) {
        public boolean selects(Statement s) {
                return s.getString().endsWith("Smith");
        }
    });
// add the Smith's to the bag
while (iter.hasNext()) {
    smiths.add(iter.nextStatement().getSubject());
}

所以,这个问题有三种解决方案。

1.简单而肮脏

如果不纯 API 的所有用法都包含在代码库的单个/小部分中,您可能只是“作弊”并执行以下操作:

def useBadJavaAPI(args): IO[Foo] = IO {
  // Everything inside this block can be imperative and mutable.
}

我说“作弊”是因为IO的想法是组合,而一个大的IO块并不是真正的组合。 但是,有时您只想封装该遗留部分而不关心它。

2. 走向组合。

基本上,和上面一样,但是在中间放了一些flatMaps

// Instead of:
def useBadJavaAPI(args): IO[Foo] = IO {
  val a = createMutableThing()
  mutableThing.add(args)
  val b = a.bar()
  b.computeFoo()
}

// You do something like this:
def useBadJavaAPI(args): IO[Foo] =
  for {
    a <- IO(createMutableThing())
    _ <- IO(mutableThing.add(args))
    b <- IO(a.bar())
    result <- IO(b.computeFoo())
  } yield result

这样做有几个原因:

  1. 因为命令式/可变 API 不包含在单个方法/ class 中,而是包含在其中的几个中。 IO 中的小步骤封装正在帮助您进行推理。
  2. 因为你想慢慢地将代码迁移到更好的地方。
  3. 因为你想对自己感觉更好:p

3.将其包装在一个纯界面中

这与许多第三方库(例如Doobiefs2-blobstoreneotypes基本相同。 在纯接口上包装Java库。

请注意,因此,必须完成的工作量远远超过前两种解决方案。 因此,如果可变的 API 正在“感染”您的代码库的许多地方,或者在多个项目中更糟,这是值得的; 如果是这样,那么这样做是有意义的,并且发布是作为一个独立的模块。
(将该模块发布为开源库也可能是值得的,您最终可能会帮助其他人并获得其他人的帮助)

由于这是一项更大的任务,仅提供您必须做的所有事情的完整答案并不容易,因此了解这些库是如何实现的并在此处或在 gitter 频道中提出更多问题可能会有所帮助。

但是,我可以给你一个简短的片段:

// First define a pure interface of the operations you want to provide
trait PureModel[F[_]] { // You may forget about the abstract F and just use IO instead.
  def op1: F[Int]
  def op2(data: List[String]): F[Unit]
}

// Then in the companion object you define factories.
object PureModel {
  // If the underlying java object has a close or release action,
  // use a Resource[F, PureModel[F]] instead.
  def apply[F[_]](args)(implicit F: Sync[F]): F[PureModel[F]] = ???
}

现在,如何创建实现是棘手的部分。 也许您可以使用Sync之类的东西来初始化可变的 state。

def apply[F[_]](args)(implicit F: Sync[F]): F[PureModel[F]] =
  F.delay(createMutableState()).map { mutableThing =>
    new PureModel[F] {
      override def op1: F[Int] = F.delay(mutableThing.foo())
      override def op2(data: List[String]): F[Unit] = F.delay(mutableThing.bar(data))
    }
  }

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM