Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Run Loop #7

Open
samuelorji opened this issue Jun 13, 2021 · 1 comment
Open

Run Loop #7

samuelorji opened this issue Jun 13, 2021 · 1 comment

Comments

@samuelorji
Copy link

Hi, I really really love your write-up. I just have a small question.

Since a for-comprehension is a series of nested flatMaps where the computation is sequenced, and the result of a previous computation is used to get the next computation. Shouldn't the Run loop here be:

FlatMap(
    Delay(() => println("What's up?")),
    (_: Unit) => FlatMap(
      Delay(() => readLine),
      (input: String) => Delay(() => println(s"Ah, $input is up!"))
    )
  )

where the function k is what leads to the next FlatMap ?

Using a toy interpreter, funny enough, I can't seem to see any difference between the two.

@slouc
Copy link
Owner

slouc commented Jun 27, 2021

Hey, sorry for the delay!

Short answer:
You're right, for-comp translates the flatMaps the way you wrote. But Cats Effect stacks the IOs in the run loop the way I wrote.🙂 It evaluates into the same thing.

Long answer:
Scala compiler translates the for-comp exactly how you wrote. But note that this format requires us to know the whole expression:

FlatMap(
  someMonad,
  functionForTheRestOfTheChain
)

With IO, we don't know the rest of the chain. So we need to build what we have right now, and later when we have the subsequent IO, we will nest the first one inside it.

val io1 = FlatMap(
    someIO,
    someFunction
)
... and then later ...
val io2 = FlatMap(
  io1,
  someFunction
)

It comes down to (A flatMap B) flatMap C vs A flatMap (B flatMap C). Both expressions evaluate into the same thing. Here's some actual code:

// the "run loop way"
val inner = Option("whats up?").flatMap(_ => Option("reading"))
val outer = inner.flatMap(_ => Option("ah, input is up!"))

// the "for comp way"
lazy val first = Option("whats up?").flatMap(_ => second)
val second = Option("reading").flatMap(_ => Option("ah, input is up!"))

assert(outer == second)

See how in the second case I even had to use lazy val for the first, because it depends on the value of second. Compiler can do that because it sees the whole for-comp expression and just turns it into a chain of flatMaps, with one final map for the yield part. Run loop doesn't know the whole expression, and it's building it iteratively (when you give me the next IO, I will take what I have so far and shove it inside).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants