Scala Cats / fs2에서 스택 안전에 대해 추론하는 방법은 무엇입니까?


13

다음은 fs2 설명서의 일부 코드입니다 . 이 함수 go는 재귀 적입니다. 문제는 스택 안전인지 어떻게 알 수 있으며 어떤 함수가 스택 안전인지 판단하는 방법입니다.

import fs2._
// import fs2._

def tk[F[_],O](n: Long): Pipe[F,O,O] = {
  def go(s: Stream[F,O], n: Long): Pull[F,O,Unit] = {
    s.pull.uncons.flatMap {
      case Some((hd,tl)) =>
        hd.size match {
          case m if m <= n => Pull.output(hd) >> go(tl, n - m)
          case m => Pull.output(hd.take(n.toInt)) >> Pull.done
        }
      case None => Pull.done
    }
  }
  in => go(in,n).stream
}
// tk: [F[_], O](n: Long)fs2.Pipe[F,O,O]

Stream(1,2,3,4).through(tk(2)).toList
// res33: List[Int] = List(1, 2)

go다른 방법으로 호출하면 스택 안전 합니까?

def tk[F[_],O](n: Long): Pipe[F,O,O] = {
  def go(s: Stream[F,O], n: Long): Pull[F,O,Unit] = {
    s.pull.uncons.flatMap {
      case Some((hd,tl)) =>
        hd.size match {
          case m if m <= n => otherMethod(...)
          case m => Pull.output(hd.take(n.toInt)) >> Pull.done
        }
      case None => Pull.done
    }
  }

  def otherMethod(...) = {
    Pull.output(hd) >> go(tl, n - m)
  }

  in => go(in,n).stream
}

아뇨 이것이 꼬리 재귀의 경우에 알려주십시오. 그러나 그렇지 않은 것 같습니다. 내가 아는 한 고양이는 스택 안전을 보장하기 위해 트램펄린이라고하는 마술을합니다. 불행히도 함수가 언제 트램폴린인지 아닌지를 알 수 없습니다.
Lev Denisov

go예를 들어 Monad[F]typeclass 를 사용하도록 다시 작성할 수 있습니다 tailRecM. 함수가 스택 안전을 보장하기 위해 명시 적으로 트램펄린을 수행 할 수있는 방법이 있습니다. 나는 틀렸을 수도 있지만 F스택없이 안전하게 (예 : 내부적으로 트램펄린을 구현하는 경우) 의존하고 있지만 누가 정의해야하는지 알지 F못하므로 그렇게해서는 안됩니다. F스택 안전하다고 보장 할 수없는 경우 tailRecM법에 의해 스택 안전하므로 제공하는 유형 클래스를 사용하십시오 .
Mateusz Kubuszok

1
컴파일러 @tailrec가 tail rec 함수에 대한 주석으로 쉽게 증명할 수 있습니다. 다른 경우 Scala AFAIK에는 공식적인 보증이 없습니다. 함수 자체가 안전하더라도 호출하는 다른 함수는 : /가 아닐 수 있습니다.
yǝsʞǝla

답변:


17

내 이전의 대답은 여기에 도움이 될 수있는 몇 가지 배경 정보를 제공합니다. 기본 아이디어는 일부 효과 유형에 flatMap스택 안전 재귀를 직접 지원하는 구현이 있다는 flatMap것입니다. 명시 적으로 또는 재귀를 통해 원하는만큼 깊이 호출 을 중첩 할 수 있으며 스택에 오버플로가 발생하지 않습니다.

일부 효과 유형 flatMap의 경우 효과의 의미로 인해 스택 안전을 유지할 수 없습니다 . 다른 경우에는 스택 세이프를 작성하는 것이 가능할 수 flatMap있지만 구현자는 성능 또는 기타 고려 사항으로 인해하지 않기로 결정했을 수 있습니다.

불행히도 flatMap주어진 유형에 대한 스택 안전 여부를 알 수있는 표준 (또는 기존의) 방법 은 없습니다. Cats에는 tailRecM모든 합법적 인 모나 딕 효과 유형에 대해 스택 안전 모나 딕 재귀를 제공해야하는 작업 이 포함되며 , 때로는 tailRecM합법적 인 것으로 알려진 구현을 보면 flatMap스택이 안전한지 여부에 대한 힌트를 얻을 수 있습니다. 이 경우 Pull다음과 같습니다 .

def tailRecM[A, B](a: A)(f: A => Pull[F, O, Either[A, B]]) =
  f(a).flatMap {
    case Left(a)  => tailRecM(a)(f)
    case Right(b) => Pull.pure(b)
  }

tailRecM단지를 통해 재귀되고 flatMap, 우리는 알고 PullMonad인스턴스가 합법적 이 꽤 좋은 증거이다, Pull의 ' flatMapIS 스택 안전가. 여기 요인을 복잡 하나는의 인스턴스가 있다는 것입니다 PullApplicativeError에 제약 FPullflatMap하지 않습니다,하지만 변경 아무것도하지 않는이 경우이다.

그렇게 tk때문에 여기에 구현 스택 안전 flatMapPull스택 안전, 우리는에서의보고 알고 tailRecM구현입니다. (우리는 더 깊은 조금 파고 경우 우리가 알아낼 수 flatMap있기 때문에 스택 - 안전 Pull본질적에 대한 래퍼입니다 FreeC되어, trampolined .)

아마 다시 작성하는 것은 매우 어렵지 않을 것입니다 tk측면에서 tailRecM우리는 그렇지 않으면 불필요한 추가해야 할 것입니다 있지만, ApplicativeError제약. 나는이 문서의 저자를 추측하고있어 명확성을 위해 그렇게하지 않기로하고, 그들이 알고 있기 때문에 Pull'이야 flatMap괜찮습니다.


업데이트 : 다음은 상당히 기계적인 tailRecM번역입니다.

import cats.ApplicativeError
import fs2._

def tk[F[_], O](n: Long)(implicit F: ApplicativeError[F, Throwable]): Pipe[F, O, O] =
  in => Pull.syncInstance[F, O].tailRecM((in, n)) {
    case (s, n) => s.pull.uncons.flatMap {
      case Some((hd, tl)) =>
        hd.size match {
          case m if m <= n => Pull.output(hd).as(Left((tl, n - m)))
          case m => Pull.output(hd.take(n.toInt)).as(Right(()))
        }
      case None => Pull.pure(Right(()))
    }
  }.stream

명시적인 재귀는 없습니다.


두 번째 질문에 대한 대답은 다른 방법의 모양에 따라 다르지만 특정 예제의 경우 >>더 많은 flatMap레이어가 생성되므로 괜찮을 것입니다.

더 일반적으로 귀하의 질문을 해결하기 위해이 전체 주제는 스칼라에서 혼란스러운 혼란입니다. 타입이 스택 세이프 모나드 재귀를 지원하는지 아닌지를 알기 위해 위에서 한 것처럼 구현을 파헤칠 필요가 없습니다. 문서에 관한 더 나은 규칙은 여기에 도움이 될 것입니다. 그러나 불행히도 우리는 그 일을 잘하지 않습니다. 당신은 항상 tailRecM"안전한"( F[_]어쨌든 일반적 일 때하고 싶은 것)로 사용할 수 있지만, 심지어 Monad구현이 합법적이라고 믿습니다.

요약하면 : 그것은 주위의 나쁜 상황이며 민감한 상황에서는 이와 같은 구현이 스택 안전인지 확인하기 위해 자체 테스트를 작성해야합니다.


설명 감사합니다. go다른 메소드에서 호출 할 때의 질문과 관련하여 스택을 안전하지 않게 만들 수있는 것은 무엇입니까? 호출하기 전에 비 재귀 계산을 수행 Pull.output(hd) >> go(tl, n - m)하면 괜찮습니까?
Lev Denisov

그렇습니다. 물론 계산 자체가 스택을 오버플로하지 않는다고 가정하면 좋습니다.
트래비스 브라운

예를 들어, 어떤 효과 유형이 모나 딕 재귀에 대해 스택 안전하지 않습니까? 연속 타입?
bob

고양이의 있지만, 마우스 오른쪽을 @bob ContT의가 flatMap 있다 (A를 통해 실제로 스택 안전 Defer기본 유형에 대한 제약). 나는을 List통해 재귀 flatMap가 스택 안전하지 않은 곳에서 더 많은 것을 생각하고있었습니다 ( tailRecM그러나 합법적입니다 ).
트래비스 브라운
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.