Fight the Future

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

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

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

That Darn Property 3(最悪の特性3)

The 3rd property said that the world can only be in one state at any given moment in time. I haven't solved that one yet and here's why it's a problem

3番目の特性は世界はあらゆる瞬間でも1つの状態にだけあるということを言っています。
それはまだ解決できていません。なぜなら問題があるからです。

class Evil_v1 extends IOApplication_v1 {
  import RTConsole_v1._
  def iomain(
      args:Array[String], 
      startState:WorldState) = {
    val (stateA, a) = getString(startState)
    val (stateB, b) = getString(startState)
    assert(a == b)
    (startState, b)
  }
}

Here I've called getString twice with the same inputs. If the code was referentially transparent then the result, a and b, should be the same but of course they won't be unless the user types the same thing twice. The problem is that "startState" is visible at the same time as the other world states stateA and stateB.

同じ入力でgetStringを2回呼び出しています。もしこのコードが参照透過であるなら結果であるaとbが同一であるべきですが、同じことを2回入力しない限りもちろんそうはなりません。
問題は「startState」が世界の異なる状態であるstateAとstateBとして同時に可視化されていることです。

Inside Out(裏返す)


As a first step towards a solution, I'm going to turn everything inside out. Instead of iomain being a function from WorldState to WorldState, iomain will return such a function and the main driver will execute it. Here's the code

解決への第1歩として、すべてを裏返してみます。
iomainをWorldStateからWorldStateを返す関数とする代わりに、iomainはそのような関数を返すようにし、mainはそれを実行するようにします。コードはこうです。

//file RTConsole.scala
object RTConsole_v2 {
  def getString = {state:WorldState => 
    (state.nextState, Console.readLine)}
  def putString(s: String) = {state: WorldState => 
    (state.nextState, Console.print(s))}
}

getString and putString no longer get or put a string - instead they each return a new function that's "waiting" to be executed once a WorldState is provided.

getStringとputStringはもはや文字列をgetやputしません。代わりにひとたびWorldStateが与えられると実行を「待つ」新しい関数を毎回返します。

//file RTIO.scala
sealed trait WorldState{def nextState:WorldState}

abstract class IOApplication_v2 {
  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]):
    WorldState => (WorldState, _)
}

IOApplication's main driver calls iomain to get the function it will execute, then executes that function with an initial WorldState. HelloWorld doesn't change too much except it no longer takes a WorldState.

IOApplicationのmain関数は実行する関数を取得するためにiomainを呼び出します。それから内部のWorldStateとともに州の関数を実行します。
HelloWorldはもはやWorldStateを引数にしないこと以外ほとんど変更しません。

//file HelloWorld.scala
class HelloWorld_v2 extends IOApplication_v2 {
  import RTConsole_v2._
  def iomain(args:Array[String]) = 
    putString("Hello world")
}

At first glance we seem to have solved our problem because WorldState is nowhere to be found in HelloWorld. But it turns out it's just been buried a bit.

一見問題を解決したように見えます。なぜならWorldStateはHelloWorldのどこにも見つからないからです。
しかし、単に隠されているだけだとわかります。

Oh That Darn Property 3(ああ、最悪の特性3)

class Evil_v2 extends IOApplication_v2 {
  import RTConsole_v2._
  def iomain(args:Array[String]) = {        
    {startState:WorldState =>
      val (statea, a) = getString(startState)
      val (stateb, b) = getString(startState)
      assert(a == b)
      (startState, b)
    }
  }
}

Evil creates exactly the kind of function that iomain is supposed to return but once again things are broken. As long as the programmer can create arbitrary IO functions he or she can see through the WorldState trick.

Evilはiomainが戻すと改訂してる関数を正確に作成していますが、またしても物事は壊れています。プログラマが自由にIO関数を作成する限り、その人はWorldStateのトリックをIO関数を通じて見れるからです。

Property 3 Squashed For Good(特性3はよいもののために押しつぶされる)


All we need to do is prevent the programmer from creating arbitrary functions with the right signature. Um...we need to do what now?

我々に必要なものはプログラマが正しいシグネチャで自由に関数を作れないようにすることだけです。うーん、今何をする必要があるでしょう?


Okay, as we saw with WorldState it's easy to prevent programmers from creating subclasses. So let's turn our function signature into a trait.

さて、WorldStateを見てきてサブクラスを作成できないようにすることは簡単です。ゆえに関数のシグネチャをtraitに変えてみましょう。

sealed trait IOAction[+A] extends 
  Function1[WorldState, (WorldState, A)] 

private class SimpleAction[+A](
   expression: => A) extends IOAction[A]...
jyukutyoコメント

