상태 저장 객체 생성은 효과 유형으로 모델링해야합니까?


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 내부에서 발생 delay하고 F [Service]를 반환 해야합니다 . 예를 들어 IOstart메소드를 참조 하면 일반 광섬유 대신 IO [Fiber [IO,?]]를 반환합니다 .
Luis Miguel Mejía Suárez

1
이 문제에 대한 완전한 대답은 this & this 를 참조하십시오 .
Luis Miguel Mejía Suárez

답변:


3

상태 저장 객체 생성은 효과 유형으로 모델링해야합니까?

이미 효과 시스템을 사용중인 경우, Ref변경 가능한 상태를 안전하게 캡슐화 하는 유형 이있을 가능성이 큽니다 .

따라서 나는 상태 저장 객체를로 모델링합니다Ref . 이것들에 대한 접근뿐만 아니라 그에 대한 접근도 이미 효과가 있기 때문에, 서비스를 자동으로 생성하는 것도 자동적으로 효과적입니다.

이것은 원래 질문을 깔끔하게 회피합니다.

정기적으로 내부 변경 가능 상태를 수동으로 관리하려는 var경우이 상태에 닿는 모든 작업이 효과로 간주되고 스레드 안전으로 간주되어 지루하고 오류가 발생하기 쉬운 지 확인해야합니다. 이것은 수행 할 수 있으며 @atl의 대답에 동의합니다 (당신이 참조 무결성의 손실로 살 수있는 한) Stateful 객체를 효과적으로 만들 필요는 없지만 문제와 포옹을 구하지 않는 이유는 무엇입니까? 당신의 효과 시스템의 도구는 끝까지?


나는 이것들이 모두 순수하고 결정적이라고 생각합니다. 결과 인스턴스가 매번 다르기 때문에 참조 용으로 투명하지 않습니다. 효과 타입을 사용하기에 좋은 시간입니까?

질문을 다음과 같이 표현할 수있는 경우

참조 투명성 및 상태에 대한 효과 유형 (이미 상태 액세스 및 돌연변이에 이미 사용 중이어야 함)을 사용하기에 충분한 로컬 추론과 참조 투명성의 추가 이점 ( "약한 유형 클래스"를 사용하여 올바르게 작동하는 구현의 상단에 있음) 창조?

그렇다면 : 그렇습니다 .

이것이 왜 유용한 지 예를 들어 보려면 :

서비스 생성이 적용되지 않더라도 다음은 제대로 작동합니다.

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

이 경우 상태 저장 서비스는 무엇을 의미합니까?

객체가 생성 될 때 부작용이 발생한다는 의미입니까? 이를 위해 더 좋은 아이디어는 응용 프로그램이 시작될 때 부작용을 실행하는 방법을 갖는 것입니다. 시공 중에 달리는 대신.

아니면 서비스 내부에서 변경 가능한 상태를 유지한다고 말하고 있습니까? 내부 변경 가능 상태가 노출되지 않는 한 괜찮습니다. 서비스와 통신하기 위해 순수한 (참조 적으로 투명한) 방법을 제공하면됩니다.

두 번째 요점을 확장하려면 :

메모리 DB를 구성한다고 가정 해 봅시다.

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, 네트워크 전화를 걸 경우에도 같은 일이 일어나므로 효과적 일 필요는 없습니다. 그러나이 클래스의 인스턴스가 하나만 있는지 확인해야합니다.

Refcats-effect에서 사용 하는 경우 일반적으로 내가하는 일은 flatMap진입 점의 심판에게 달려 있으므로 수업이 효과적 일 필요는 없습니다.

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]아무것도 닫을 것이 없다면.


"서비스와 통신 할 수있는 순수한 방법을 제공해야합니다."또는 그 반대 일 수도 있습니다. 순전히 내부 상태의 초기 구성은 효과가 필요하지 않지만 서비스에서 해당 변경 가능 상태와 상호 작용하는 모든 작업은 어떤 방법은 다음 effectful 표시 할 필요가있다 (피하기 사고가 좋아에 val neverRunningThisButStillMessingUpState = Task.pure(service.changeStateThinkingThisIsPure()).repeat(5))
틸로

또는 반대편에서 : 서비스를 효과적으로 만들 었는지 여부는 중요하지 않습니다. 그러나 어떤 방식 으로든, 어떤 방식 으로든 해당 서비스와 상호 작용하는 것은 효과적이어야합니다 (이 상호 작용의 영향을받는 내부에 변경 가능한 상태가 있기 때문에).
Thilo

1
@thilo 네, 맞습니다. 내가 의미하는 바 pure는 참조 적으로 투명해야한다는 것입니다. 예를 들어 미래를 예로 들어 봅시다. val x = Future {... }그리고 def x = Future { ... }다른 일을 의미한다. (이는 코드를 리팩토링 할 때 물릴 수 있습니다.) 그러나 cats-effect, monix 또는 zio의 경우는 아닙니다.
atl
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.