Created
May 6, 2011 23:46
-
-
Save SethTisue/960019 to your computer and use it in GitHub Desktop.
abandoned attempt at a Haskell-style IO monad in Scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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