val x = ${expr}
(x, x)
(${expr}, ${expr})
are these programs the same..?
It depends on ${expr}
val x = "hello"
(x, x)
("hello", "hello")
are these programs the same..?
Yes!
val x = println("hello")
(x, x)
(println("hello"), println("hello"))
are these programs the same..?
No!
println("hello")
How can we make this referentially transparent?
() => A
() => println("hello")
def greeter: Unit = {
println("Whats your name?")
val name = StdIn.readLine()
println(s"Hello $name")
}
type Action[A] = () => A
def printLine(s: String): Action[Unit] =
() => println(s)
val readLine: Action[String] =
() => StdIn.readLine()
def greeter: Action[Unit] = {
val prompt = printLine("Whats your name?")
val readName = readLine
val printGreeting = printLine("Hello " + ???)
???
}
def after[A, B](
action: Action[A],
fn: A => Action[B]): Action[B] = // ...
implicit class AndThenOps[A](a: Action[A]) {
def andThen[B](fn: A => Action[B]): Action[B] =
after(a, fn)
}
def after[A, B](
action: Action[A],
fn: A => Action[B]
): Action[B] =
() => fn(action.apply()).apply()
def greeter: Action[Unit] = {
printLine("Whats your name?")
.andThen(_ => readLine)
.andThen(name => printLine(s"Hello $name"))
}
def greeter: Action[Unit] = {
printLine("Whats your name?")
.flatMap(_ => readLine)
.flatMap(name => printLine(s"Hello $name"))
}
def greeter: Action[Unit] =
for {
_ <- printLine("Whats your name?")
name <- readLine
_ <- printLine(s"Hello $name")
} yield ()
def greeter: Unit = {
println("Whats your name?")
val name = StdIn.readLine()
println(s"Hello $name")
}
case class IO[A](unsafeRun: () => A) {
def map[B](f: A => B): IO[B] =
IO(() => f(unsafeRun()))
def flatMap[B](f: A => IO[B]): IO[B] =
IO(() => f(unsafeRun()).unsafeRun())
}
object IO {
def pure[A](a: A): IO[A] = IO(() => a)
}