4
sealed trait IO[A] { self => def run: A def map[B](f: A => B): IO[B] = new IO[B] { def run = f(self.run) } def flatMap[B](f: A => IO[B]): IO[B] = new IO[B] { def run = f(self.run).run } } object IO extends Monad[IO] { def unit[A](a: => A): IO[A] = new IO[A] { def run = a } override def flatMap[A,B](fa: IO[A])(f: A => IO[B]) = fa flatMap f def apply[A](a: => A): IO[A] = unit(a) } def ReadLine: IO[String] = IO { readLine } def PrintLine(msg: String): IO[Unit] = IO { println(msg) } def fahrenheitToCelsius(f: Double): Double = (f - 32) * 5.0/9.0 def converter: IO[Unit] = PrintLine("Enter a temperature in Fahrenheit: ").flatMap{ _ => ReadLine.map(_.toDouble).flatMap{ d => PrintLine(fahrenheitToCelsius(d).toString).map{ _ => ()}}} Simple IO Monad def converter: IO[Unit] = for { _ <- PrintLine("Enter a temperature in Fahrenheit: ") d <- ReadLine.map(_.toDouble) _ <- PrintLine(fahrenheitToCelsius(d).toString) } yield () Our converter definition no longer has side effects—it’s a referentially transparent description of a computation with effects, and converter.run is the interpreter that will actually execute those effects. And because IO forms a Monad, we can use all the monadic combinators we wrote previously. We don’t necessarily endorse writing code this way in Scala. But it does demonstrate that FP is not in any way limited in its expressiveness—every program can be expressed in a purely functional way, even if that functional program is a straightforward embedding of an imperative program into the IO monad. An IO monad like what we have so far is a kind of least common denominator for expressing programs with external effects. Its usage is important mainly because it clearly separates pure code from impure code, forcing us to be honest about where interactions with the outside world are occurring. It also encourages the beneficial factoring of effects that we discussed earlier. Functional Programming in Scala val echo = ReadLine.flatMap(PrintLine)—An IO[Unit] that reads a line from the console and echoes it back val readInt = ReadLine.map(_.toInt)—An IO[Int] that parses an Int by reading a line from the console val readInts = readInt ** readInt—An IO[(Int,Int)] that parses an (Int,Int) by reading two lines from the console2 replicateM(10)(ReadLine)—An IO[List[String]] that will read 10 lines from the console and return the list of results Here are some other example usages of IO Our converter function is pure—it returns an IO value, which simply describes an action that needs to take place, but doesn’t actually execute it. We say that converter has (or produces) an effect or is effectful, but it’s only the interpreter of IO (its run method) that actually has a side effect.

Simple IO Monad in 'Functional Programming in Scala

Embed Size (px)

Citation preview

sealed trait IO[A] { self =>def run: Adef map[B](f: A => B): IO[B] = new IO[B] { def run = f(self.run) }def flatMap[B](f: A => IO[B]): IO[B] = new IO[B] { def run = f(self.run).run }

}

object IO extends Monad[IO] {def unit[A](a: => A): IO[A] = new IO[A] { def run = a }override def flatMap[A,B](fa: IO[A])(f: A => IO[B]) = fa flatMap fdef apply[A](a: => A): IO[A] = unit(a)

}

def ReadLine: IO[String] = IO { readLine }def PrintLine(msg: String): IO[Unit] = IO { println(msg) }

def fahrenheitToCelsius(f: Double): Double = (f - 32) * 5.0/9.0

def converter: IO[Unit] =PrintLine("Enter a temperature in Fahrenheit: ").flatMap{ _ =>

ReadLine.map(_.toDouble).flatMap{ d =>PrintLine(fahrenheitToCelsius(d).toString).map{ _ => ()}}}

SimpleIOMonad

def converter: IO[Unit] = for {_ <- PrintLine("Enter a temperature in Fahrenheit: ")d <- ReadLine.map(_.toDouble)_ <- PrintLine(fahrenheitToCelsius(d).toString)

} yield ()

Our converter definition no longer has side effects—it’s a referentially transparent description of a computation with effects, and converter.run is theinterpreter that will actually execute those effects. And because IO forms a Monad, we can use all the monadic combinators we wrote previously.

We don’t necessarily endorse writing code this way in Scala. But it does demonstrate that FP is not in any way limited in its expressiveness—every program can beexpressed in a purely functional way, even if that functional program is a straightforward embedding of an imperative program into the IO monad.

An IO monad like what we have so far is a kind of least common denominator for expressing programs with external effects. Its usage is important mainly because itclearly separates pure code from impure code, forcing us to be honest about where interactions with the outside world are occurring. It also encourages the beneficialfactoring of effects that we discussed earlier.

Functional Programming in Scala

val echo = ReadLine.flatMap(PrintLine)—An IO[Unit] that reads a linefrom the console and echoes it back

