Fight the Future

Java言語とJVM、そしてJavaエコシステム全般にまつわること

モナドについて調べていく(23)

One Div Zero: Monads are Elephants Part 4の翻訳続き。

長かった連載もこれで最後です。

Ladies and Gentleman I Present the Mighty IO Monad(みなさん、すばらしいIOモナドを紹介します)


The IOAction.apply factory method takes an expression of type A and returns an IOAction[A]. It sure looks like "unit." It's not, but it's close enough for now. And if we knew what flatMap was for this monad then the monad laws would tell us how to create map using it and unit. But what's flatMap going to be? The signature needs to look like def flatMap[B](f: A=>IOAction[B]):IOAction[B]. But what does it do?

IOActionのファクトリメソッドapplyは引数にA型の式をとりIOAction[A]を返します。たしかに「unit」のように見えます、そうではないのですが、今はだいたい同じでいいです。
もしflatMapがこのモナドのためにすることを知っているならモナド則は我々にそれとunitを使ってマップを作成する方法を教えてくれます。
しかし何がflatMapであるべきでしょう?シグネチャはdef flatMap[B](f: A=>IOAction[B]):IOAction[B]のようなものを必要とします。しかし、それは何をするのでしょう?


What we want it to do is chain an action to a function that returns an action and when activated causes the two actions to occur in order. In other words, getString.flatMap{y => putString(y)} should result in a new IOAction monad that, when activated, first activates the getString action then does the action that putString returns. Let's give it a whirl.

それにやらせたいことはアクションを返す関数のための連鎖したアクションであり、活性化したとき2つのアクションを順に実行するものです。言い換えると、getString.flatMap{y => putString(y)}は新しいIOActionモナドとなるべきであり、活性化されると最初にgetStringアクションを活性化しそれからputStringが返すアクションを実行します。試してみましょう。

//file RTIO.scala
sealed abstract class IOAction_v4[+A] extends 
    Function1[WorldState, (WorldState, A)] {
  def map[B](f:A => B):IOAction_v4[B] = 
    flatMap {x => IOAction_v4(f(x))}  
  def flatMap[B](f:A => IOAction_v4[B]):IOAction_v4[B]= 
    new ChainedAction(this, f)
  
  private class ChainedAction[+A, B](
      action1: IOAction_v4[B], 
      f: B => IOAction_v4[A]) extends IOAction_v4[A] {
    def apply(state1:WorldState) = {
      val (state2, intermediateResult) = 
        action1(state1);
      val action2 = f(intermediateResult)
      action2(state2)
    }
  }  
}

object IOAction_v4 {
  def apply[A](expression: => A):IOAction_v4[A] = 
    new SimpleAction(expression)

  private class SimpleAction[+A](expression: => A) 
      extends IOAction_v4[A] {
    def apply(state:WorldState) = 
      (state.nextState, expression)
  }    
}

// the rest remains the same
sealed trait WorldState{def nextState:WorldState}