「sealed」はクラスに設定できる修飾詞です。

* sealedとされたクラスは、同一ファイル内のクラスからは継承できますが、別ファイル内で定義されたクラスでは継承できません。

[http://d.hatena.ne.jp/unageanu/20080510:title=[Scala] finalとsealed - うなの日記]


Unlike WorldState we do need to create IOAction instances. For example, getString and putString are in a separate file but they would need to create new IOActions. We just need them to do so safely. It's a bit of a dilemma until we realize that getString and putString have two separate pieces: the piece that does the primitive IO and the piece that turns the input world state into the next world state. A bit of a factory method might help keep things clean, too.

WorldStateと異なりIOActionインスタンスを生成する必要があります。たとえば、getStringとputStringは別のファイルにありますが新しいIOActionを生成する必要があるでしょう。
我々は安全にそれをする必要があるだけです。我々がgetStringとputStringが2つの別のものだと理解しない限り少しジレンマがあります。
プリミティブなIOをするものと入力した世界の状態を次の世界の状態に変えるものです。
少しのファクトリメソッドがものごとを整理する手助けになるでしょう。

//file RTIO.scala
sealed trait IOAction_v3[+A] extends 
  Function1[WorldState, (WorldState, A)] 

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

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

sealed trait WorldState{def nextState:WorldState}

abstract class IOApplication_v3 {
  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_v3[_]
}

The IOAction object is just a nice factory to create SimpleActions. SimpleAction's constructor takes a lazy expression as an argument, hence the "=> A" annotation. That expression won't be evaluated until SimpleAction's apply method is called. To call SimpleAction's apply method, a WorldState must be passed in. What comes out is a tuple with the new WorldState and the result of the expression.

IOActionオブジェクトはSimpleActionを生成する単なるファクトリです。
SimpleActionのコンストラクタは遅延評価の式を引数に取ります。それゆえ「=> A」の注釈となります。
この式はSimpleActionのapplyメソッドが呼び出されるまで評価されません。SimpleActionのapplyメソッドを呼び出すためには、WorldStateが渡されなければなりません。結果は新しいWorldStateと式の結果のタプルです。


jyukutyoコメント

「:」と型名の間に「=>」を入れることで、遅延評価渡し、すなわち無評価で実引数を渡すこともできる

Scala4階:関数


Here's what our IO methods look like now

今IOメソッドは次のようになります。

//file RTConsole.scala
object RTConsole_v3 {
  def getString = IOAction_v3(Console.readLine)
  def putString(s: String) = 
    IOAction_v3(Console.print(s))
}
jyukutyoコメント

applyは特殊なメソッド。IOAction_v3(Console.readLine)の呼び出しはobject IOAction_v3のapplyメソッド呼び出しとなる。

applyメソッドを使うと,Array(...)の様に,返り値を持った関数の様にも使える.

object Length {
def apply(s: String): int = s.length
}
println(Length("Foo"))

とすれば,3が帰ってくる.Length.apply()とLength()が等価.

利点は簡単で,importすれば,objectを関数代わりに使えるという事.ユーティリティメソッド代わりにできるかな.

航海日誌(2007-09-08)

applyについて詳しい説明とサンプルはへ。


And finally our HelloWorld class doesn't change a bit

結局HelloWorldクラスは少しも変わっていません。

class HelloWorld_v3 extends IOApplication_v3 {
  import RTConsole_v3._
  def iomain(args:Array[String]) = 
    putString("Hello world")
}

A little thought shows that there's no way to create an Evil IOApplication now. A programmer simply has no access to a WorldState. It has become totally sealed away. The main driver will only pass a WorldState to an IOAction's apply method, and we can't create arbitrary IOAction subclasses with custom definitions of apply.

ちょっとした見解として、今Evil IOApplicationを生成する手段はありません。
プログラマは単純にWorldStateへアクセスできません。すべて隠蔽されています。
main関数はWorldStateをIOActionのapplyメソッドに渡すだけであり、独自のapplyを定義した任意のIOActionのサブクラスを作成することはできません。


Unfortunately, we've got a combining problem. We can't combine multiple IOActions so we can't do something as simple as "What's your name", Bob, "Hello Bob."

不幸なことに、結びついた問題があります。複数のIOActionを組み合わせることができないため、「名前は何ですが」「ボブです」「やあボブ」のような単純なことができません。


Hmmmm, IOAction is a container for an expression and monads are containers. IOAction needs to be combined and monads are combinable. Maybe, just maybe...

んー、IOActionは式のためのンテナであり、モナドはコンテナです。
IOActionは組み合わせる必要があり、モナドは組み合わせ可能です。そうですね、もしかしたら。。。