val readInt = ReadLine.map(_.toInt)—An IO[Int] that parses an Int byreading a line from the console

val readInts = readInt ** readInt—An IO[(Int,Int)] that parses an(Int,Int) by reading two lines from the console2

replicateM(10)(ReadLine)—An IO[List[String]] that will read 10 linesfrom the console and return the list of results

Here are some other example usages of IO

Our converter function is pure—it returns an IO value, which simply describes an action that needs totake place, but doesn’t actually execute it. We say that converter has (or produces) an effect or iseffectful, but it’s only the interpreter of IO (its run method) that actually has a side effect.

sealed trait IO[A] { self =>def run: Adef map[B](f: A => B): IO[B] = new IO[B] { def run = f(self.run) }def flatMap[B](f: A => IO[B]): IO[B] = new IO[B] { def run = f(self.run).run }

}

object IO extends Monad[IO] {def unit[A](a: => A): IO[A] = new IO[A] { def run = a }override def flatMap[A,B](fa: IO[A])(f: A => IO[B]) = fa flatMap fdef apply[A](a: => A): IO[A] = unit(a)

}

def ReadLine: IO[String] = IO { readLine }def PrintLine(msg: String): IO[Unit] = IO { println(msg) }

def fahrenheitToCelsius(f: Double): Double = (f - 32) * 5.0/9.0

def converter: IO[Unit] =PrintLine("Enter a temperature in Fahrenheit: ").flatMap{ _ =>

ReadLine.map(_.toDouble).flatMap{ d =>PrintLine(fahrenheitToCelsius(d).toString).map{ _ => ()}}}

def converter: IO[Unit] = for {_ <- PrintLine("Enter a temperature in Fahrenheit: ")d <- ReadLine.map(_.toDouble)_ <- PrintLine(fahrenheitToCelsius(d).toString)

} yield ()

SimpleIOMonad

Functional Programming in Scala

IO

IO

IO

IO

IO

IO

IO

flatMap

flatMap

mapmap

IO { run = f(self.run).run }

IO { run = readLine }

IO { run = println(msg) }

f

IO { run = f(self.run).run }

f

IO { run = f(self.run) }

IO { run = println(msg) }

PrintLine(fahrenheitToCelsius(d).toString)ReadLine

IO { run = f(self.run) }

PrintLine("Enter a temperature in Fahrenheit: ")

()

f

1new

2new

3run

4run

toDouble

5new

6new

7new

8run

9run

run10

new11

new12

run13

run14

converter

f“122”

Unit

122.0

“122”

122.0 122.0

Unit

Unit

d=122.0

Unit Unit

Unit

Unit

d=122.0

msg=“50.0”

converter.run

1new

2new

1new

…run14

SimpleIOMonad

IO

IO

IO

IO

IO

IO

IO

flatMap

flatMap

mapmap

IO { run = f(self.run).run }

IO { run = readLine }

IO { run = println(msg) }

f

IO { run = f(self.run).run }

f

IO { run = f(self.run) }

IO { run = println(msg) }

PrintLine(fahrenheitToCelsius(d).toString)ReadLine

IO { run = f(self.run) }

PrintLine("Enter a temperature in Fahrenheit: ")

()

f

1new

2new

3run

4run

toDouble

5new

6new

7new

8run

9run

run10

new11

new12

run13

run14

f“122”

Unit

122.0

“122”

122.0 122.0

Unit

Unit

d=122.0

Unit Unit

Unit

Unit

d=122.0

msg=“50.0”

sealed trait IO[A] { self =>def run: Adef map[B](f: A => B): IO[B] = new IO[B] { def run = f(self.run) }def flatMap[B](f: A => IO[B]): IO[B] = new IO[B] { def run = f(self.run).run }

}

object IO extends Monad[IO] {def unit[A](a: => A): IO[A] = new IO[A] { def run = a }override def flatMap[A,B](fa: IO[A])(f: A => IO[B]) = fa flatMap fdef apply[A](a: => A): IO[A] = unit(a)

}

def ReadLine: IO[String] = IO { readLine }def PrintLine(msg: String): IO[Unit] = IO { println(msg) }

def fahrenheitToCelsius(f: Double): Double =(f-32)*5.0/9.0

def converter: IO[Unit] =PrintLine("Enter a temperature in Fahrenheit:").flatMap{_=>ReadLine.map(_.toDouble).flatMap{ d =>PrintLine(fahrenheitToCelsius(d).toString).map{_=>()}}}

def converter: IO[Unit] = for {_ <- PrintLine("Enter a temperature in Fahrenheit: ")d <- ReadLine.map(_.toDouble)_ <- PrintLine(fahrenheitToCelsius(d).toString)

} yield ()

converter

converter.run

1new

2new

1new

run

14

SimpleIOMonad