Created
October 29, 2022 08:21
-
-
Save windymelt/36c18a81c583d9be729e9b646750cc23 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env amm | |
import $ivy.`org.typelevel::cats-effect:3.3.14` | |
import cats.effect.{IO, IOApp, Resource, Ref} | |
import cats.effect.std.{Hotswap, Random} | |
import cats.implicits._ | |
// トイレットペーパーのある一定の部分 | |
case class PaperPiece(offset: Int) extends AnyVal | |
/** トイレットペーパー | |
* | |
* すべて使った後は適切に芯を捨てなければならない | |
*/ | |
case class ToiletPaper(val initialLasting: Int = 30) { | |
private var lasting_ = initialLasting | |
def lasting = lasting_ | |
def dispose(): Unit = { | |
println("Disposing toilet paper") | |
} | |
def pull(): PaperPiece = { | |
if (lasting == 0) { | |
// ペーパーが切れると大変なことになる | |
throw new Exception("Toilet apocalypse!!!!!!!!!!") | |
} | |
val piece = PaperPiece(initialLasting - lasting) | |
lasting_ = lasting_ - 1 | |
piece | |
} | |
def status: String = ("*" * lasting) ++ ("_" * (initialLasting - lasting)) | |
} | |
// Hotswap | |
// リソースの中で次のリソースを安全に作るための仕組み。ローテーションが必要なリソースなどで効力を発揮する。 | |
// ふつう、Resourceの中でResourceを作ると、スコープを抜けて閉じるまでは2つとも開きっぱなしになってしまうのでメモリがリークし続けることになる。 | |
// そこで、Resourceを交換するためのハンドラであるHotswapを注入するための仕組みがHotswapである。 | |
// Hotswapするハンドラ自体もResourceの性質を持つので、Resourceとして提供される。 | |
// Resourceのスコープでswapすると、使用中のResourceがfinalizeされ、同時に新たなResourceで差し替えることができる。 | |
// swapは、差し替えられたリソースの中身を返すので、Refと組み合わせて使う。 | |
val acquireToiletPaperIO = IO.println("\nAcquiring toilet paper") *> IO(ToiletPaper()) | |
val disposeToiletPaperIO = (old: ToiletPaper) => IO(old.dispose()) | |
val toiletPaperResource: Resource[IO, ToiletPaper] = Resource.make(acquireToiletPaperIO)(disposeToiletPaperIO) | |
type HotswapPair = (Hotswap[IO, ToiletPaper], ToiletPaper) | |
type ResourceForPaperPiece = Resource[IO, () => IO[PaperPiece]] | |
val automatedPaper0: HotswapPair => ResourceForPaperPiece = { | |
case (hotswap, initialPaper) => | |
Resource.eval { | |
for { | |
// 内部的にペーパーの管理にRefを使う | |
paperHolder <- Ref[IO].of(initialPaper) | |
// 新たなToiletPaperを調達してRefに入れる処理をワンセットで定義し、 | |
// 既に捨てたペーパーに対して操作することを防ぐ | |
swap <- IO.pure { (newPaperResource: Resource[IO, ToiletPaper]) => | |
for { | |
// 新たなペーパーの入手と古いペーパーの解放を行う | |
newPaper <- hotswap.swap(newPaperResource) | |
// Refにセットする | |
_ <- paperHolder.set(newPaper) | |
} yield () } | |
} yield () => // 唯一ユーザが操作できる0-引数関数を返す | |
for { | |
// 紙を取り出し、必要に応じてswapを呼び出し、PaperPieceを返す | |
piece <- paperHolder.modify { p => (p, p.pull()) } | |
roll <- paperHolder.get | |
_ <- IO.print(s"\r${roll.status}") | |
_ <- if (roll.lasting == 0) swap(toiletPaperResource) else IO.unit | |
} yield piece | |
} | |
} | |
val automatedPaper = Hotswap(toiletPaperResource) >>= automatedPaper0 | |
object Main extends IOApp.Simple { | |
import scala.concurrent.duration._ | |
import scala.language.postfixOps | |
def run = automatedPaper.use { pullPaper => | |
{ | |
for { | |
_ <- pullPaper() // 紙を引くことだけができる | |
r <- Random.scalaUtilRandom[IO] | |
wait <- r.nextGaussian map (_.abs * 50) | |
_ <- IO.sleep(wait milliseconds) | |
} yield () | |
}.foreverM | |
} | |
} | |
@main def main() = { | |
Main.main(Array.empty[String]) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment