Skip to content

Instantly share code, notes, and snippets.

@SethTisue
Created May 6, 2011 23:46
Show Gist options
  • Save SethTisue/960019 to your computer and use it in GitHub Desktop.
Save SethTisue/960019 to your computer and use it in GitHub Desktop.
abandoned attempt at a Haskell-style IO monad in Scala
// I started with code from:
// http://apocalisp.wordpress.com/2011/01/10/functional-programming-for-beginners/
// the filter stuff doesn't really make sense, since IO has no zero, but I was
// interested in fooling with it anyway and seeing what worked and what didn't.
// since I did this Apocalisp has done an official version and added it to Scalaz
object io {
sealed trait IO[+A] {
def run(): A
def flatMap[B](f: A => IO[B]): IO[B] =
bind(this, f)
def map[B](f: A => B): IO[B] =
bind(this, f andThen const)
def withFilter(p: A => Boolean) = new WithFilter(p)
class WithFilter(p: A => Boolean) {
def flatMap[B](f: A => IO[B]): IO[Unit] =
bindFiltered(IO.this, f, p)
def map[B](f: A => B): IO[Unit] =
mapFiltered(IO.this, f, p)
def withFilter(p2: A => Boolean) =
new WithFilter((a: A) => p(a) && p2(a))
}
}
def readLn = new IO[String] {
override def run() = readLine() }
def writeLn(a: Any) = new IO[Unit] {
override def run() = println(a.toString) }
def const[A](a: A) = new IO[A] {
override def run() = a }
def bind[A, B](a: IO[A], f: A => IO[B]) = new IO[B] {
override def run() = f(a.run()).run() }
def sequence[A, B](a: IO[A], b: => IO[B]) = new IO[B] {
override def run() = { a.run(); b.run() } }
def bindFiltered[A, B](a: IO[A], f: A => IO[B], p: A => Boolean) = new IO[Unit] {
override def run() = {
val a2 = a.run(); if(p(a2)) f(a2).run() } }
def mapFiltered[A, B](a: IO[A], f: A => B, p: A => Boolean) = new IO[Unit] {
override def run() = {
val a2 = a.run(); if(p(a2)) f(a2) } }
}
import io._
// TODO:
// - dibblego says const should be lazy. try to add that, and try to construct an example that shows
// why it matters
// - dibblego suggests:
// case class IOUnit(a: IO[Unit]) { .. go for it
// - maybe this is the same thing as Apocalisp's blog entry about writing a proper const function in Scala
// the problem is, if the predicate returns false, I need to return some sort of value
// because Scala is strict, I need to have that value right away
// if it were lazy, I could return a value that *will* signal an error if anyone actually asks for it
// but if they never do, there's no problem
// I'll look at that blog post again and see if I can insert laziness into my code in the same way.
// - doh, this doesn't compile, maybe IO[Unit] is wrong?
// def iter: IO[Unit] =
// for{line1 <- readLn
// line2 <- readLn
// x1 = line1.toInt
// if x1 > 0
// x2 = line2.toInt
// if x2 % 2 == 0
// _ <- writeLn(x1 * x2)}
// yield ()
// - how does it even make sense to write this kind of thing if the IO monad doesn't have a zero?
// is the fact that my example does "yield ()" at the end the only thing that's making this work?
// - how to stop at eof?
// - compared to "do" in Haskell, "for" is not so nice for this.
// should I invent my own DSL-y thing that's better?
def iter: IO[Unit] =
for{line1 <- readLn
line2 <- readLn
x1 = line1.toInt
x2 = line2.toInt
if x1 % 2 == 0
_ <- writeLn(x1 * x2)}
yield ()
lazy val loop: IO[Unit] = sequence(iter, loop)
loop.run()
// so e.g. from shell:
// % echo "0\n1\n1\n2\n3\n4\n5\n5\n6\n7\n7\n7\n8\n8\n8\n" | scala28 IOTest.scala
// 0
// 42
// 64
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment