ステートフルオブジェクトの構築は、エフェクトタイプでモデル化する必要がありますか?


9

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)
  }

構造は間違いがないので、のような弱い型クラスを使用できますApply

// never throws
def withWeakEffect[F: Applicative](name: String): F[Service] =
  new Service(name).pure[F]

これらはすべて純粋で確定的だと思います。結果のインスタンスは毎回異なるため、参照透過的ではありません。エフェクトタイプを使うのにいい時ですか?または、ここで異なる機能パターンがあるでしょうか?


2
はい、変更可能な状態の作成は副作用です。そのため、それはa内で発生しdelayF [Service]を返す必要があります。例として、IOstartメソッドを参照してください。プレーンファイバーの代わりにIO [Fiber [IO、?]]を返します。
Luis MiguelMejíaSuárez19年

1
この問題への完全な答えは、以下を参照してください。このこのことを
Luis MiguelMejíaSuárez19年

回答:


3

ステートフルオブジェクトの構築は、エフェクトタイプでモデル化する必要がありますか?

すでにエフェクトシステムを使用している場合は、おそらくRef変更可能な状態を安全にカプセル化するタイプがあります。

つまり、ステートフルオブジェクトをでモデル化しRefます。それらの作成(およびそれらへのアクセス)はすでに効果があるので、これにより自動的にサービスの作成も効果的になります。

これにより、元の質問が適切に回避されます。

レギュラーvarを使用して内部の可変状態を手動で管理する場合は、この状態に触れるすべての操作が効果と見なされることを確認する必要があります(また、ほとんどの場合スレッドセーフになる)。これは面倒でエラーが発生しやすくなります。これは可能であり、ステートフルオブジェクトの作成を効果的にする必要がない(参照整合性の損失に耐えられる限り)必要があるという@atlの回答に同意しますが、問題を解決して受け入れる必要があります。エフェクトシステムのツールは?


これらはすべて純粋で確定的だと思います。結果のインスタンスは毎回異なるため、参照透過的ではありません。エフェクトタイプを使うのにいい時ですか?

質問を次のように言い換えることができる場合

参照透過性とローカルの推論の追加の利点(「弱い型クラス」を使用した実装に加えて)は、状態の効果タイプ(状態アクセスとミューテーションにすでに使用されている必要があります)を使用することを正当化するのに十分です作成?

次に:はい、絶対に

これが役立つ理由の例を挙げます。

サービスの作成が効果を上げていなくても、以下は正常に機能します。

val service = makeService(name)
for {
  _ <- service.doX()
  _ <- service.doY()
} yield Ack.Done

ただし、これを以下のようにリファクタリングすると、コンパイル時エラーは発生しませんが、動作が変更され、おそらくバグが発生しています。makeService有効であると宣言した場合、リファクタリングは型チェックされず、コンパイラによって拒否されます。

for {
  _ <- makeService(name).doX()
  _ <- makeService(name).doY()
} yield Ack.Done

メソッドの名前をmakeService(およびパラメーターも含めて)付与すると、メソッドの動作が明確になり、リファクタリングは安全ではないことがわかりますが、「ローカルな推論」は、調べる必要がないことを意味します。命名規則とそれmakeServiceを理解するための実装時:動作を変更せずに機械的に入れ替えることができない式(重複排除、遅延、意欲、デッドコードの除去、並列化、遅延、キャッシュ、キャッシュからのパージなど)つまり、「純粋」ではありません)、効果があると入力する必要があります。


2

この場合、ステートフルサービスは何を指しますか?

オブジェクトが構築されるときに副作用を実行するということですか? このため、アプリケーションの起動時に副作用を実行するメソッドを用意することをお勧めします。構築中に実行する代わりに。

それとも、サービス内で変更可能な状態を保持していると言っているのでしょうか?内部の変更可能な状態が公開されていない限り、問題はありません。サービスと通信するための純粋な(参照透過)メソッドを提供する必要があるだけです。

私の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)
}

IMO、これは効果的である必要はありません。ネットワーク呼び出しを行った場合も同じことが起こるからです。ただし、このクラスのインスタンスが1つだけであることを確認する必要があります。

Reffrom 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)
  }
}

ステートフルオブジェクト(複数の同時実行プリミティブとしましょう)に依存する共有サービスまたはライブラリを作成していて、ユーザーが何を初期化するかを気にしたくない場合は、OTOH。

そして、はい、それは効果に包まれる必要があります。Resource[F, MyStatefulService]すべてが適切に閉じられていることを確認するようなものを使用できます。またはF[MyStatefulService]、閉じるものが何もない場合。


「サービスと通信するための純粋なメソッドをメソッドに提供する必要があるだけ」またはその逆:純粋に内部状態の初期構築は効果である必要はありませんが、サービス内のその可変状態と相互作用する操作その後、どのような方法でも効果があるとマークする必要があります(などの事故を回避するためval neverRunningThisButStillMessingUpState = Task.pure(service.changeStateThinkingThisIsPure()).repeat(5)
Thilo

または反対側から来る:そのサービスの作成を効果的にするかどうかは本当に重要ではありません。ただし、どちらの方法でも、サービスとの対話は何らかの方法で行う必要があります(サービス内に変更可能な状態が含まれているため、これらの対話によって影響を受けます)。
Thilo、

1
@thiloはい、そうです。私が意味したことpureは、それは参照的に透明でなければならないということです。たとえば、Futureの例を考えてみましょう。val x = Future {... }そしてdef x = Future { ... }別のことを意味します。(これはコードをリファクタリングしているときにあなたを噛む可能性があります)しかし、cats-effect、monix、またはzioの場合はそうではありません。
atl
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.