Skip to content

Instantly share code, notes, and snippets.

@gakuzzzz
Created May 20, 2012 03:03
Show Gist options
  • Save gakuzzzz/2737698 to your computer and use it in GitHub Desktop.
Save gakuzzzz/2737698 to your computer and use it in GitHub Desktop.
中断可能なfold
sealed trait FoldResult[+A]
case class Continue[+A](a: A) extends FoldResult[A]
case class End[+A](a: A) extends FoldResult[A]
class LimitFoldable[+A](underlying: Traversable[A]) {
def limitFoldLeft[B](init: B)(f: (B, A) => FoldResult[B]): B = {
@tailrec
def fold(sentinel: B, rest: Traversable[A]): B = {
if (rest.isEmpty) return sentinel
f(sentinel, rest.head) match {
case End(e) => e
case Continue(e) => fold(e, rest.tail)
}
}
fold(init, underlying)
}
}
implicit def traversableToLimitFoldable[A](underlying: Traversable[A]) =
new LimitFoldable(underlying)
scala> List(1, 2, 3, 4, 5, 6, 7).limitFoldLeft(0) {
| case (a, b) => if (a > 10) End(a) else Continue(a+b)
| }
res0: Int = 15
scala> List(1, 2, 3, 4, 5, 6, 7).limitFoldLeft(0) {
| case (a, b) => if (a > 1000) End(a) else Continue(a+b)
| }
res1: Int = 28
@halcat0x15a
Copy link

scala> List(1, 2, 3, 4, 5, 6, 7).foldLeftM(0) { (a, b) =>
     |   (a > 10) ? (Free.pause >| a) | Free.return_(a |+| b)
     | }
res2: scalaz.Free.Trampoline[Int] = Gosub(Suspend(<function0>),<function1>)

scala> res2.run
res3: Int = 15

@gakuzzzz
Copy link
Author

👍

@halcat0x15a
Copy link

すみません、中断できていませんでした・・・・

scala> List(1, 2, 6, 3, 4, 5).foldLeftM(0) { (a, b) =>
     |   (b > 5) ? (Free.pause >| a) | Free.return_(a |+| b)
     | }
res11: scalaz.Free.Trampoline[Int] = Gosub(Suspend(<function0>),<function1>)

scala> res11.run
res12: Int = 15

@halcat0x15a
Copy link

scala> List(1, 2, 6, 3, 4, 5).foldLeftM[Free.Trampoline, Int](0) { (a, b) =>
     |   (b > 5).fold(Free.return_(a), Free.Return(a |+| b))
     | }.resume.left.map(_())
res15: Product with Serializable with Either[scalaz.Free[Function0,Int],Int] = Left(Gosub(Return(3),<function1>))

なにかできそうですが方法が見つかりません。

@halcat0x15a
Copy link

scala> List(1, 2, 6, 3, 4, 5).limitFoldLeft(0) { 
     |   case (a, b) => if (b > 5) End(a) else Continue(a+b)
     | }
res17: Int = 3

@halcat0x15a
Copy link

scala> List(1, 2, 6, 3, 4, 5).foldLeftM[({ type X[A] = Either[Int, A] })#X, Int](0) { (a, b) =>
     |   (b > 5).fold(a.left, (a |+| b).right)
     | }.merge
res41: Int = 3

@gakuzzzz
Copy link
Author

irb(main):020:0> [1, 2, 3, 4, 5, 6, 7].inject(0) do |a, b|
irb(main):021:1*   break a if a > 10
irb(main):022:1>   a + b
irb(main):023:1> end
=> 15
irb(main):024:0>
irb(main):022:0> [1, 2, 6, 3, 4, 5].inject do |a, b|
irb(main):023:1*   break a if b > 5
irb(main):024:1>   a + b
irb(main):025:1> end
=> 3
irb(main):026:0>

Ruby だと break や return 使える。下手すると予想だにしない動きしてバグの元になるけど。

@gakuzzzz
Copy link
Author

全然脱線なんだけど

List(1, 2, 6, 3, 4, 5).limitFoldLeft(0) { 
   case (a, b) => if (b > 5) End(a) else Continue(a+b)
}

こう書いた方がいいか

List(1, 2, 6, 3, 4, 5).limitFoldLeft(0) { 
   case (a, b) if b > 5 => End(a)
   case (a, b) => Continue(a+b)
}

こう書いた方がいいか地味に迷う。

@halcat0x15a
Copy link

確かにmatch式の中で分岐するのはもにょりますね。

@gakuzzzz
Copy link
Author

そうなんですよ。もにょもにょ

@rirakkumya
Copy link

保守性考えるとこっちですね。

List(1, 2, 6, 3, 4, 5).limitFoldLeft(0) { 
   case (a, b) if b > 5 => End(a)
   case (a, b) => Continue(a+b)
}

条件が簡単に追加できますし。