abstract class IOApplication_v4 {
  private class WorldStateImpl(id:BigInt) ...


The IOAction factory and SimpleAction remain the same. The IOAction class gets the monad methods. Per the monad laws, map is just defined in terms of flatMap and what we're using as unit for now. flatMap defers all the hard work to a new IOAction implementation called ChainedAction.

IOActionのファクトリとSimpleActionは変わっていません。IOActionはモナドメソッドを加えました。モナド則に従って、mapは単にflatMapとさしあたりunitとして使っているものを利用して実装しています。flatMapはChainedActionという新しいIOActionの実装への難しい責務を任せています。


The trick in ChainedAction is its apply method. First it calls action1 with the first world state. This results in a second world state and an intermediate result. The function it was chained to needs that result and in return the function generates another action: action2. action2 is called with the second world state and the tuple that come out is the end result. Remember that none of this will happen until the main driver passes in an initial WorldState object.

ChainedActionにおけるトリックはそのapplyメソッドです。初めaction1を1番目の世界の状態で呼び出します。
これは2番目の世界の状態と中間結果が結果となります。それがつなぐ関数は結果を必要とし、戻り値として他のアクションであるaction2を生成します。
action2は2番目の席あの状態とともに呼び出し、出てくるタプルを最終結果とします。
内部的なWorldStateオブジェクトをmain関数に渡さない限りこのどれもが起こらないことを覚えておいてください。

A Test Drive(テスト駆動)


At some point you may have wondered why getString and putString weren't renamed to something like createGetStringAction/createPutStringAction since that's in fact what they do. For an answer, look at what happens when we stick 'em in our old friend "for".

getStringとputStringがcreateGetStringAction/createPutStringActionのような何かになぜ名前を変更しないのかある時点で不思議に思うかもしれません。
実際それらがやっていることがそうだからです。

object HelloWorld_v4 extends IOApplication_v4 {
  import RTConsole_v4._
  def iomain(args:Array[String]) = {
    for{
        _ <- putString(
            "This is an example of the IO monad.");
        _ <- putString("What's your name?");
        name <- getString;
        _ <- putString("Hello " + name)
    } yield ()
  }
}

It's as if "for" and getString/putString work together to create a mini language just for creating a complex IOActions.

これはまるで「for」とgetString/putStringが複雑なIOActionを作成するためだけにミニ言語を作成しようとともに動いているかのようです。

Take a Deep Breath(深呼吸しましょう)


Now's a good moment to sum up what we've got. IOApplication is pure plumbing. Users subclass it and create a method called iomain which is called by main. What comes back is an IOAction - which could in fact be a single action or several actions chained together. This IOAction is just "waiting" for a WorldState object before it can do its work. The ChainedAction class is responsible for ensuring that the WorldState is changed and threaded through each chained action in turn.

さて我々が成し遂げたことを総括するいい機会です。IOApplicationは純粋な配管です。
ユーザーがそのサブクラスを作りmainから呼び出されるiomainというメソッドを作成します。
IOActionを言い換えましょう。実際IOActionは単一のアクションでも連結したいくつかのアクションでもあるかもしれません。このIOActionはその作業をする前にWorldStateオブジェクトを「待って」いるだけです。
ChainedActionクラスはWorldStateが順番に各連結したアクションを通じて変更され通されたことを保証する責務があります。


getString and putString don't actually get or put Strings as their names might indicate. Instead, they create IOActions. But, since IOAction is a monad we can stick it into a "for" statement and the result looks as if getString/putString really do what they say the do.

getStringとputStringはその名前が指し示すように実際にStringを取得したり設定したりしません。代わりにIOActionを生成します。しかしIOActionはモナドであるため、それを「for」構文に突き通すことができますし結果はまるでgetString/putStringが実際に名前の通りのことをやっているかのように見えます。


It's a good start; we've almost got a perfectly good monad in IOAction. We've got two problems. The first is that, because unit changes the world state we're breaking the monad laws slightly (e.g. m flatMap unit === m). That's kinda trivial in this case because it's invisible. But we might as well fix it.

いいスタートです。IOActionにおけるほぼ完璧なできのモナドを手に入れました。問題が2つあります。1つめはunitが世界の状態を変更してしまうのでモナド則を少し破っているということです(たとえばm flatMap unit === m)。この場合見えないことなので取るに足らないことです。しかし、これも対応できるでしょう。


The second problem is that, in general, IO can fail and we haven't captured that just yet.

2つめの問題は、一般にIOは失敗し、それをすぐに捉えることができないということです。

IO Errors(IOエラー)


In monadic terms, failure is represented by a zero. So all we need to do is map the native concept of failure (exceptions) to our monad. At this point I'm going to take a different tack from what I've been doing so far: I'll write one final version of the library with comments inline as I go.

モナド的な見地では、失敗はゼロで表現します。ゆえに必要なことは失敗(例外)の固有の概念をモナドに結びつけるだけです。この点では、これまでやってきたこととは異なる方針をとります。インラインでコメントをつけたこのライブラリの最終バージョンを書きます。


The IOAction object remains a convenient module to hold several factories and private implementations (which could be anonymous classes, but it's easier to explain with names). SimpleAction remains the same and IOAction's apply method is a factory for them.

IOActionオブジェクトはいくつかのファクトリとプライベートな実装(それらは無名クラスかもしれませんが、名前で説明する方が簡単です)を保持する便利なモジュールとして残っています。SimpleActionも同様であり、IOActionのapplyメソッドはそれらのファクトリです。

//file RTIO.scala
object IOAction {
  private class SimpleAction[+A](expression: => A) 
      extends IOAction[A] {
    def apply(state:WorldState) = 
      (state.nextState, expression)
  }

  def apply[A](expression: => A):IOAction[A] = 
    new SimpleAction(expression)

UnitAction is a class for unit actions - actions that return the specified value but don't change the world state. unit is a factory method for it. It's kind of odd to make a distinction from SimpleAction, but we might as well get in good monad habits now for monads where it does matter.

UnitActionはunitアクションのためのクラスです。unitアクションは指定された値を返すが世界の状態は変更しないアクションです。unitはそれのためのファクトリメソッドです。SimpleActionと区別させることはやや奇妙ですが、その上に我々は今優れたモナドにおけるモナドを重要足らしめているところのものの性質がわかるかもしれません。

  private class UnitAction[+A](value: A) 
      extends IOAction[A] {
    def apply(state:WorldState) = 
      (state, value)
  }
  
  def unit[A](value:A):IOAction[A] = 
    new UnitAction(value)

FailureAction is a class for our zeros. It's an IOAction that always throws an exception. UserException is one such possible exception. The fail and ioError methods are factory methods for creating zeroes. Fail takes a string and results in an action that will raise a UserException whereas ioError takes an arbitrary exception and results in an action that will throw that exception.

FailureActionはゼロのためのクラスです。それは常に例外をスローするIOActionです。UserExceptionはそういう例外の1つです。failとioErrorメソッドはゼロを生成するファクトリメソッドですioErrorが任意の例外を引数に取りその例外をスローするアクションを返すのに対して、failは文字列を引数に取りUserExceptionを発生させるアクションを返します。

  private class FailureAction(e:Exception) 
      extends IOAction[Nothing] {
    def apply(state:WorldState) = throw e
  }
  
  private class UserException(msg:String) 
    extends Exception(msg)

  def fail(msg:String) = 
    ioError(new UserException(msg))    
  def ioError[A](e:Exception):IOAction[A] = 
    new FailureAction(e)
}

IOAction's flatMap, and ChainedAction remain the same. Map changes to actually call the unit method so that it complies with the monad laws.

IOActionのflatMapとChainedActionは変わっていません。mapは実はunitメソッドを呼び出すように変わりました。モナド則を満たすためです。


I've also added two bits of convenience: >> and <<. Where flatMap sequences this action with a function that returns an action, >> and << sequence this action with another action.

また便利なものを2つばかり追加しました。>>と<<です。flatMapはアクションを返す関数とともにこのアクションを順序づけるのに対して、>>と<<はこのアクションを他のアクションとともに順序づけます。


It's just a question of which result you get back. >>, which can be pronounced "then", creates an action that returns the second result, so 'putString "What's your name" >> getString' creates an action that will display a prompt then return the user's response.

どちらの結果を戻すのかという疑問があります。>>は、「then」と発音しますが、2つ目の結果を返すアクションを生成するので、「putString "What's your name" >> getString」はプロンプトを表示しユーザーのレスポンスを戻すアクションを生成します。


Conversely, <<, which can be called "before" creates an action that will return the result from the first action.

逆に、<<は、「before」と呼びますが、最初のアクションから結果を戻すアクションを生成します。

sealed abstract class IOAction[+A] 
    extends Function1[WorldState, (WorldState, A)] {
  def map[B](f:A => B):IOAction[B] = 
    flatMap {x => IOAction.unit(f(x))}  
  def flatMap[B](f:A => IOAction[B]):IOAction[B]=
    new ChainedAction(this, f)

  private class ChainedAction[+A, B](
      action1: IOAction[B], 
      f: B => IOAction[A]) extends IOAction[A] {
    def apply(state1:WorldState) = {
      val (state2, intermediateResult) = 
        action1(state1);
      val action2 = f(intermediateResult)
      action2(state2)
    }
  }  

  def >>[B](next: => IOAction[B]):IOAction[B] =
    for {
      _ <- this;
      second <- next
    } yield second
    
  def <<[B](next: => IOAction[B]):IOAction[A] =
    for {
      first <- this;
      _ <- next
    } yield first

Because we've got a zero now, it's possible to add a filter method by just following the monad laws. But here I've created two forms of filter method. One takes a user specified message to indicate why the filter didn't match whereas the other complies with Scala's required interface and uses a generic error message.

今ゼロを得たので、モナド則に従うだけでフィルターメソッドを追加することができます。しかしここで2つの形式のフィルターメソッドを作成しました。1つはフィルターがマッチしなかった理由を示すためにユーザーが指定したメッセージを引数に取りますが、もう1つはSからが必要とするインターフェースを満たし一般的なエラーメッセージを使います。

  def filter(
      p: A => Boolean, 
      msg:String):IOAction[A] =
    flatMap{x => 
      if (p(x)) IOAction.unit(x) 
      else IOAction.fail(msg)}
  def filter(p: A => Boolean):IOAction[A] =
    filter(p, "Filter mismatch")

A zero also means we can create a monadic plus. As some infrastructure for creating it, HandlingAction is an action that wraps another action and if that action throws an exception then it sends that exception to a handler function. onError is a factory method for creating HandlingActions. Finally, "or" is the monadic plus. It basically says that if this action fails with an exception then try the alternative action.

ゼロはまたモナド的な加算を作成することができるということを意味します。それを作成するためのある基盤として、HandlingActionは他のアクションをラップしそのアクションが例外をスローすればその例外をハンドラーに渡すというアクションです。
onErrorはHandlingActionを生成するファクトリメソッドです。最後に、「or」はモナド的な加算です。それは基本的にこのアクションがもし例外とともに失敗すれば代わりのアクションを試すということを述べています。

  private class HandlingAction[+A](
      action:IOAction[A],
      handler: Exception => IOAction[A]) 
      extends IOAction[A] {
    def apply(state:WorldState) = {
      try {
        action(state)
      } catch {
        case e:Exception => handler(e)(state)
      }
    }    
  }

  def onError[B >: A](
      handler: Exception => IOAction[B]):
      IOAction[B] = 
    new HandlingAction(this, handler)      

  def or[B >: A](
      alternative:IOAction[B]):IOAction[B] =
    this onError {ex => alternative}
}

The final version of IOApplication stays the same

IOApplicationの最終バージョンも変わりません。

sealed trait WorldState{def nextState:WorldState}

abstract class IOApplication {
  private class WorldStateImpl(id:BigInt) 
      extends WorldState {
    def nextState = new WorldStateImpl(id + 1)
  }
  final def main(args:Array[String]):Unit = {
    val ioaction = iomain(args)
    ioaction(new WorldStateImpl(0));
  }
  def iomain(args:Array[String]):IOAction[_]
}

RTConsole stays mostly the same, but I've added a putLine method as an analog to println. I've also changed getString to be a val. Why not? It's always the same action.

RTConsoleはほとんど変わっていませんが、printlnの類似のものとしてputLineメソッドを追加しました。またgetStringをvalへ変えました。なぜ?常に同じアクションだからです。

//file RTConsole.scala
object RTConsole {
  val getString = IOAction(Console.readLine)
  def putString(s: String) = 
    IOAction(Console.print(s))
  def putLine(s: String) = 
    IOAction(Console.println(s))
}  

And now a HelloWorld application to exercise some of this new functionality. sayHello creates an action from a string. If the string is a recognized name then the result is an appropriate (or inappropriate) greeting. Otherwise it's a failure action.

さあHelloWorldアプリケーションでこの新しい機能性のいくつかを試してみましょう。
sayHelloは文字列から空くshんを生成します。もし文字列が名前として認識できるなら結果は適切な(もしくは不適切な)あいさつになります。そうでなければアクションは失敗します。


Ask is a convenience method that creates an action that will display a specified string then get one. The >> operator ensures that the action's result will be the result of getString.

askは指定した文字列を表示してそれを取得するアクションを生成する便利なメソッドです。>>演算子はアクションの結果がgetStringの結果であることを確かめます。


processsString takes an arbitrary string and, if it's 'quit' then it creates an action that will say goodbye and be done. On any other string sayHello is called. The result is combined with another action using 'or' in case sayHello fails. Either way the action is sequenced with the loop action.

processsStringは任意の文字列を引数に取り、もしそれが「quit」ならさようならを言うアクションを生成します。他の文字列ならsayHelloを呼び出します。結果はsayHelloが失敗した場合「or」を使って他のアクションと組み合わせます。


Loop is interesting. It's defined as a val just because it can be - a def would work just as well. So it's not quite a loop in the sense of being a recursive function, but it is a recursive value since it's defined in terms of processString which in turn is defined based on loop.

ループは興味深いです。そうすることができるという理由だけでvalとして定義しています。defと同様に動作します。ゆえに再帰関数であるという意味においては完全にはループではありませんが、再帰的な値であるので今度はループに基づくものとして定義されているprocessStringの見地から定義されます。


The iomain function kicks everything off by creating an action that will display an intro then do what the loop action specifies.

iomain関数はイントロを表示しループアクションが指定することを実行するアクションを生成してすべてを開始します。


Warning: because of the way the library is implemented this loop will eventually blow the stack. Do not use it in production code. Read the comments to see why.

警告:ライブラリを実装した手段により、このループは最終的にスタックを破壊するかもしれません。プロダクションコードでこれを使わないでください。理由はコメントを読んでみてください。

object HelloWorld extends IOApplication {
  import IOAction._
  import RTConsole._
  
  def sayHello(n:String) = n match {
    case "Bob" => putLine("Hello, Bob")
    case "Chuck" => putLine("Hey, Chuck")
    case "Sarah" => putLine("Helloooo, Sarah")
    case _ => fail("match exception")
  }
  
  def ask(q:String) =
    putString(q) >> getString

  def processString(s:String) = s match {
    case "quit" => putLine("Catch ya later")
    case _ => (sayHello(s) or         
        putLine(s + ", I don't know you.")) >>
        loop 
  }
    
  val loop:IOAction[Unit] = 
    for {
      name <- ask("What's your name? ");
      _ <- processString(name)
    } yield ()
  
  def iomain(args:Array[String]) = {
    putLine(
        "This is an example of the IO monad.") >>
    putLine("Enter a name or 'quit'") >>
    loop
  }
}
jyukutyoコメント

理由というのはこのコメントだと思う。

As for loop not being tail recursive - well, it can't be. The reason is a bit subtle. Loop isn't quite a normal loop. Instead, ultimately, it's an instance of ChainedAction. That's where the real problem is: as the library is designed ChainedAction's apply method cannot be tail recursive since its tail call must be to some arbitrary IOAction's apply method rather than to its own apply method.

One Div Zero: Monads are Elephants Part 4

要は、loopが末尾再帰ではないからというのが理由らしい。ループは単なるループではなくて、結局はChainedActionのインスタンスである。ChainedActionのapplyメソッドは末尾再帰にできない。自身のapplyメソッドではなく任意のIOActionのapplyメソッド呼び出しでなければならない、と。
末尾再帰でない以上、その関数呼び出しから戻ってきた後の処理の情報をスタックに保存するので、再帰が深くなればスタックの使用量が増え、いつかはあふれるということ。

Conclusion for Part 4(パート4の結論)


In this article I've called the IO monad 'IOAction' to make it clear that instances are actions that are waiting to be performed. Many will find the IO monad of little practical value in Scala. That's okay, I'm not here to preach about referential transparency. However, the IO monad is one of the simplest monads that's clearly not a collection in any sense.

この記事ではインスタンスが実行を待つアクションであることをはっきりさせるためにIOモナドを「IOAction」と呼びました。ScalaにおけるIOモナドの実践的な価値を少しわかっていただけたでしょう。それで大丈夫です。私はここで参照透過性について説こうとしているわけではありません。しかしながら、IOモナドはいかなる意味においても明らかにコレクションでないモナドのもっとも単純な1つです。


Still, instances of the IO monad can be seen as containers. But instead of containing values they contain expressions. flatMap and map in essence turn the embedded expressions into more complex expressions.

まだIOモナドインスタンスをコンテナとして見るかもしれません。しかし値を格納する代わりにIOモナドは式を格納します。flatMapとmapは本質的に埋め込まれた式をより複雑な式へ変換します。


Perhaps a more useful mental model is to see instances of the IO monad as computations or functions. flatMap can be seen as applying a function to the computation to create a more complex computation.

おそらくより役に立つ観念的なモデルはIOモナドインスタンスを計算や関数としてみることです。flatMapはより複雑な計算を生成するために計算に関数を適用するものとして見ることができます。


In the last part of this series I'll cover a way to unify the container and computation models. But first I want to reinforce how useful monads can be by showing an application that uses an elephantine herd of monads to do something a bit more complicated.

この連載の最後のパートではコンテナと計算モデルを統合する手段をカバーします。しかし最初により少し複雑な何かをするためにモナドの象の群れを使ったアプリケーションを見せることによってモナドがどれだけ役に立つのかを述べたいです。