atnos-eff의 Eff.Eff로 addLast 시뮬레이션
개시하다
atnos-eff는 Scara에서 Eff(More Extensible Effects)를 처리하는 데 사용되는 설명서입니다.Eff는 Oleg과 석정의 논문에서 제기되었지만atnos-eff에서 본고에서 기술하지 않은 기능을 제공했다
Eff.addLast
.addLast
Eff 응용 해석기에 존재하고 마지막에 어떤 부작용을 수행한다.이 문서에서 먼저 addLast
의 운동을 설명하고 이어서 문제점을 서술한다.이후 논문에서 기술한 Eff의 기능 시뮬레이션addLast
을 활용해 기존addLast
문제를 해결하는 것을 목표로 한다.또 이 기사에 등장하는 코드의 일부를 지티허브에 공개했다.
addLast 및 기존 문제
addLast란?
위에서 말한 바와 같이atnos-eff는
Eff.addLast
이고 다음과 같은 인터페이스이다.def addLast(l: =>Eff[R, Unit]): Eff[R, A] =
addLast(Last.eff(l))
private[eff] def addLast(l: Last[R]): Eff[R, A]
Eff
는 세 가지 데이터 유형Pure
과 Impure
, 그리고 ImpureAp
를 ADT[1]로 한다.다음 필드last
가 각각 추가되어 있기 때문에 이렇게 사용할 수 있습니다addLast
.하지만ImpureAp
은 애플을 합성할 때 전용 처리(병행화 등)를 쓸 수 있도록 확장하기 위한 것이다.case class Pure[R, A](value: A, last: Last[R] = Last.none[R]) extends Eff[R, A] {
def addLast(l: Last[R]): Eff[R, A] =
Pure(value, last <* l)
}
case class Impure[R, X, A](union: Effect[R, X], continuation: Continuation[R, X, A], last: Last[R] = Last.none[R]) extends Eff[R, A] {
def addLast(l: Last[R]): Eff[R, A] =
Impure[R, X, A](union, continuation, last <* l)
}
case class ImpureAp[R, X, A](unions: Unions[R, X], continuation: Continuation[R, Vector[Any], A], last: Last[R] = Last.none[R]) extends Eff[R, A] {
def addLast(l: Last[R]): Eff[R, A] =
ImpureAp[R, X, A](unions, continuation, last <* l)
}
위에서 말한 바와 같이 모든 데이터last
의 필드가 존재하고 addLast
는 last
와 기존l
과 입력<*
을 결합한 형식으로 이루어진다.이productL
는 다음과 같다.def <*(last: Last[R]): Last[R] =
(value, last.value) match {
case (None, None) => this
case (Some(r), None) => this
case (None, Some(l)) => last
case (Some(r), Some(l)) => Last(Option(r.map2(l)((a, b) => b <* a)))
}
그리고 이런 방식으로 조합된<*
은 마지막으로last
의 함수Eff[NoFx, A] => A
로 실행된다.문제점
저자는 이용
Eff.run
이 큰 문제라고 생각한다.map2
의 설명에서도 아트노스-eff에서는 애플리카티브와 몬드의 경우 효율화 등 목적에 따라 다른 동작을 할 수 있다고 살짝 언급했다.예를 들어ImpureAp
의 행위를 감안하면 이해하기 쉽다.Future
가 되어 ma.flatMap(a => f(a))
에 의존하는 함수ma
를 집행하기 위해 집행f
이후 순차적으로 집행ma
f
, ma.map2(mb)((a, b) => f(a, b))
및 ma
는 결과와 무관하므로 2개mb
를 동시에 실행한 후 f
부터 flatMap
까지의 Applicative 실례는 자동으로 생성될 수 있지만 굳이 다른 실현을 실현함으로써 효율을 얻을 수 있다.예시map2
에서 보듯이 Scara에서는 이러한 Applicative에서 행동을 바꾸기 때문에 (실제로는 Monad 실례와 일치하지 않기 때문에) atnos-eff와 Applicative는 더욱 효율적인 다른 처리를 써서 지원할 수 있을 것이다.이 토론을 바탕으로 합성
Future
에 주목할 것이다.현재 합성된 코드는 addLast
인데 그 위에 모형을 더하면 다음과 같다.Last(Option(
(r: Eval[Eff[R, Unit]]).map2(l: Eval[Eff[R, Unit]])
((a: Eff[R, Unit], b: Eff[R, Unit]) => b <* a)
))
즉 두 개Last(Option(r.map2(l)((a, b) => b <* a)))
의 값Eff
과 a
을 통과b
(=<*
로 합성한다.결과적으로 b.map2(a){(b, _) => b}
는 Applicative 값으로 실행됩니다.이것은 해석기addLast
의 추가 순서와 실제 실행 순서가 완전히 일치하지 않기 때문이라고 할 수 있다.따라서 예를 들어 주 코드가 addLast
등에서 비동기적이어도 종결 처리를 순서대로 진행할 수 없다.atnos-eff-last-action
그래서 이번에는 Eff의 개조로 이런 행동에 대응하지 않았다.원본 코드는 다음 창고에 존재합니다.
sealed trait LastAction[A]
case class SideEffectLastAction(
value: Eval[Unit]
) extends LastAction[Unit]
type _last[R] = LastAction |= R
그리고 해석기는 다음과 같다.def runLast[U](implicit
m: Member.Aux[LastAction, R, U]
): Eff[U, A] =
Interpret
.runInterpreter(eff)(new Interpreter[LastAction, U, A, Eval[A]] {
override def onPure(a: A): Eff[U, Eval[A]] = {
Eff.pure(Eval.now(a))
}
override def onEffect[X](
x: LastAction[X],
continuation: Continuation[U, X, Eval[A]]
): Eff[U, Eval[A]] = {
x match {
case SideEffectLastAction(value) =>
continuation(())
.map { eval =>
value >> eval
}
}
}
override def onLastEffect[X](
x: LastAction[X],
continuation: Continuation[U, X, Unit]
): Eff[U, Unit] =
x match {
case SideEffectLastAction(value) =>
continuation(()).map(_ => value.value)
}
override def onApplicativeEffect[X, T[_] : Traverse](
xs: T[LastAction[X]],
continuation: Continuation[U, T[X], Eval[A]]
): Eff[U, Eval[A]] = {
continuation
.apply(xs.map { case SideEffectLastAction(_) => () })
.map { eval =>
xs.foldLeft(Eval.Unit) {
case (acc, SideEffectLastAction(value)) =>
value >> acc
} >> eval
}
}
})
.map(_.value)
간단하게 설명하면 다음과 같다.패턴 일치
Task
를 통해 GADTsSideEffectLastAction
의 유형LastAction[X]
을 X
으로 고정Unit
continuation
중 기동이 계속될 수 있음(): Unit
*>
))>>
가 아닌ma.flatMap(a => f(a).map(a))
결합 사용Eff.addLast
의 차이에 신경 쓰지 않고 Go 언어defer
처럼 상반된 순서로 실행하는 행동을 간단하게 할 수 있다.총결산
대체로 atnos-eff에 홍보를 보냈습니다.
아무래도
addLast
목표대로 움직이면 상황에 따라 스스로 건조기를 만들어도 해결되지 않아 포크 등이 필요해 번거롭다.다른 한편, 이렇게 해석기를 사용한 수법이라면 스스로 손만 대면 많은 행동을 바꿀 수 있기 때문에 더 좋다고 생각한다.각주
ImpureAp
원래의 논문에 나타나지 않았다.↩︎ Reference
이 문제에 관하여(atnos-eff의 Eff.Eff로 addLast 시뮬레이션), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://zenn.dev/yyu/articles/e58af48c003c13c6f41c텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)