Scalaz(18)-Monad:Reader WriterState-は簡単なプログラミング言語であることができる
17044 ワード
FPといえば、すぐにMonadを連想します.Monadの代表関数flatMapは2つの演算F[A],F[B]を連続させることができ,プログラムの意味でシリアル型のフロー(workflow)を形成することができると述べた.もっと率直な言い方は、flatMapが実現すればfor-comprehension、for{...}yield.ここにいるよでは、OOPのようにプログラムを書くことができます.このforは演算モードで、for{...}に規範化されています.にコマンドを入れる行為.私たちはOOPスタイルからFPプログラミングモードに入っています.最も基本的なFPプログラミングモードがOOPプログラミングスタイルの文法と思考に沿っていることを望んでいます.Monadは最適な汎関数データ型であるはずです.まず基本的なことから始めましょう.もし行令プログラムがあれば、
これらの関数e 1,e 2,e 3により最後にd値が算出される.FPスタイルでこのプログラムを作るなら、まず関数の結果dをF[d]のFに入れなければなりません.Fは前述の演算モードであり,ここではよく知られているcontext(コンテキスト)で表すことができる.FはMonadでなければなりません.F[]はfor{...}に相当します.yield.まずIdを試してみましょう.Id[A]はAに対して何の処理もせず、直接戻ってくるので意味がないようですが、このタイプはmapとflatMapを備えており、for-comprehensionを使うことができるはずです.
for-loopではOOPの命令プログラムであることがわかります.しかし、このIdに意味がないと感じたら、Optionを試してみてください.
ほら、シェル(context)を変えましたが、for-loopのプログラムは変わりません.言い換えればfor-loopのプログラムは小包のcontextを全然気にしない.
ReaderもMonadですが、それを使うとどうなりますか.
文法的には下手だが、for-loopのプログラムが外のcontextを気にしないことを証明した.では、このprgは簡単なFPプログラミング言語だと言えるでしょうか.演算結果をcontextに入れ、あるinterpreterを実行してから実際の演算値を取得するまで(run(10)で22を得る).もちろん、あるプログラムでは、その演算行為が単一のタイプのcontextに制約されているのは少し弱いかもしれません.利用可能なFPプログラミング言語を得る必要がある場合は、単一のタイプのcontextを複数のタイプの混合contextに組み合わせる方法を検討するかもしれません.
scalazにはReader,WriterT,StateTなどのTで終わるtype classの名前があることが分かった.このTは変形器Transformerを指し,これを用いてcontextを積み上げることができることを意味する.StateTを見てみると、簡単な定義は次のようになります.
私たちはF類をStateに積み上げることができます.このFがflatMapを実現すれば,積層されたタイプもflatMapを実現できることが実証された.はい、scalazのOptionはflatMapを実現していますが、Stateと積み上げてもらえますか?積み重ねたcontextはどんな効果がありますか?まず、contextとしての単一OptionとStateの効果を見てみましょう.
私から見れば、Optionの主な効果はNone値に遭遇したときにすぐに終了することです.Stateの主な役割は、演算と同時に1つの状態を維持することです.では、OptionとStateを重ねると、この2つのタイプの特徴が同時に備わっているのではないでしょうか.すなわち,状態を維持できるとともにNone値に遭遇したときに直ちに演算を終了して終了する.まず、OptionのflatMapを用いてcontextを重畳するflatMapを実現することを検証する.
はい、OptionのmapとflatMapでOptionStateのmapとflatMapを実現できます.もちろん、for-comprehensionでOptionとStateを同時に使用するには、OptionStateタイプに昇格する必要があります.
重ね合わせ効果のfor-comprehensionを試してみましょう.
見て、状態を維持することもできるし、None処理メカニズムも備えている.
はい、scalazにはReaderWriterStateというtype classがあります.Reader+Writer+Stateが積み上げられたMonadです.scalazは特にこのようなtype classを提供していると信じています.その意図があるはずです.私の推測では、このMonadは機能が比較的完全な組み合わせのMonadです.for-comprehensionのcontextとしては比較的包括的な効果を提供できるはずです.意味的に解釈すると、それによって形成されたMonadicプログラミング言語では、演算(compute)、追跡(logging)、状態維持機能を同時に提供することができる.基礎タイプはIndexedReader WriterStateT:scalaz/packageです.scala
Reader,Writer,Stateのデザインを分けて比較分析すると:
では、以上の3つを組み合わせると、そのデザインはこうなるでしょう.
入力されたタイプと返されたタイプは一致します.scalazではscalaz/Reader WriterStateTと定義されています.scala
IndexedReader WriterStateTは,eval,execなど多くのIndexedStateTの演算方法を実現していることを見た.mapとflatMapがどのように実現されているかを見てみましょう.
私たちが前に行ったOptionStateの例と同じように、FがmapとflatMapを実現できれば、IndexedReader WriterStateTはmapとflatMapを実現することができます.In dexedReader WriterStateTは、for-loopで各コマンドがliftを使用してタイプを昇格させることを省くために、ほとんどの操作関数を再実現しました.
このReader WriterStateを使用して、通信ポートの使用プログラムをシミュレートし、使用状況を記録するプログラムを作成します.まずポート番号を入力します.プログラムで使用するポート番号をリセットできます.
これは高級言語で書かれたプログラムのようです.詳細はいくつかの機能関数にあります.Reader WriterStateタイプを返す必要があります.
/*
val a = e1
val b = e2(a)
val c = e3(a,b)
val d = e2(c)
*/
これらの関数e 1,e 2,e 3により最後にd値が算出される.FPスタイルでこのプログラムを作るなら、まず関数の結果dをF[d]のFに入れなければなりません.Fは前述の演算モードであり,ここではよく知られているcontext(コンテキスト)で表すことができる.FはMonadでなければなりません.F[]はfor{...}に相当します.yield.まずIdを試してみましょう.Id[A]はAに対して何の処理もせず、直接戻ってくるので意味がないようですが、このタイプはmapとflatMapを備えており、for-comprehensionを使うことができるはずです.
import scalaz._
import Scalaz._
def e1:Id[Int] = 10 //> e1: => scalaz.Scalaz.Id[Int]
def e2(a: Int): Id[Int] = a + 1 //> e2: (a: Int)scalaz.Scalaz.Id[Int]
def e3(a: Int, b: Int): Id[Int] = a + b //> e3: (a: Int, b: Int)scalaz.Scalaz.Id[Int]
for {
a <- e1
b <- e2(a)
c <- e3(a,b)
d <- e2(c)
} yield d //> res0: scalaz.Scalaz.Id[Int] = 22
for-loopではOOPの命令プログラムであることがわかります.しかし、このIdに意味がないと感じたら、Optionを試してみてください.
import scalaz._
import Scalaz._
def e1:Option[Int] = 10.some //> e1: => Option[Int]
def e2(a: Int): Option[Int] = (a + 1).some //> e2: (a: Int)Option[Int]
def e3(a: Int, b: Int): Option[Int] = (a + b).some//> e3: (a: Int, b: Int)Option[Int]
for {
a <- e1
b <- e2(a)
c <- e3(a,b)
d <- e2(c)
} yield d //> res0: Option[Int] = Some(22)
ほら、シェル(context)を変えましたが、for-loopのプログラムは変わりません.言い換えればfor-loopのプログラムは小包のcontextを全然気にしない.
ReaderもMonadですが、それを使うとどうなりますか.
import scalaz._
import Scalaz._
def e1:Reader[Int,Int] = Reader[Int,Int](a => a) //> e1: => scalaz.Reader[Int,Int]
def e2(a: Int): Reader[Int,Int] = Reader[Int,Int](_ => a + 1)
//> e2: (a: Int)scalaz.Reader[Int,Int]
def e3(a: Int, b: Int): Reader[Int, Int] = Reader[Int,Int](_ => a+b)
//> e3: (a: Int, b: Int)scalaz.Reader[Int,Int]
val prg = for {
a <- e1
b <- e2(a)
c <- e3(a,b)
d <- e2(c)
} yield d //> prg : scalaz.Kleisli[scalaz.Id.Id,Int,Int] = Kleisli(<function1>)
prg.run(10) //> res0: scalaz.Id.Id[Int] = 22
文法的には下手だが、for-loopのプログラムが外のcontextを気にしないことを証明した.では、このprgは簡単なFPプログラミング言語だと言えるでしょうか.演算結果をcontextに入れ、あるinterpreterを実行してから実際の演算値を取得するまで(run(10)で22を得る).もちろん、あるプログラムでは、その演算行為が単一のタイプのcontextに制約されているのは少し弱いかもしれません.利用可能なFPプログラミング言語を得る必要がある場合は、単一のタイプのcontextを複数のタイプの混合contextに組み合わせる方法を検討するかもしれません.
scalazにはReader,WriterT,StateTなどのTで終わるtype classの名前があることが分かった.このTは変形器Transformerを指し,これを用いてcontextを積み上げることができることを意味する.StateTを見てみると、簡単な定義は次のようになります.
case class StateT[F[_],S,A](run: S => F[(S,A)])
私たちはF類をStateに積み上げることができます.このFがflatMapを実現すれば,積層されたタイプもflatMapを実現できることが実証された.はい、scalazのOptionはflatMapを実現していますが、Stateと積み上げてもらえますか?積み重ねたcontextはどんな効果がありますか?まず、contextとしての単一OptionとStateの効果を見てみましょう.
for {
a <- 3.some
b <- (None: Option[Int])
c <- 4.some
} yield c //> res1: Option[Int] = None
val statePrg = for {
a <- get[Int]
b <- State[Int,Int](s => (s, s + a))
_ <- put(9)
} yield b //> statePrg : scalaz.IndexedStateT[scalaz.Id.Id,Int,Int,Int] = scalaz.IndexedS
//| tateT$$anon$10@15ff3e9e
statePrg.run(3) //> res2: scalaz.Id.Id[(Int, Int)] = (9,6)
私から見れば、Optionの主な効果はNone値に遭遇したときにすぐに終了することです.Stateの主な役割は、演算と同時に1つの状態を維持することです.では、OptionとStateを重ねると、この2つのタイプの特徴が同時に備わっているのではないでしょうか.すなわち,状態を維持できるとともにNone値に遭遇したときに直ちに演算を終了して終了する.まず、OptionのflatMapを用いてcontextを重畳するflatMapを実現することを検証する.
case class OptionState[S,A](run: S => Option[(S,A)]) {
def map[B](f: A => B): OptionState[S,B] =
OptionState {
s => run(s) map { case (s1,a1) => (s1,f(a1)) }
}
def flatMap[B](f: A => OptionState[S,B]): OptionState[S,B] =
OptionState {
s => run(s) flatMap { case (s1,a1) => f(a1).run(s1) }
}
}
はい、OptionのmapとflatMapでOptionStateのmapとflatMapを実現できます.もちろん、for-comprehensionでOptionとStateを同時に使用するには、OptionStateタイプに昇格する必要があります.
def liftOption[S,A](oa: Option[A]): OptionState[S,A] = oa match {
case Some(a) => OptionState {s => (s,a).some }
case None => OptionState {_ => none}
}
def liftState[S,A](sa: State[S,A]): OptionState[S,A] =
OptionState {s => sa(s).some}
重ね合わせ効果のfor-comprehensionを試してみましょう.
val osprg: OptionState[Int,Int] = for {
a <- liftOption(3.some)
b <- liftState(put(a))
c <- liftState(get[Int])
d <- liftState(State[Int,Int](s => (s+c, s+a)))
} yield c //> osprg : Exercises.rws.OptionState[Int,Int] = OptionState(<function1>)
osprg.run(2) //> res3: Option[(Int, Int)] = Some((6,3))
val osprg1: OptionState[Int,Int] = for {
a <- liftOption(3.some)
b <- liftState(put(a))
_ <- liftOption((None: Option[Int]))
c <- liftState(get[Int])
d <- liftState(State[Int,Int](s => (s+c, s+a)))
} yield c //> osprg1 : Exercises.rws.OptionState[Int,Int] = OptionState(<function1>)
osprg1.run(2) //> res4: Option[(Int, Int)] = None
見て、状態を維持することもできるし、None処理メカニズムも備えている.
はい、scalazにはReaderWriterStateというtype classがあります.Reader+Writer+Stateが積み上げられたMonadです.scalazは特にこのようなtype classを提供していると信じています.その意図があるはずです.私の推測では、このMonadは機能が比較的完全な組み合わせのMonadです.for-comprehensionのcontextとしては比較的包括的な効果を提供できるはずです.意味的に解釈すると、それによって形成されたMonadicプログラミング言語では、演算(compute)、追跡(logging)、状態維持機能を同時に提供することができる.基礎タイプはIndexedReader WriterStateT:scalaz/packageです.scala
type ReaderWriterStateT[F[_], -R, W, S, A] = IndexedReaderWriterStateT[F, R, W, S, S, A]
object ReaderWriterStateT extends ReaderWriterStateTInstances with ReaderWriterStateTFunctions {
def apply[F[_], R, W, S, A](f: (R, S) => F[(W, A, S)]): ReaderWriterStateT[F, R, W, S, A] = IndexedReaderWriterStateT[F, R, W, S, S, A] { (r: R, s: S) => f(r, s) }
}
type IndexedReaderWriterState[-R, W, -S1, S2, A] = IndexedReaderWriterStateT[Id, R, W, S1, S2, A]
object IndexedReaderWriterState extends ReaderWriterStateTInstances with ReaderWriterStateTFunctions {
def apply[R, W, S1, S2, A](f: (R, S1) => (W, A, S2)): IndexedReaderWriterState[R, W, S1, S2, A] = IndexedReaderWriterStateT[Id, R, W, S1, S2, A] { (r: R, s: S1) => f(r, s) }
}
type ReaderWriterState[-R, W, S, A] = ReaderWriterStateT[Id, R, W, S, A]
object ReaderWriterState extends ReaderWriterStateTInstances with ReaderWriterStateTFunctions {
def apply[R, W, S, A](f: (R, S) => (W, A, S)): ReaderWriterState[R, W, S, A] = IndexedReaderWriterStateT[Id, R, W, S, S, A] { (r: R, s: S) => f(r, s) }
}
type IRWST[F[_], -R, W, -S1, S2, A] = IndexedReaderWriterStateT[F, R, W, S1, S2, A]
val IRWST: IndexedReaderWriterStateT.type = IndexedReaderWriterStateT
type IRWS[-R, W, -S1, S2, A] = IndexedReaderWriterState[R, W, S1, S2, A]
val IRWS: IndexedReaderWriterState.type = IndexedReaderWriterState
type RWST[F[_], -R, W, S, A] = ReaderWriterStateT[F, R, W, S, A]
val RWST: ReaderWriterStateT.type = ReaderWriterStateT
type RWS[-R, W, S, A] = ReaderWriterState[R, W, S, A]
val RWS: ReaderWriterState.type = ReaderWriterState
Reader,Writer,Stateのデザインを分けて比較分析すると:
case class Reader[R, A](f: R => A) // R, A R
case class Writer[W, A](w: (W, A)) // W,A
case class State[S, A](f: S => (A, S)) // S, A S
では、以上の3つを組み合わせると、そのデザインはこうなるでしょう.
case class ReaderWriterState[R, W, S, A](
run: (R, S) => (W, A, S) // R,S W,A,S
)
case class ReaderWriterStateT[F[_],R, W, S, A](
run: (R, S) => F[(W, A, S)] // R,S W,A,S。 F
)
入力されたタイプと返されたタイプは一致します.scalazではscalaz/Reader WriterStateTと定義されています.scala
/** A monad transformer stack yielding `(R, S1) => F[(W, A, S2)]`. */
sealed abstract class IndexedReaderWriterStateT[F[_], -R, W, -S1, S2, A] {
self =>
def run(r: R, s: S1): F[(W, A, S2)]
/** Discards the writer component. */
def state(r: R)(implicit F: Functor[F]): IndexedStateT[F, S1, S2, A] =
IndexedStateT((s: S1) => F.map(run(r, s)) {
case (w, a, s1) => (s1, a)
})
/** Calls `run` using `Monoid[S].zero` as the initial state */
def runZero[S <: S1](r: R)(implicit S: Monoid[S]): F[(W, A, S2)] =
run(r, S.zero)
/** Run, discard the final state, and return the final value in the context of `F` */
def eval(r: R, s: S1)(implicit F: Functor[F]): F[(W, A)] =
F.map(run(r,s)) { case (w,a,s2) => (w,a) }
/** Calls `eval` using `Monoid[S].zero` as the initial state */
def evalZero[S <: S1](r:R)(implicit F: Functor[F], S: Monoid[S]): F[(W,A)] =
eval(r,S.zero)
/** Run, discard the final value, and return the final state in the context of `F` */
def exec(r: R, s: S1)(implicit F: Functor[F]): F[(W,S2)] =
F.map(run(r,s)){case (w,a,s2) => (w,s2)}
/** Calls `exec` using `Monoid[S].zero` as the initial state */
def execZero[S <: S1](r:R)(implicit F: Functor[F], S: Monoid[S]): F[(W,S2)] =
exec(r,S.zero)
...
IndexedReader WriterStateTは,eval,execなど多くのIndexedStateTの演算方法を実現していることを見た.mapとflatMapがどのように実現されているかを見てみましょう.
def map[B](f: A => B)(implicit F: Functor[F]): IndexedStateT[F, S1, S2, B] = IndexedStateT(s => F.map(apply(s)) {
case (s1, a) => (s1, f(a))
})
def flatMap[S3, B](f: A => IndexedStateT[F, S2, S3, B])(implicit F: Bind[F]): IndexedStateT[F, S1, S3, B] = IndexedStateT(s => F.bind(apply(s)) {
case (s1, a) => f(a)(s1)
})
私たちが前に行ったOptionStateの例と同じように、FがmapとflatMapを実現できれば、IndexedReader WriterStateTはmapとflatMapを実現することができます.In dexedReader WriterStateTは、for-loopで各コマンドがliftを使用してタイプを昇格させることを省くために、ほとんどの操作関数を再実現しました.
private trait ReaderWriterStateTMonad[F[_], R, W, S]
extends MonadReader[({type λ[r, α]=ReaderWriterStateT[F, r, W, S, α]})#λ, R]
with MonadState[({type f[s, α] = ReaderWriterStateT[F, R, W, s, α]})#f, S]
with MonadListen[({type f[w, α] = ReaderWriterStateT[F, R, w, S, α]})#f, W]
with IndexedReaderWriterStateTFunctor[F, R, W, S, S] {
implicit def F: Monad[F]
implicit def W: Monoid[W]
def bind[A, B](fa: ReaderWriterStateT[F, R, W, S, A])(f: A => ReaderWriterStateT[F, R, W, S, B]): ReaderWriterStateT[F, R, W, S, B] = fa flatMap f
def point[A](a: => A): ReaderWriterStateT[F, R, W, S, A] =
ReaderWriterStateT((_, s) => F.point((W.zero, a, s)))
def ask: ReaderWriterStateT[F, R, W, S, R] =
ReaderWriterStateT((r, s) => F.point((W.zero, r, s)))
def local[A](f: R => R)(fa: ReaderWriterStateT[F, R, W, S, A]): ReaderWriterStateT[F, R, W, S, A] =
ReaderWriterStateT((r, s) => fa.run(f(r), s))
override def scope[A](k: R)(fa: ReaderWriterStateT[F, R, W, S, A]): ReaderWriterStateT[F, R, W, S, A] =
ReaderWriterStateT((_, s) => fa.run(k, s))
override def asks[A](f: R => A): ReaderWriterStateT[F, R, W, S, A] =
ReaderWriterStateT((r, s) => F.point((W.zero, f(r), s)))
def init: ReaderWriterStateT[F, R, W, S, S] =
ReaderWriterStateT((_, s) => F.point((W.zero, s, s)))
def get = init
def put(s: S): ReaderWriterStateT[F, R, W, S, Unit] =
ReaderWriterStateT((r, _) => F.point((W.zero, (), s)))
override def modify(f: S => S): ReaderWriterStateT[F, R, W, S, Unit] =
ReaderWriterStateT((r, s) => F.point((W.zero, (), f(s))))
override def gets[A](f: S => A): ReaderWriterStateT[F, R, W, S, A] =
ReaderWriterStateT((_, s) => F.point((W.zero, f(s), s)))
def writer[A](w: W, v: A): ReaderWriterStateT[F, R, W, S, A] =
ReaderWriterStateT((_, s) => F.point((w, v, s)))
override def tell(w: W): ReaderWriterStateT[F, R, W, S, Unit] =
ReaderWriterStateT((_, s) => F.point((w, (), s)))
def listen[A](ma: ReaderWriterStateT[F, R, W, S, A]): ReaderWriterStateT[F, R, W, S, (A, W)] =
ReaderWriterStateT((r, s) => F.map(ma.run(r, s)) { case (w, a, s1) => (w, (a, w), s1)})
}
このReader WriterStateを使用して、通信ポートの使用プログラムをシミュレートし、使用状況を記録するプログラムを作成します.まずポート番号を入力します.プログラムで使用するポート番号をリセットできます.
val program: ReaderWriterState[Config, List[String], Int, Int] = for {
_ <- log("Start - r: %s, s: %s")
res <- invokeService
_ <- log("Between - r: %s, s: %s")
_ <- setService(8,"Com8")
_ <- invokeService
_ <- log("Done - r: %s, s: %s")
} yield res //> program : scalaz.RWS[Exercises.rws.Config,List[String],Int,Int] = scalaz.I
//| ndexedReaderWriterStateT$$anon$5@223191a6
これは高級言語で書かれたプログラムのようです.詳細はいくつかの機能関数にあります.Reader WriterStateタイプを返す必要があります.
case class Config(var port: Int, var portName: String)
def log[R, S](msg: String): RWS[R, List[String], S, Unit] =
ReaderWriterState {
case (r, s) => (msg.format(r, s) :: Nil, (), s) //.point[Identity]
} //> log: [R, S](msg: String)scalaz.RWS[R,List[String],S,Unit]
def invokeService: ReaderWriterState[Config, List[String], Int, Int] =
ReaderWriterState {
case (cfg, invocationCount) => (
List("Invoking service with port: " + cfg.portName),
scala.util.Random.nextInt(100),
invocationCount + 1
) //.point[Identity]
} //> invokeService: => scalaz.ReaderWriterState[Exercises.rws.Config,List[String
//| ],Int,Int]
def setService(p: Int, n: String): ReaderWriterState[Config, List[String], Int, Int] =
ReaderWriterState {
case (cfg, invocationCount) => cfg.port=p; cfg.portName=n
(List("Changing service port to " + cfg.portName),
scala.util.Random.nextInt(100),
invocationCount)
} //> setService: (p: Int, n: String)scalaz.ReaderWriterState[Exercises.rws.Confi
//| g,List[String],Int,Int]
val program: ReaderWriterState[Config, List[String], Int, Int] = for {
_ <- log("Start - r: %s, s: %s")
res <- invokeService
_ <- log("Between - r: %s, s: %s")
_ <- setService(8,"Com8")
_ <- invokeService
_ <- log("Done - r: %s, s: %s")
} yield res //> program : scalaz.RWS[Exercises.rws.Config,List[String],Int,Int] = scalaz.I
//| ndexedReaderWriterStateT$$anon$5@223191a6
val r = program run (Config(443,"Com3"), 0) //> r : scalaz.Id.Id[(List[String], Int, Int)] = (List(Start - r: Config(443,C
//| om3), s: 0, Invoking service with port: Com3, Between - r: Config(443,Com3)
//| , s: 1, Changing service port to Com8, Invoking service with port: Com8, Do
//| ne - r: Config(88,Com8), s: 2),68,2)
println("Result: " + r._2) //> Result: 68
println("Service invocations: " + r._3) //> Service invocations: 2
println("Log: %n%s".format(r._1.mkString("\t", "%n\t".format(), "")))
//> Log:
//| Start - r: Config(443,Com3), s: 0
//| Invoking service with port: Com3
//| Between - r: Config(443,Com3), s: 1
//| Changing service port to Com8
//| Invoking service with port: Com8
//| Done - r: Config(88,Com8), s: 2