Fight the Future

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

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

One Div Zero: Monads are Elephants Part 4の翻訳。
今回からパート4です。
ずっと気になっていた、IOとモナドの関係に入りました。

Until you experience an adult elephant first hand you won't really understand just how big they can be. If monads are elephants then so far in this series of articles I've only presented baby elephants like List and Option. But now it's time to see a full grown adult pachyderm. As a bonus, this one will even do a bit of circus magic.

大人の象を直接見るまでは本当に象がどれだけ大きいのか理解したことにはなりません。
モナドがもし象であるならこの連載ではまだListとOptionのような幼い象を紹介しただけです。
しかし機は熟しました。大人の象を見ていきましょう。ボーナスとして少しサーカスマジックもさせます。

Functional Programming and IO(関数型プログラミングとIO)


In functional programming there's a concept called referential transparency. Referential transparency means you can call a particular function anywhere and any time and the same arguments will always give the same results. As you might imagine, a referentially transparent function is easier to use and debug than one that isn't.

関数型プログラミングにおいて参照透過性という概念があります。
参照透過性はある関数を同じ引数でいつどこで呼び出したとしても常に同じ結果になるという意味です。
想像のとおり、参照透過である関数はそうでない関数よりも使いやすくデバックしやすいものになります。


There's one area where referential transparency would seem impossible to achieve: IO. Several calls to the same readLine console function may result in any number of different strings depending on things like what the user ate for breakfast. Sending a network packet may end in successful delivery or it might not.

参照透過性を達成できない領域が1つあります。それはIOです。
同じコンソールを1行読み出す関数を何回か呼び出すとユーザーが朝食に何を食べたかのようなことに依存して異なる文字列が返ってくるかもしれません。
ネットワークパケットを送ることは送信に成功したかそうでないかの結果になります。


But we can't get rid of IO just to accomplish referential transparency. A program without IO is just a complicated way to make your CPU hot.

しかし、参照透過性を達成するためにIOを取り除くことはできません。IOなしのプログラミングは単にCPIを熱くさせる複雑な方法でしかありません。


You might guess that monads provide a solution for referentially transparent IO given the topic of this series but I'm going to work my way up from some simple principles. I'll solve the problem for reading and writing strings on the console but the same solution can be extended to arbitrary kinds of IO like file and network.

この連載のトピックを鑑みてモナドは参照透過性のあるIOのための解決方法を提供するものと推測しているかもしれませんが、いくつかの単純な原則から築き上げようと思います。
コンソールから読み書きするために問題を解決しようとしますが、同じ解決方法はファイルやネットワークのような任意の種類のIOにも拡張できます。


Of course, you may not think that referentially transparent IO is terribly important in Scala. I'm not here to preach the one true way of purely functional referential transparency. I'm here to talk about monads and it just so happens that the IO monad is very illustrative of how several monads work.

もちろん、参照透過であるIOがScalaにおいてとても重要であるというこは考えなくてもかまいません。
私は純粋に関数的な参照透過性の1つの真実の手段を説こうとしているわけではありません。
モナドについて話そうとしており、IOモナドがいくつかのモナドがどのように動作しているかについてとても説明に役立つのでそうしているだけです。

The World In a Cup(カップの中の世界)


Reading a string from the console wasn't referentially transparent because readLine depends on the state of the user and "user" isn't one of its parameters. A file reading function would depend on the state of the file system. A function that reads a web page would depend on the state of the target web server, the Internet, and the local network. Equivalent output functions have similar dependencies.

コンソールから文字列を読み出すことは参照透過ではありません。なぜなら1行読みだすことはユーザーの状態に依存し、「ユーザー」はその関数の引数ではないからです。
ファイルを読み出す関数はファイルシステムの状態に依存します。
ウェブページを読み出す関数は対象となるウェブサーバーやインターネット、ローカルネットワークの状態に依存します。
同様に出力する関数も同じような依存性があります。


All this could be summed up by creating a class called WorldState and making it both a parameter and a result for all IO functions. Unfortunately, the world is a big place. My first attempt to write a WorldState resulted in a compiler crash as it ran out of memory. So instead I'll try for something a bit smaller than modeling the whole universe. That's where a bit of circus magic comes in.

これらはすべてWorldStateというクラスを作りすべてのIO関数がパラメータと戻り値の両方ともWorldStateとなるようにすれば話は終わります。
不幸なことに、世界は広いです。WorldStateを書くという最初の目論みはアウトオブメモリーでコンパイラがクラッシュするという結果になります。
ゆえに代わりに全世界をモデル化することよりも少し小さい何かを試してみましょう。
サーカスマジックが出てきました。


The slight-of-hand I'll use is to model only a few aspects of the world and just pretend WorldState knows about the rest of the world. Here are some aspects that would be useful

世界のいくつかの面ををモデル化し、WorldStateが世界の残りをしるように装うためだけに手品を使います。いくつか役に立つ見地があります。

jyukutyoコメント

slight-of-handはsleight of handのタイポ?マジックと手品だし。


  1. The state of the world changes between IO functions.
  2. The world's state is what it is. You can't just create new ones whenever you want (val coolWorldState = new WorldState(){def jamesIsBillionaire = true}).
  3. The world is in exactly one state at any moment in time.
  1. 世界の状態はIO関数間で変わる。
  2. 世界の状態はそのままのものである。val coolWorldState = new WorldState(){def jamesIsBillionaire = true}のようなことを望んだとしても新しい世界は作れない。
  3. 世界はどんな瞬間でも厳密に1つの状態である。


Property 3 is a bit tricky so let's deal with properties 1 and 2 first.

特性3は少しトリッキーなのでまず特性1と2から考えてみましょう。


Here's a rough sketch for property 1

特性1のラフスケッチです。

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

getString and putString use functions defined in scala.Console as raw primitive functions. They take a world state and return a tuple consisting of a new world state and the result of the primitive IO.

getStringとputStringは低レベルなプリミティブ関数としてscala.Consoleに定義されている関数を使います。世界の状態を引数にとり、世界の新しい状態とプリミティブなIOの結果をで構成されたタプルを返します。


Here's how I'll implement property 2

特性2を実装しました。

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

abstract class IOApplication_v1 {
  private class WorldStateImpl(id:BigInt) 
      extends WorldState {
    def nextState = new WorldStateImpl(id + 1)
  }
  final def main(args:Array[String]):Unit = {
    iomain(args, new WorldStateImpl(0))
  }
  def iomain(
      args:Array[String], 
      startState:WorldState):(WorldState, _)
}

WorldState is a sealed trait; it can only be extended within the same file. IOApplication defines the only implementation privately so nobody else can instantiate it. IOApplication also defines a main function that can't be overridden and calls a function named iomain that must be implemented in a subclass. All of this is plumbing that is meant to be hidden from programmers that use the IO library.

WorldStateはtraitにします。継承できるのは同じファイルにあるものだけです。
IOApplicationはその実装だけをプライベートで定義します。ゆえに誰もインスタンス化することはできません。
IOApplicationはまたオーバーライドできず、サブクラスで必ず実装しなければならないiomain関数を呼び出すものであるmain関数を定義します。
これらはすべてIOライブラリを使うプログラマから隠蔽するための配管です。


Here's what hello world looks like given all this

次のようなhello worldがあります。

// file HelloWorld.scala
class HelloWorld_v1 extends IOApplication_v1 {
  import RTConsole_v1._
  def iomain(
        args:Array[String], 
        startState:WorldState) = 
    putString(startState, "Hello world")
}