3 ms·
Purity is not a goal in itself, its purpose (its meaning) is to help me answer useful questions about the behaviour of the program. In a typical IO function, `
by victorNicollet 7y ago
Purity is not a goal in itself, its purpose (its meaning) is to help me answer useful questions about the behaviour of the program.
In a typical IO function, `fn()` only gives me 10% of the code in `fn`: the part up to the first bind. To get the rest, I need to `do` it, and this is where purity is no longer meaningful:
a <- fn "a"
b <- fn "b"
Can I swap these two lines ? The IO monad tells me there might be consequences, but what are they ? Another example:
foo fn
Will `foo` trigger the IO effects of `fn` zero, one or more times ?
I believe these are meaningful questions, and yet, a program built from pure functions can answer them no better than an imperative program.
- bad_user 7y ago> "Purity is not a goal in itself" Agreed. However having referential transparency for IO does help. Let me explain... The program you mentioned has one clear property: those two computations will execute sequentially. This sequence of executing operations becomes independent of evaluation via IO. And if you want parallelism, you need to make it explicit. val a = fn(1) val b = fn(2) for { r1 <- a r2 <- b } yield r1 + r2 For IO this always has the same execution characteristic. "B" will execute after "A". You have a clear happens-before relationship. As an exercise: 1. think of what happens if that function returns a Future/Promise 2. think of what happens if that function is synchronous, but still side effectful In both cases the actual order of execution is not guaranteed. In the second case the ordering is only guaranteed from the point of view of the current thread only. And this is interesting b/c the only way to force ordering in a multi-threaded context, besides memory barriers which are a runtime trick, is to create a data dependence. In other words, while this code doesn't guarantee visibility with IO, it does have stronger ordering guarantees than any side effectful code you can throw in there. You're a little unfair here btw. If the two operations here are not dependent on each other, then the Monad's flatMap/bind isn't the operation you want, since Monads literally describe sequencing. And you want sequencing in case order of execution does matter. In other words, when you see a sequence described via bind/flatMap, you have to assume that the order is relevant, because the author could've done this instead, which is self explanatory: (a, b).parMapN { (r1, r2) => r1 + r2 } And now they are executed in parallel, with no related evaluation gotchas (exposing the API in the Cats library here). So to say that IO doesn't give you any useful property here is factually untrue. The obvious gain is that you look at an expression and know exactly how and when it will execute.