List(1, 2, 6, 3, 4, 5).limitFoldLeft(0) { 
   case (a, b) if a > 10 => End(a)
   case (a, b) if b > 5 => End(a)
   case (a, b) => Continue(a+b)
}

@gakuzzzz
Copy link
Author

なるほど。
ガード節だけ異なる場合はまとめた方が保守性考えると良さそうな気がします。

List(1, 2, 6, 3, 4, 5).limitFoldLeft(0) {
   case (a, b) if a > 10 || b > 5 => End(a)
   case (a, b) => Continue(a+b)
}

パターンが異なるならパターン分けた方が良さそうですね。

List(1, 2, 6, 3, 4, 5).limitFoldLeft(0) { 
   case (a, _) if a > 10 => End(a)
   case (a, b) if b > 5 => End(a)
   case (a, b) => Continue(a+b)
}

@xuwei-k
Copy link

xuwei-k commented May 21, 2012

"org.scalaz" % "scalaz-core_2.9.1" % "6.0.4"
scala> import scalaz._,Scalaz._
import scalaz._
import Scalaz._

scala> import Trampoline._
import Trampoline._

scala> List(1,2,6,3,4,5).foldLeftM(0){ (a, b) => (a > 5) ? (Free.pause >| a) | Free.return_(a + b)}
res0: scalaz.Free.Trampoline[Int] = Gosub(Suspend(<function0>),<function1>)

scala> res0.run
res1: Int = 9

scalaz6.0.4だと、Trampoline の monad の instance はデフォルトでは import されない らしくて、import Trampoline._ を加えたら、Trampoline でできた(?)

@gakuzzzz
Copy link
Author

おお、scalaz6 だとデフォルト import されてないんですね。

scala> List(1,2,6,3,4,5).foldLeftM(0){ (a, b) => (a > 5) ? (Free.pause >| a) | Free.return_(a + b)}
res0: scalaz.Free.Trampoline[Int] = Gosub(Suspend(<function0>),<function1>)

scala> res0.run
res1: Int = 9

scala> List(1,2,6,3,4,5).foldLeftM(0){ (a, b) => (b > 5) ? (Free.pause >| a) | Free.return_(a + b)}
res2: scalaz.Free.Trampoline[Int] = Gosub(Suspend(<function0>),<function1>)

scala> res2.run
res3: Int = 15

やっぱり中断ではなくて条件に合致する場合skipされてる感じですね。

@terazzo
Copy link

terazzo commented May 25, 2012

Continuationモナドを使うと良いと思うのですが、
cf: http://www.sampou.org/haskell/a-a-monads/html/contmonad.html

case class Cont[R, +A](runCont: (A => R) => R) {
  def flatMap[B](f: A => Cont[R, B]): Cont[R, B] = {
    Cont(k => this.runCont(a => f(a).runCont(k)))
  }
}
object Cont {
  def return_[R, A](a: A) = Cont[R, A](k => k(a))
  def callcc[R, A, B](f: (A => Cont[R, B]) => Cont[R, A]) =
    Cont[R, A](k => f(a => Cont(x => k(a))).runCont(k))
}

class LimitFoldable[+A](underlying: Traversable[A]) {
  def limitFoldLeft[B](init: B)(f: (B, A, B => Cont[B, B], B => Cont[B, B]) => Cont[B, B]): B = {
    def return_(b: B) = Cont.return_[B, B](b)
    def continue_(b: B) = return_(b)

    Cont.callcc[B, B, B](break_ =>  {
      underlying.foldLeft(return_(init))((cont: Cont[B, B], a: A) => {
        cont flatMap (b => f(b, a, continue_, break_))
      })
    }).runCont(b => b)
  }
}
implicit def traversableToLimitFoldable[A](underlying: Traversable[A]) =
  new LimitFoldable(underlying)
scala> List(1, 2, 3, 4, 5, 6, 7).limitFoldLeft(0)((b, a, continue_, break_) =>  
     |   if (a > 5) break_(b) else continue_(a + b)
     | )
res1: Int = 15

scala> List(1, 2, 3, 6, 4, 5, 6, 7).limitFoldLeft(0)((b, a, continue_, break_) =>  
     |   if (a > 5) break_(b) else continue_(a + b)
     | )
res2: Int = 6

このままだとスタックオーバーフローするのでTrampolineしたいのですが、
噂によるとContinuationモナドはFreeで書けるらしいので、最初からFreeで書けばすべて解決しそうな気も。
Freeを理解しようとしたが圏力が及ばなかった……

@gakuzzzz
Copy link
Author

なるほど継続モナド!まさしくこの用途にぴったりですね。
ってことは限定継続つかえばスタックオーバーフローの問題も解決できるんですかね?

ContinuationモナドとFreeモナドの関係は僕もぜんぜん理解できてませんorz
というか僕の継続モナドの理解も怪しい気がしてきました><
このコードじっくり読んで勉強させてもらいます!ありがとうございます!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment