Skip to content

Instantly share code, notes, and snippets.

@windymelt
Created October 29, 2022 08:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save windymelt/36c18a81c583d9be729e9b646750cc23 to your computer and use it in GitHub Desktop.
Save windymelt/36c18a81c583d9be729e9b646750cc23 to your computer and use it in GitHub Desktop.
#!/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