Scala Cats / fs2'de yığın güvenliği ile ilgili nedenler?


13

İşte fs2 belgelerinden bir parça kod . İşlev goözyinelemeli. Soru, yığının güvenli olup olmadığını nasıl bilebiliriz ve herhangi bir fonksiyonun yığın güvenli olup olmadığını nasıl anlayabiliriz?

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)

goBaşka bir yöntemden çağırırsak da yığın güvenli olur mu?

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
}

Hayır, tam olarak değil. Her ne kadar bu kuyruk özyineleme durumuysa, lütfen söyleyin, ama öyle görünmüyor. Bildiğim kadarıyla kediler yığın güvenliğini sağlamak için trambolin adı verilen bir sihir yapıyor. Ne yazık ki bir fonksiyonun ne zaman tramboline edildiğini ve ne zaman tramplen olmadığını söyleyemem.
Lev Denisov

goÖrneğin Monad[F]typeclass'ı kullanmak için yeniden yazabilirsiniz - tailRecMişlevin istif güvenli olacağından emin olmak için trambolini açıkça gerçekleştirmenizi sağlayan bir yöntem vardır . Yanılıyor olabilirim ama onsuz Fkendi başına güvenli bir şekilde istiflenmeye güveniyorsunuz (örneğin, trambolini dahili olarak uygularsa), ancak kiminizi tanımlayacağını asla bilemezsiniz F, bu yüzden bunu yapmamalısınız. FYığın güvenliği için bir garantiniz yoksa, tailRecMyasalar tarafından yığın açısından güvenli olduğu için bir tür sınıfı kullanın .
Mateusz Kubuszok

1
Derleyicinin @tailreckuyruk kayıt işlevleri için ek açıklama ile kanıtlamasına izin vermek kolaydır . Diğer durumlar için Scala AFAIK'te resmi bir garanti yoktur. Fonksiyonun kendisi güvenli olsa bile, çağırdığı diğer fonksiyonlar olmayabilir: /.
yǝsʞǝla

Yanıtlar:


17

Buradaki önceki cevabım faydalı olabilecek bazı arka plan bilgileri veriyor. Temel fikir, bazı efekt türlerinin flatMapdoğrudan yığın güvenli özyinelemeyi destekleyen uygulamalara sahip olmasıdır;flatMap çağrıları açıkça veya özyineleme yoluyla istediğiniz kadar derinlemesine iç ve yığının üzerine taşmazsınız.

Bazı efekt türlerinde flatMap, efektin anlambilimi nedeniyle yığın güvenli olmak mümkün değildir . Diğer durumlarda, bir yığın kasası yazmak mümkün olabilir flatMap, ancak uygulayıcılar performans veya diğer hususlar nedeniyle olmamasına karar vermiş olabilir.

Ne yazık ki, flatMapbelirli bir tür için yığın güvenli olup olmadığını bilmenin standart (hatta geleneksel) bir yolu yoktur . Kediler, tailRecMherhangi bir yasal monadik etki türü için yığın güvenli monadik özyineleme sağlaması gereken bir işlem içerir ve bazen tailRecMyasal olduğu bilinen bir uygulamaya bakmak a'nın flatMapyığın güvenli olup olmadığı hakkında bazı ipuçları verebilir . Durumunda PullÖyle görünüyor bu :

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

Bu tailRecMsadece içinden recursing edilir flatMapve biz biliyoruz Pull'ın Monadörneği yasal olduğunu bu oldukça iyi bir kanıt olan Pulls' flatMapisimli yığını güvenli. Burada faktörünü komplike bir için örnek olmasıdır Pullbir sahiptir ApplicativeErrorüzerine kısıtlamayı Fo Pull'ın flatMapyaptığı değil, ama değişim şey yapmaz bu durumda.

Bu yüzden, buradaki tkuygulama yığın güvenlidir, çünkü flatMapon Pullyığın güvenlidir ve bunun tailRecMuygulanmasına bakmasını biliyoruz . (Biraz daha derine inersek flatMap, yığın için güvenli olduğunu anlayabiliriz , çünkü Pullesas FreeColarak tramplenli bir sarıcıdır .)

Aksi halde gereksiz kısıtlamayı eklememiz gerekse de, muhtemelen tkaçısından yeniden yazmak çok zor olmayacaktır . Ben belgelerin yazarları tahmin ediyorum netlik için bunu seçtim ve onlar bildikleri için 's gayet iyi.tailRecMApplicativeErrorPullflatMap


Güncelleme: işte oldukça mekanik bir tailRecMçeviri:

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

Açık bir özyineleme olmadığını unutmayın.


İkinci sorunuzun cevabı, diğer yöntemin neye benzediğine bağlıdır, ancak özel örneğiniz durumunda, >>sadece daha fazla flatMapkatmanla sonuçlanacaktır , bu yüzden iyi olmalıdır.

Sorunuzu daha genel olarak ele almak için, bu konunun tamamı Scala'da kafa karıştırıcı bir karmaşa. Sadece bir türün yığın güvenli monadik özyinelemeyi destekleyip desteklemediğini bilmek için yukarıda yaptığımız gibi uygulamaları incelemeniz gerekmez. Belgelerle ilgili daha iyi sözleşmeler burada yardımcı olacaktır, ancak maalesef çok iyi bir iş çıkarmıyoruz. Her zaman tailRecM"güvenli" olarak kullanabilirsiniz ( F[_]zaten genel olduğunda yapmak isteyeceğiniz şey budur), ancak o zaman bile Monaduygulamanın yasal olduğuna güveniyorsunuz .

Özetlemek gerekirse: bu kötü bir durumdur ve hassas durumlarda bu tür uygulamaların yığın güvenli olduğunu doğrulamak için kesinlikle kendi testlerinizi yazmalısınız.


Açıklama için teşekkürler. goBaşka bir yöntemden aradığımızda soru ile ilgili olarak, güvensiz yığını ne yapabilir? Aramadan önce özyinelemeli olmayan hesaplamalar yaparsak Pull.output(hd) >> go(tl, n - m)sorun olmaz mı?
Lev Denisov

Evet, bu iyi olmalı (hesaplamanın kendisinin elbette yığını taşmadığı varsayılarak).
Travis Brown

Örneğin, hangi efekt türü monadik özyineleme için yığın güvenli olmaz? Devam tipi?
bob

Kediler en rağmen, Right @bob ContT'ın flatMap olduğu (a aracılığı aslında yığın güvenli Deferyatan tipine kısıtlaması). Ben daha fazla gibi bir şey düşünüyordum List, nerede üzerinden yineleme flatMapyığın güvenli değildir (olsa da, bir yasal var tailRecM).
Travis Brown
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.