読者です 読者をやめる 読者になる 読者になる

Fight the Future



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

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.


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.


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.


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.


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.


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.


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.


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



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.


Here's a rough sketch for property 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.


Here's how I'll implement property 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(
      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.


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(
        startState:WorldState) = 
    putString(startState, "Hello world")