构造状态对象是否应使用效果类型建模?

Mar*_*las 8 functional-programming scala scala-cats cats-effect

当使用像Scala和的功能环境时cats-effect,是否应该使用效果类型来建模有状态对象的构造?

// not a value/case class
class Service(s: name)

def withoutEffect(name: String): Service =
  new Service(name)

def withEffect[F: Sync](name: String): F[Service] =
  F.delay {
    new Service(name)
  }
Run Code Online (Sandbox Code Playgroud)

构造不是容易犯错的,因此我们可以使用较弱的typeclass,例如Apply

// never throws
def withWeakEffect[F: Applicative](name: String): F[Service] =
  new Service(name).pure[F]
Run Code Online (Sandbox Code Playgroud)

我想所有这些都是纯粹的和确定性的。只是不是参照透明的,因为每次生成的实例都是不同的。那是使用效果类型的好时机吗?还是这里会有不同的功能模式?

atl*_*atl 2

这里所说的有状态服务指的是什么?

你的意思是它会在构造对象时执行副作用吗? 为此,更好的想法是使用一种在应用程序启动时运行副作用的方法。而不是在施工期间运行它。

或者也许您是说它在服务内部保持可变状态?只要内部可变状态不暴露,就应该没问题。您只需要提供一个纯粹的(引用透明的)方法来与服务进行通信。

扩展我的第二点:

假设我们正在构建一个内存数据库。

class InMemoryDB(private val hashMap: ConcurrentHashMap[String, String]) {
  def getId(s: String): IO[String] = ???
  def setId(s: String): IO[Unit] = ???
}

object InMemoryDB {
  def apply(hashMap: ConcurrentHashMap[String, String]) = new InMemoryDB(hashMap)
}
Run Code Online (Sandbox Code Playgroud)

IMO,这不需要有效,因为如果您进行网络调用,也会发生同样的事情。尽管如此,您需要确保该类只有一个实例。

如果你使用的Ref是 cats-effect,我通常会做的是flatMap入口点的 ref,所以你的类不必是有效的。

object Effectful extends IOApp {

  class InMemoryDB(storage: Ref[IO, Map[String, String]]) {
    def getId(s: String): IO[String] = ???
    def setId(s: String): IO[Unit] = ???
  }

  override def run(args: List[String]): IO[ExitCode] = {
    for {
      storage <- Ref.of[IO, Map[String, String]](Map.empty[String, String])
      _ = app(storage)
    } yield ExitCode.Success
  }

  def app(storage: Ref[IO, Map[String, String]]): InMemoryDB = {
    new InMemoryDB(storage)
  }
}
Run Code Online (Sandbox Code Playgroud)

OTOH,如果您正在编写依赖于有状态对象(比方说多个并发原语)的共享服务或库,并且您不希望用户关心要初始化什么。

然后,是的,它必须包含在效果中。您可以使用类似的东西Resource[F, MyStatefulService]来确保所有内容都正确关闭。或者只是F[MyStatefulService]如果没有什么可以关闭的。