KEMBAR78
Simple IO Monad in 'Functional Programming in Scala' | PDF
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.
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{ _ => ()}}}
def converter: IO[Unit] = for {
_ <- PrintLine("Enter a temperature in Fahrenheit: ")
d <- ReadLine.map(_.toDouble)
_ <- PrintLine(fahrenheitToCelsius(d).toString)
} yield ()
Simple	IO	Monad
Functional Programming in Scala
IO
IO
IO
IO
IO
IO
IO
flatMap
flatMap
map
map
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
1
new
2
new
3
run
4
run
toDouble
5
new
6
new
7
new
8
run
9
run
run
10
new
11
new
12
run
13
run
14
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
1
new
2
new
1
new
…
run
14
Simple	IO	Monad
IO
IO
IO
IO
IO
IO
IO
flatMap
flatMap
map
map
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
1
new
2
new
3
run
4
run
toDouble
5
new
6
new
7
new
8
run
9
run
run
10
new
11
new
12
run
13
run
14
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: 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{_=>()}}}
def converter: IO[Unit] = for {
_ <- PrintLine("Enter a temperature in Fahrenheit: ")
d <- ReadLine.map(_.toDouble)
_ <- PrintLine(fahrenheitToCelsius(d).toString)
} yield ()
converter
converter.run
1
new
2
new
1
new
…
run
14
Simple	IO	Monad

Simple IO Monad in 'Functional Programming in Scala'

  • 1.
    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.
  • 2.
    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{ _ => ()}}} def converter: IO[Unit] = for { _ <- PrintLine("Enter a temperature in Fahrenheit: ") d <- ReadLine.map(_.toDouble) _ <- PrintLine(fahrenheitToCelsius(d).toString) } yield () Simple IO Monad Functional Programming in Scala
  • 3.
    IO IO IO IO IO IO IO flatMap flatMap map map 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 1 new 2 new 3 run 4 run toDouble 5 new 6 new 7 new 8 run 9 run run 10 new 11 new 12 run 13 run 14 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 1 new 2 new 1 new … run 14 Simple IO Monad
  • 4.
    IO IO IO IO IO IO IO flatMap flatMap map map 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 1 new 2 new 3 run 4 run toDouble 5 new 6 new 7 new 8 run 9 run run 10 new 11 new 12 run 13 run 14 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: 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{_=>()}}} def converter: IO[Unit] = for { _ <- PrintLine("Enter a temperature in Fahrenheit: ") d <- ReadLine.map(_.toDouble) _ <- PrintLine(fahrenheitToCelsius(d).toString) } yield () converter converter.run 1 new 2 new 1 new … run 14 Simple IO Monad